C ++ -- boost: Introduction to the use of asio

Source: Internet
Author: User

C ++ -- boost: Introduction to the use of asio
Background

Efficient Network Programming generally depends on IO reuse. IO reuse refers to sending and listening to multiple socket or file read/write events at the same time. IO multiplexing is commonly used in two efficient methods: Reactor and Proactor. Both methods are asynchronous and non-blocking at the operating system level. That is to say, users can directly return a request after submitting it. However, the Reactor is synchronized at the user level. After a series of operations are submitted to the operating system, the Reactor needs to block the listener and wait for the event to occur, if an event occurs, you can manually call the relevant functions for processing. The operating system uses the Event Notification interface of the operating system. The Proactor is asynchronous in the user's opinion. It registers a callback function at the same time during the call. If the request has a result at the operating system level, the registered callback function will be automatically called. This aio asynchronous call interface is used at the operating system level.

 

The most significant difference is that, at TCP distance, the Reactor will notify the upper-layer application when the kernel receives TCP data, the following code extracts data from the kernel and calls the processing function for processing (when and how ). After receiving data from TCP, The Proactor will copy the data to the specified space by the kernel, and then immediately call the registered callback function for processing.

It seems that Proactor is much simpler and faster than Reactor, but it is not necessarily because of engineering reasons.

 

Introduction

Abstract The entire asynchronous platform to boost: asio: io_service. To use asio, you must first create this object. Many components can be used on the asynchronous platform, such as boost: asio: ip: tcp: socket. These components have their own methods. But the process is unified: (asio can execute synchronous and asynchronous calls)

Synchronous call. Call socket. connect (server_endpoint) or other remote interaction methods. The request is first sent to io_service. io_service calls the method of the operating system and returns the result to io_service. io_service notifies the upper-layer user component. Error Notification (can be blocked), correct return value.

For Asynchronous calls. A callback function must be provided when the component calls the io_service command to execute the command. Multiple asynchronous requests can be published at the same time. All returned results will be stored in the io_service queue. When a process calls io_service: run (), It extracts the requests stored in the queue one by one and calls the previously passed callback function for processing.

I/O objects are components used to complete actual functions. There are multiple objects:

Boost: asio: ip: tcp: socket

Boost: asio: ip: tcp: resolver

Boost: asio: ip: tcp: acceptor

Boost: asio: local: stream_protocol: socket local connection

Boost: asio: posix: stream_descriptor stream-oriented file descriptor, such as stdout and stdin

Boost: asio: deadline_timer Timer

Boost: asio: signal_set Signal Processing

Most of these objects need io_service for initialization. There is also a work class used to control the io_service lifecycle, and a buffer class used to store data.

 

Io_servicerun () vs poll ()

Run () and poll () both cyclically execute the I/O object event, the difference is that if the event is not triggered (ready), run () will wait, but poll () will return immediately. That is to say, poll () Only executes the triggered I/O events.

For example, the I/O objects socket1, socket2, and socket3 are all bound to the socket. async_read_some () event. At this time, socket1 and socket3 have data. Call poll () to execute the handler corresponding to socket1 and socket3, and then return the result. Call run () to execute the corresponding handler of socket1 and socket3, but it will continue to wait for the socket2 read event.

Stop ()

Calling io_service.stop () will abort the run loop, which is generally used in multiple threads.

Post () vs dispatch ()

Both post () and dispatch () require io_service to execute a handler, but dispatch () requires immediate execution, while post () always adds the handler to the event queue first.

When do I need to use post ()? If you do not want to call a handler immediately, but asynchronously call the handler, you should call post () to submit the handler to io_service for execution in the event queue. For example, the chat room example provided by Boost. Asio, which implements a chat room client that supports asynchronous IO, is a good example.

The write () function of chat_client.cpp uses post () to avoid synchronization in the critical section. The write () call and the async_write () execution in do_write () belong to two threads respectively. The former will write data to write_msgs _, while the latter will read data from write_msgs, if you directly call do_write () without using post (), you need to use a lock to synchronize write_msgs _. However, using post () is equivalent to scheduling write_msgs _ read/write by io_service, which is completed in a thread without the need for additional lock mechanisms.

Work class

workClass for notificationio_serviceWhether it can end, as long as the objectwork(io_service)Exist,io_serviceIt will not end. SoworkClass is more like an identifier, for example:

boost::asio::io_serviceio_service;

boost::asio::io_service::work*work = new boost::asio::io_service::work( io_service );

// deletework; // If you do not comment out this sentence, the run loop will not exit. Generally, you use shared_ptr to maintain the work object and use work. reset () to end its lifecycle.

io_service.run()

Buffer class

The buffer class is divided into mutable_buffer and const_buffer. The buffer class is very simple and has only two member variables: the pointer to the data and the corresponding data length. The buffer class does not apply for memory, but provides an encapsulation of the existing memory.

It should be noted that all functions such as async_write () and async_read () Accept the buffer type MutableBufferSequence/ConstBufferSequence, which means they can accept both boost: asio: buffer, it can also accept std: vector This type.

Buffer Management

The life cycle of the buffer zone is one of the two things that need to be paid attention to when using asio. The reason why the buffer zone needs to pay attention to is the description in Asio asynchronous call Reference:

Althoughthe buffers object may be copied as necessary, ownership of the underlyingmemory blocks is retained by the caller, which must guarantee that they remainvalid until the handler is called.

This means that the buffer zone needs to be controlled by io_service from initiating asynchronous calls to handler execution. This restriction often causes some asio code to be more troublesome than the corresponding code of the Reactor.

Let's take the example in the chat room above. The do_write () function of chat_client.cpp saves the data to std: deque after receiving the user input. Write_msgs _ queue instead of storing it in an array similar to chardata [], and then calling async_write (.. data ..) sending data to avoid this situation: the input data speed is too fast. When the handler called by async_write () has not been able to process it yet, a new data is received, if data is saved directly, the buffer of the last async_write () is overwritten. Async_write () requires the buffer to start from calling async_write () until the handler processes this time period.

Similarly, before calling the async_write () function in the do_write () function, determine whether the write_msgs _ queue is empty to ensure async_write () valid data is always obtained from the write_msgs _ queue header. when the data is sent in handle_write (), pop_front () pops up the sent data packet. In this way, the queue header is popped up before the handler of the previous async_write () is executed, leading to the corresponding buffer failure.

This is mainly because of the difference between async_write () and async_read (). The former is initiated proactively, and the latter can be controlled by io_service. Therefore, the latter does not have to worry about overwriting of the buffer. Because in the same thread, even if the events to be read are triggered faster, io_service must process them one by one.

In the example of this chat room, if you do not consider sending data in the user input order, you can use a simpler method to process the do_write () function, for example:

 

: C ++

Voiddo_write (chat_message msg)

{

Chat_message * pmsg = new chat_message (msg); // implement copy ctor for chat_message firstly

Boost: asio: async_write (socket _,

Boost: asio: buffer (pmsg-> data (), pmsg-> length ()),

Boost: bind (& chat_client: handle_write, this,

Boost: asio: placeholders: error, pmsg ));

}

Voidhandle_write (const boost: system: error_code & error, chat_message * pmsg)

{

If (! Error ){

 

} Else {

Do_close ();

}

Delete pmsg;

}

This is equivalent to allocating a memory of your own to each asynchronous call. When the asynchronous call is completed, it is automatically released, and some are similar to closures. If you do not want to frequently use new/delete memory, you can also use boost: circular_buffer to allocate memory one time and use it one by one.

I/O object socket

The most common objects of Boost. Asio should be socket. Common functions generally include the following:

When reading and writing TCP sockets, read (), async_read (), write (), and async_write () are generally used. To avoid the so-called short readsand writes, receive () is generally not used (), async_receive (), send (), async_send ().

Generally, receive (), async_receive (), send (), and async_send () are used to read and write UDP socket with connection ().

Generally, receive_from (), async_receive_from (), send_to (), and async_send_to () are used to read and write unconnected UDP sockets ().

The free function boost: asio: async_write () and the class member function socket. what is the difference between async_write_some () (boost: asio: async_read () and socket. async_read_some () is similar ):

Boost: asio: async_write () asynchronous write. return immediately. But it can ensure that the content of the entire buffer zone is written; otherwise, an error will be reported. Boost: asio: async_write () is called n times by socket. async_write_some (), so the Code must ensure that no other write operations are executed on the same socket during boost: asio: async_write. When you call boost: asio: async_write (), if the length of the specified buffer is not written or an error occurs, the corresponding handler will not be called back, it will always be executed in the run loop; it will not call handler to continue processing until all data in the buffer is written or an error occurs (the length returned by handler is definitely smaller than the buffer length; and socket. async_write_some () won't have such a problem. It will only try to write it once, and the written length will be returned in the handler parameter.

Therefore, the second important thing to pay attention to when using asio is the return value of handler (generally declared as boost: asio: placeholders: error ). Because all tasks in asio are asynchronously executed by io_service, handler is called back only after the execution is successful or fails. Therefore, the returned value is the only way to understand the current asynchronous operation status, remember not to ignore handler's return value processing.

Signal Processing

The signal processing of Boost. Asio is very simple. Declare a signal set and then bind the corresponding asynchronous handler. If you want to process all the signals in a signal set, you can obtain the signal currently triggered based on the second handler parameter. For example:

Boost: asio: signal_set signals (io_service, SIGINT, SIGTERM );

Signals. add (SIGUSR1); // You can also directly use the add function to add signals.

 

Signals. async_wait (boost: bind (handler, _ 1, _ 2 ));

 

Void handler (

Constboost: system: error_code & error,

Intsignal_number // obtain the currently triggered signal value through this parameter

);

Timer

The timer of Boost. Asio is as simple as the root signal set, but it is too simple and inconvenient. For example, in a UDP server, generally each UDP packet received contains a sequence number to identify the UDP to handle Packet Handling timeout. Assume that the processing time of each UDP packet is only 100 ms. If the packet times out, the timeout mark is directly returned to the client. Some Reactor frameworks commonly used in the simplest timer have perfect solutions. Generally, they are implemented by creating a timer linked list, but the timer in Asio cannot do this work independently.

Boost: asio: deadline_timer has only two statuses: timeout and no timeout. Therefore, only one timer can be created for each UDP packet, and then the sequence number to the timer ing is saved with std: map and boost: shared_ptr, determine whether the timer times out or is actively canceled based on the return value of the timer handler.

Strand

In multithreading, handler of multiple I/O objects must access the same critical section. In this case, strand can be used to ensure synchronization between these handler.

Example:

 

We register func1 and func2 with the timer, which may simultaneously access global objects (such as std: cout ). In this case, we want to synchronize the calls to func1 and func2, that is, when one of them is executed, the other is waiting.

 

In this case, the boost: asio: strand class can be used. It can package several cmd commands for Synchronous execution. For example, when we register func1 and func2 with the timer, we can change it:

 

Boost: asio: strand the_strand;

T1.async _ wait (the_strand.wrap (func1); // wrap

T2.async _ wait (the_strand.wrap (func2 ));

This ensures that func1 and func2 are not executed at any time.

In addition, if you want to bind an io_service object to multiple threads. In this case, boost: asio: strand is required to ensure that handler is not executed at the same time, because asynchronous operations such as async_write and async_receive_from affect the buffer in the critical section.

For details, refer to the example in asio examples: connection. hpp Design of HTTPServer 2 and HTTP Server 3.

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.