Introduction to the official tutorial of Boost asio

1. General

This chapter introduces the Boost C + + library Asio, which is the core of asynchronous input and output. The name itself says everything: Asio means asynchronous input / output. The library allows C + + to process data asynchronously and is platform independent. Asynchronous data processing means that there is no need to wait for tasks to complete after they are triggered. Instead, boost Asio will trigger an application when the task is completed. The main advantage of asynchronous tasks is that there is no need to block the application while waiting for the task to complete, and other tasks can be performed.
 
A typical example of asynchronous tasks is network applications. If the data is sent out, such as to the Internet, you usually need to know whether the data is sent successfully. If there's no one like boost For libraries such as ASIO, the return value of the function must be evaluated. However, it is required to wait until all data is sent and a confirmation or error code is obtained. Instead, use boost ASIO, this process is divided into two separate steps: the first step is to start data transmission as an asynchronous task. Once the transfer is completed, whether successful or error, the application will be notified of the corresponding results in the second step. The main difference is that the application does not need to block until the transmission is completed, but can perform other operations during this time.
 

2. I/O service and I/O object

Use boost Asio's applications for asynchronous data processing are based on two concepts: I/O services and I/O objects. I/O services abstract the interface of the operating system, allowing asynchronous data processing at the first time, while I/O objects are used to initialize specific operations. Whereas boost Asio only provides one named boost:: Asio:: io_ As an I/O service, the class of service implements optimized classes for each supported operating system. In addition, the library also contains several classes for different I/O objects. Among them, class boost::asio::ip::tcp::socket is used to send and receive data through the network, while class boost::asio::deadline_timer provides a timer to measure the arrival of a fixed point in time or the passage of a specified period of time. In the first example below, the timer is used because it does not require any knowledge of network programming compared with other I/O objects provided by Asio.

#include <iostream>  
#include <boost/asio.hpp> 

 
 void handler(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 int main() 
 { 
   boost::asio::io_service io_service; 
   boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(5)); 
   timer.async_wait(handler); 
   io_service.run(); 
 } 


The function main() first defines an I/O service io_service, used to initialize the I/O object timer. Like boost::asio::deadline_timer, all I/O objects usually need an I/O service as the first parameter of their constructor. Since timer functions like an alarm clock, boost:: ASIO:: deadline_ The constructor of timer can pass in the second parameter to indicate that the alarm clock stops at a certain point in time or after a certain period of time. The above example specifies the duration of five seconds. The alarm clock starts counting immediately after the timer is defined.
 
Although we can call a function that returns after five seconds, we can call the method async_wait() and pass in the name of the handler() function as the unique parameter to enable Asio to start an asynchronous operation. Note that we just passed in the name of the handler() function, and the function itself was not called.
 
 async_ The advantage of wait () is that the function call returns immediately instead of waiting for five seconds. Once the alarm time is up, the function provided as a parameter will be called accordingly. Therefore, the application can call async_ Perform other operations after wait() instead of blocking here.
 
Like Async_ Methods such as wait() are called non blocking. I/O objects also typically provide blocking methods that allow execution flow to remain blocked until a particular operation is completed. For example, you can call the blocking wait() method instead of boost:: ASIO:: deadline_ Call of timer. Because it blocks the call, it does not need to pass in a function name, but returns after a specified point in time or a specified length of time.
 
Looking at the source code above, you can notice that async is being called_ After wait (), a method called run() is called on the I/O service. This is necessary because control must be taken over by the operating system before the handler() function can be called in five seconds.
 
 async_ run() starts asynchronously and returns a blocking operation. Therefore, the program execution will stop after calling run(). Ironically, many operating systems only support asynchronous operations through blocking functions. The following example shows why this limitation is usually not a problem.

 #include <boost/asio.hpp> 
 #include <iostream> 
 
 void handler1(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 void handler2(const boost::system::error_code &ec) 
 { 
   std::cout << "10 s." << std::endl; 
 } 
 
 int main() 
 { 
   boost::asio::io_service io_service; 
   boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(5)); 
   timer1.async_wait(handler1); 
   boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(10)); 
   timer2.async_wait(handler2); 
   io_service.run(); 
 } 


The above program uses two boost:: ASIO:: deadline_ I/O object of type timer. The first I/O object represents an alarm clock triggered after five seconds, while the second represents an alarm clock triggered after ten seconds. After each specified period of time has passed, the functions handler1() and handler2() will be called accordingly.
 
At the end of main(), the run() method is called again on the only I/O service. As mentioned earlier, this function blocks execution and gives control to the operating system to take over asynchronous processing. With the help of the operating system, the handler1() function will be called in five seconds, while the handler2() function will be called in ten seconds.
 
At first glance, you may wonder why asynchronous processing calls the blocking run() method. However, since the application must be prevented from being aborted, there is virtually no problem doing so. If run() is not blocked, main() ends, aborting the application. If the application should not be blocked, it should call run() inside a new thread, which will naturally block only that thread.
 
Once all asynchronous operations for a particular I/O service are completed, control is returned to the run() method, which then returns. In the above two examples, the application will end immediately after the alarm clock arrives.
 

3. Scalability and multithreading


Use boost ASIO such a library to develop applications, and the general C + + style is different. It is not possible to call those functions in a longer order. Instead of calling blocking functions, boost ASIO starts an asynchronous operation. Those functions that need to be called after the operation are implemented as corresponding handles. The disadvantage of this method is that the functions originally executed in sequence become physically separated, making the corresponding code more difficult to understand.
 
Like boost Libraries like ASIO are often designed to make applications more efficient. Instead of waiting for a specific function to complete, the application can perform other tasks during the execution, such as starting another operation that takes a long time.
 
Scalability refers to the ability of an application to effectively benefit from new resources. If those operations that take a long time should not block other operations, it is recommended to use boost Asio. Because today's PC s usually have multi-core processors, the application of threads can further improve the one based on boost ASIO's application scalability.
 
If in a boost:: ASIO:: IO_ If the run() method is called on an object of type service, the associated handle will also be executed in the same thread. By using multithreading, an application can call multiple run() methods at the same time. Once an asynchronous operation ends, the corresponding I/O service will execute the handle in one of these threads. If the second operation ends soon after the first operation, the I/O service can execute the handle in another thread without waiting for the first handle to terminate.

 #include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <iostream> 
 
 void handler1(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 void handler2(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 boost::asio::io_service io_service; 
 
 void run() 
 { 
   io_service.run(); 
 } 
 
 int main() 
 { 
   boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(5)); 
   timer1.async_wait(handler1); 
   boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(5)); 
   timer2.async_wait(handler2); 
   boost::thread thread1(run); 
   boost::thread thread2(run); 
   thread1.join(); 
   thread2.join(); 
 } 


The example in the previous section has now become a multithreaded application. By using in boost / Thread The boost::thread class defined in HPP comes from the Boost C + + library Thread. We created two threads in main(). Both threads call the run() method for the same I/O service. In this way, when the asynchronous operation is completed, the I/O service can use two threads to execute the handle function.
 
Both clocks in this example are set to trigger after five seconds. Since there are two threads, handler1() and handler2() can be executed simultaneously. If the first timer is still executing when the second timer is triggered, the second handle is executed in the second thread. If the handle of the first timer has terminated, the I/O service is free to choose any thread.
 
Threads can improve the performance of applications. Because threads are executed on the processor kernel, it makes no sense to create more threads than the kernel. This ensures that each thread executes on its own kernel and that no other thread on the same kernel competes with it.
 
Note that using threads is not always worth it. The operation of the above example will result in mixed output of different information on the standard output stream, because the two handles may run in parallel and access the same shared resource: standard output stream std::cout. This access must be synchronized to ensure that each piece of information is completely written out before another thread can write another piece of information to the standard output stream. In this case, using threads does not provide much benefit if individual handles cannot run independently in parallel.
 
Calling the run() method of the same I/O service multiple times is based on boost ASIO's application is recommended to increase scalability. There is also a different approach: instead of binding multiple threads to a single I/O service, create multiple I/O services. Each I/O service then uses a thread. If the number of I/O services matches the number of processor cores of the system, asynchronous operations can be performed on their respective cores.

 #include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <iostream> 
 
 void handler1(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 void handler2(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 boost::asio::io_service io_service1; 
 boost::asio::io_service io_service2; 
 
 void run1() 
 { 
   io_service1.run(); 
 } 
 
 void run2() 
 { 
   io_service2.run(); 
 } 
 
 int main() 
 { 
   boost::asio::deadline_timer timer1(io_service1, boost::posix_time::seconds(5)); 
   timer1.async_wait(handler1); 
   boost::asio::deadline_timer timer2(io_service2, boost::posix_time::seconds(5)); 
   timer2.async_wait(handler2); 
   boost::thread thread1(run1); 
   boost::thread thread2(run2); 
   thread1.join(); 
   thread2.join(); 
 } 


The previous example using two timers was rewritten to use two I/O services. The application is still based on two threads; But now each thread is bound to a different I/O service. In addition, the two I/O objects timer1 and timer2 are now bound to different I/O services.
 
The functionality of this application is the same as the previous one. Under certain conditions, it is beneficial to use multiple I/O services. Each I/O service has its own thread, preferably running on its own processor core, so that each asynchronous operation together with their handles can be executed locally. If there is no remote data or function to access, then each I/O service is like a small autonomous application. Local and remote resources here refer to resources such as cache and memory pages. Since it is necessary to have a special understanding of the underlying hardware, operating system, compiler and potential bottlenecks before determining the optimization strategy, multiple I/O services should be used only when these benefits are clear.
 

4. Network programming


Although boost ASIO is a library that can process any kind of data asynchronously, but it is mainly used for network programming. This is because, in fact, boost ASIO has supported network functions long before adding other I/O objects. Network function is a good example of asynchronous processing, because data transmission through the network may take a long time, so confirmation or error conditions cannot be obtained directly.
 
 Boost.Asio provides multiple I/O objects to develop network applications. The following example uses the boost::asio::ip::tcp::socket class to establish a connection with another PC in and download the 'Highscore' homepage; It's like a browser pointing to www.Highscore.com What to do when De.

 #include <boost/asio.hpp> 
 #include <boost/array.hpp> 
 #include <iostream> 
 #include <string> 
 
 boost::asio::io_service io_service; 
 boost::asio::ip::tcp::resolver resolver(io_service); 
 boost::asio::ip::tcp::socket sock(io_service); 
 boost::array<char, 4096> buffer; 
 
 void read_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
 { 
   if (!ec) 
   { 
     std::cout << std::string(buffer.data(), bytes_transferred) << std::endl; 
     sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
   } 
 } 
 
 void connect_handler(const boost::system::error_code &ec) 
 { 
   if (!ec) 
   { 
     boost::asio::write(sock, boost::asio::buffer("GET / HTTP 1.1\r\nHost: highscore.de\r\n\r\n")); 
     sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
   } 
 } 
 
 void resolve_handler(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it) 
 { 
   if (!ec) 
   { 
     sock.async_connect(*it, connect_handler); 
   } 
 } 
 
 int main() 
 { 
   boost::asio::ip::tcp::resolver::query query("www.highscore.de", "80"); 
   resolver.async_resolve(query, resolve_handler); 
   io_service.run(); 
 } 


The most obvious part of this program is the use of three handles: connect_handler() and read_ The handler () function will be called after the connection is established and the data is received. So why do you need resolve_ What about the handler () function?
 
The Internet uses so-called IP addresses to identify each PC. The IP address is actually just a long string of numbers, which is difficult to remember. And remember things like www.highscore A name like De is much easier. In order to use similar names on the Internet, they need to be translated into corresponding IP addresses through a process called domain name resolution. This process is completed by the so-called domain name parser. The corresponding I/O object is: boost::asio::ip::tcp::resolver.
 
Domain name resolution is also a process that needs to be connected to the Internet. Some special PCs, called DNS servers, function like a phonebook. They know which IP address is assigned to which PC. Because the process itself is transparent, you only need to understand the concept behind it and why you need the boost::asio::ip::tcp::resolver I/O object. Since domain name resolution does not occur locally, it is also implemented as an asynchronous operation. Once the domain name resolution is successful or interrupted by an error, resolve_ The handler () function is called.
 
Because receiving data requires a successful connection and then a successful domain name resolution, these three different asynchronous operations should be started with three different handles. resolve_handler() accesses the I/O object sock and creates a connection with the resolved address provided by the iterator it. And the sock is also connecting_ Handler () is used internally to send HTTP requests and start data reception. Because all these operations are asynchronous, the name of each handle is passed as a parameter. Depending on each handle, corresponding other parameters are required, such as iterator it pointing to the resolved address or buffer used to store the received data.
 
After execution, the application will create an object query of type boost::asio::ip::tcp::resolver::query, which represents a query with the name www.highscore De and port 80 commonly used on the Internet. This query is passed to async_resolve() method to resolve the name. Finally, main() just calls the run() method of the I/O service and hands over the control to the operating system for asynchronous operation.
 
When the domain name resolution process is completed, resolve_handler() is called to check whether the domain name can be resolved. If the resolution is successful, the object ec with the error condition is set to 0. Only in this case will the socket be accessed accordingly to create a connection. The address of the server is provided through the second parameter of type boost::asio::ip::tcp::resolver::iterator.
 
Async called_ After the connect () method, connect_handler() will be called automatically. Inside the handle, the ec object is accessed to check whether the connection has been established. If the connection is valid, async is called on the corresponding socket_ read_ Some () method to start data reading operation. In order to save the received data, a buffer is provided as the first parameter. In the above example, the type of buffer is boost::array, which comes from the Boost C + + library Array and is defined in boost / Array hpp.
 
Whenever one or more bytes are received and saved to the buffer, read_ The handler () function is called. The exact number of bytes is determined by STD:: size_ Parameter bytes of type T_ Transferred is given. Under the same rule, the handle should first look at the parameter ec to check for reception errors. If the reception is successful, the data is written out to the standard output stream.
 
Please note that read_handler() will call async again after writing out the data to std::cout_ read_ Some() method. This is necessary because there is no guarantee that the entire web page can be received in only one asynchronous operation. async_read_some() and read_ The alternate call to handler () is aborted only when the connection is broken, such as when the web server has sent a complete web page. In this case, in read_handler() will report an error internally to prevent further data output to the standard output stream and further calls to async on the socket_ Read() method. At this point, the routine will stop because there are no more asynchronous operations.
 
The last example is used to retrieve www.highscore.com The next example demonstrates a simple web server. The main difference is that the application will not connect to other PC s, but wait for the connection.
 

#include <boost/asio.hpp> 
 #include <string> 
 
 boost::asio::io_service io_service; 
 boost::asio::ip::tcp::endpoint endpoint(boost::asio::ip::tcp::v4(), 80); 
 boost::asio::ip::tcp::acceptor acceptor(io_service, endpoint); 
 boost::asio::ip::tcp::socket sock(io_service); 
 std::string data = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, world!"; 
 
 void write_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
 { 
 } 
 
 void accept_handler(const boost::system::error_code &ec) 
 { 
   if (!ec) 
   { 
     boost::asio::async_write(sock, boost::asio::buffer(data), write_handler); 
   } 
 } 
 
 int main() 
 { 
   acceptor.listen(); 
   acceptor.async_accept(sock, accept_handler); 
   io_service.run(); 
 } 


The I/O object acceptor of type boost::asio::ip::tcp::acceptor - is initialized to the specified protocol and port number - is used to wait for incoming connections from other PC s. The initialization is done through the endpoint object. The object type is boost::asio::ip::tcp::endpoint. The receiver in this example is configured to use port 80 to wait for the incoming connection of IP v4. This is the port and protocol commonly used by the WWW.
 
After the initialization of the receiver is completed, main() first calls the listen() method to put the receiver in the receiving state, and then async_ The accept () method waits for the initial connection. The socket used to send and receive data is passed as the first parameter.
 
When a PC tries to establish a connection, accept_handler() is called automatically. If the connection request is successful, execute the free function boost::asio::async_write() to send the information saved in data through socket. boost::asio::ip::tcp::socket and another one named Async_ write_ The method of some () can also send data; However, it will call the associated handle after sending at least one byte. The handle needs to calculate how many bytes remain and call async repeatedly_ write_ Some() until all bytes are sent. Instead, use boost::asio::async_write() avoids this because the asynchronous operation ends only after all bytes of the buffer have been sent.
 
In this example, when all data is sent, the empty function write_handler() will be called. The application terminated because all asynchronous operations have completed. The connection with other PC s is also closed accordingly.

5. Develop boost ASIO extension

Although boost ASIO mainly supports network functions, but it is also very easy to add other I/O objects to perform other asynchronous operations. This section describes boost A general layout of ASIO extension. Although this is not necessary, it provides a feasible framework for other extensions as a starting point.
 
To apply to boost To add a new asynchronous operation to ASIO, you need to implement the following three classes:
 
One derived from boost::asio::basic_io_object to represent the new I/O object. Use this new boost Developers of ASIO extensions will only see this I/O object.
 
One derived from boost:: ASIO:: IO_ The class of service:: service represents a service, which is registered as an I/O service and can be accessed from an I/O object. I/O of a given service can only be accessed by one service instance at any time, which is very different from I/O of a given service.
 
A class that does not derive from any other class represents the specific implementation of the service. Since each I/O service can only have one service instance at any given point in time, the service will create an instance of its specific implementation for each I/O object. This instance manages the internal data related to the corresponding I/O object.
 
Boost.com developed in this section ASIO extension not only provides a framework, but also simulates an available boost::asio::deadline_timer object. It is the same as the original boost:: asio:: deadline_ The difference of timer is that the duration of timer is passed as a parameter to wait() or async_wait() method instead of passing it to the constructor.

#include <boost/asio.hpp> 
 #include <cstddef> 
 
 template <typename Service> 
 class basic_timer 
   : public boost::asio::basic_io_object<Service> 
 { 
   public: 
     explicit basic_timer(boost::asio::io_service &io_service) 
       : boost::asio::basic_io_object<Service>(io_service) 
     { 
     } 
 
     void wait(std::size_t seconds) 
     { 
       return this->service.wait(this->implementation, seconds); 
     } 
 
     template <typename Handler> 
     void async_wait(std::size_t seconds, Handler handler) 
     { 
       this->service.async_wait(this->implementation, seconds, handler); 
     } 
 }; 


Each I/O object is usually implemented as a template class, which requires instantiation with a service - usually the service specifically developed for this I/O object. When an I/O object is instantiated, the service will pass the parent class boost::asio::basic_io_object is automatically registered as an I/O service unless it has been previously registered. Each I/O service is registered only once for any I/O service.
 
Inside the I/O object, you can access the corresponding service through the service reference. The usual access is to go to the service before calling the method. Since the service needs to save data for each I/O object, an instance should be automatically created for each I/O object using the service. This is also in the parent class boost:: ASIO:: basic_ io_ With the help of object. The actual service implementation is passed as a parameter to any method call, so that the service can know which I/O object started the call. The specific implementation of the service is accessed through the implementation attribute.
 
Generally speaking, I/O objects are relatively simple: the installation of services and the creation of service implementations are all done by the parent class boost::asio::basic_io_object, and the method call is only transferred to the corresponding service; Take the actual service implementation of the I/O object as the parameter.

 #include <boost/asio.hpp> 
 #include <boost/thread.hpp> 
 #include <boost/bind.hpp> 
 #include <boost/scoped_ptr.hpp> 
 #include <boost/shared_ptr.hpp> 
 #include <boost/weak_ptr.hpp> 
 #include <boost/system/error_code.hpp> 
 
 template <typename TimerImplementation = timer_impl> 
 class basic_timer_service 
   : public boost::asio::io_service::service 
 { 
   public: 
     static boost::asio::io_service::id id; 
 
     explicit basic_timer_service(boost::asio::io_service &io_service) 
       : boost::asio::io_service::service(io_service), 
       async_work_(new boost::asio::io_service::work(async_io_service_)), 
       async_thread_(boost::bind(&boost::asio::io_service::run, &async_io_service_)) 
     { 
     } 
 
     ~basic_timer_service() 
     { 
       async_work_.reset(); 
       async_io_service_.stop(); 
       async_thread_.join(); 
     } 
 
     typedef boost::shared_ptr<TimerImplementation> implementation_type; 
 
     void construct(implementation_type &impl) 
     { 
       impl.reset(new TimerImplementation()); 
     } 
 
     void destroy(implementation_type &impl) 
     { 
       impl->destroy(); 
       impl.reset(); 
     } 
 
     void wait(implementation_type &impl, std::size_t seconds) 
     { 
       boost::system::error_code ec; 
       impl->wait(seconds, ec); 
       boost::asio::detail::throw_error(ec); 
     } 
 
     template <typename Handler> 
     class wait_operation 
     { 
       public: 
         wait_operation(implementation_type &impl, boost::asio::io_service &io_service, std::size_t seconds, Handler handler) 
           : impl_(impl), 
           io_service_(io_service), 
           work_(io_service), 
           seconds_(seconds), 
           handler_(handler) 
         { 
         } 
 
         void operator()() const 
         { 
           implementation_type impl = impl_.lock(); 
           if (impl) 
           { 
               boost::system::error_code ec; 
               impl->wait(seconds_, ec); 
               this->io_service_.post(boost::asio::detail::bind_handler(handler_, ec)); 
           } 
           else 
           { 
               this->io_service_.post(boost::asio::detail::bind_handler(handler_, boost::asio::error::operation_aborted)); 
           } 
       } 
 
       private: 
         boost::weak_ptr<TimerImplementation> impl_; 
         boost::asio::io_service &io_service_; 
         boost::asio::io_service::work work_; 
         std::size_t seconds_; 
         Handler handler_; 
     }; 
 
     template <typename Handler> 
     void async_wait(implementation_type &impl, std::size_t seconds, Handler handler) 
     { 
       this->async_io_service_.post(wait_operation<Handler>(impl, this->get_io_service(), seconds, handler)); 
     } 
 
   private: 
     void shutdown_service() 
     { 
     } 
 
     boost::asio::io_service async_io_service_; 
     boost::scoped_ptr<boost::asio::io_service::work> async_work_; 
     boost::thread async_thread_; 
 }; 
 
 template <typename TimerImplementation> 
 boost::asio::io_service::id basic_timer_service<TimerImplementation>::id; 


In order to communicate with boost For ASIO integration, a service must meet several requirements:
 
It must be derived from boost::asio::io_service::service. The constructor must accept a reference to the I/O service, which will be passed to boost:: ASIO:: IO accordingly_ Service:: constructor of service.
 
Any service must contain a service of type boost:: ASIO:: io_ Static public attribute id of service:: id. This attribute is used to identify the service inside the I/O service.
 
You must define two public methods named construct() and construct(), both of which require an implementation type_ Parameter of type. implementation_type is usually the type definition of the specific implementation of the service. As shown in the above example, a boost:: shared can be easily used in construct()_ PTR object to initialize a service implementation and destruct it accordingly in destruct(). Since these two methods are automatically called when an I/O object is created or destroyed, a service can create and destroy service implementations for each I/O object using construct() and destroy () respectively.
 
You must define a named shutdown_ Method of service(); But it can be private. For general boost For ASIO extensions, it is usually an empty method. Only with boost ASIO is only used by services that are very tightly integrated. However, this method must have so that the extension can be compiled successfully.
 
In order to forward the method call to the corresponding service, the method to forward must be defined for the corresponding I/O object. These methods usually have names similar to those in I/O objects, such as wait () and async in the above example_ wait(). Synchronous methods, such as wait (), only access the specific implementation of the service to call a blocking method, while asynchronous methods, such as async_wait() is called the blocking method in a thread.
 
Using asynchronous operations with the help of threads is usually accomplished by accessing a new I/O service. The above example contains an example called async_io_service_ The type of the attribute is boost::asio::io_service. The run () method of the I/O service is started in its own thread, and its thread is async of type boost::thread inside the constructor of the service_ thread_ Created. The third attribute is async_work_ The type of is boost:: scoped_ ptr<boost::asio::io_service::work > to avoid the immediate return of the run() method. Otherwise, this may occur because no other asynchronous operation is being created. Create a file of type boost::asio::io_service::work object and bind it to the I/O service. This action also occurs in the constructor of the service, which can prevent the run() method from returning immediately.
 
A service can also be implemented without accessing its own I/O service - single thread is enough. The reason for using a new I/O service for new threads is that it is simpler: threads can use I/O services to communicate with each other very easily. In this example, async_wait() creates a file of type wait_operation and pass it to the internal I/O service through the post() method. The overloaded operator () of the function object is then called within the thread of the run() method used to execute the internal I/O service. post() provides a simple way to execute a function object in another thread.
 
 wait_ The overloaded operator () operator of operation basically performs the same work as the wait() method: calling the blocking wait() method in the service implementation. However, it is possible that the I/O object and its service implementation are destroyed during the execution of the operator()() operator by this thread. If the service implementation is destroyed in destroy (), the operator()() operator will no longer be able to access it. This situation is prevented by using a weak pointer. From Chapter 1, we know that if the service implementation still exists when lock() is called, the weak pointer impl_ Returns one of its shared pointers, otherwise it will return 0. In this case, instead of accessing the service implementation, operator()() uses a boost::asio::error::operation_aborted error to call handle.

#include <boost/system/error_code.hpp> 
 #include <cstddef> 
 #include <windows.h> 
 
 class timer_impl 
 { 
   public: 
     timer_impl() 
       : handle_(CreateEvent(NULL, FALSE, FALSE, NULL)) 
     { 
     } 
 
     ~timer_impl() 
     { 
       CloseHandle(handle_); 
     } 
 
     void destroy() 
     { 
       SetEvent(handle_); 
     } 
 
     void wait(std::size_t seconds, boost::system::error_code &ec) 
     { 
       DWORD res = WaitForSingleObject(handle_, seconds * 1000); 
       if (res == WAIT_OBJECT_0) 
         ec = boost::asio::error::operation_aborted; 
       else 
         ec = boost::system::error_code(); 
     } 
 
 private: 
     HANDLE handle_; 
 }; 


Service implementation timer_impl uses Windows API functions and can only be compiled and used in windows. The purpose of this example is only to illustrate a potential implementation.
 
 timer_impl provides two basic methods: wait() to wait a few seconds. destroy() is used to cancel a wait operation, which is necessary, because for asynchronous operations, the wait () method is invoked in its own thread. If the I/O object and its service implementation are destroyed, the blocking wait () method should use destroy() as much as possible to cancel.
 
This boost ASIO extensions can be used as follows.  

#include <boost/asio.hpp> 
 #include <iostream> 
 #include "basic_timer.hpp" 
 #include "timer_impl.hpp" 
 #include "basic_timer_service.hpp" 
 
 void wait_handler(const boost::system::error_code &ec) 
 { 
   std::cout << "5 s." << std::endl; 
 } 
 
 typedef basic_timer<basic_timer_service<> > timer; 
 
 int main() 
 { 
   boost::asio::io_service io_service; 
   timer t(io_service); 
   t.async_wait(5, wait_handler); 
   io_service.run(); 
 } 


 
Compared with the example at the beginning of this chapter, this boost The usage of ASIO extension is similar to boost::asio::deadline_timer. In practice, boost:: ASIO:: deadline should be preferred_ Timer, because it has been integrated in boost ASIO's in. The only purpose of this extension is to demonstrate boost How ASIO extends new asynchronous operations.
 
Directory Monitor is a boost in reality ASIO extension, which provides an I/O object that can monitor directories. If a file in the monitored directory is created, modified or deleted, a handle will be called accordingly. The current version supports Windows and Linux (kernel version 2.6.13 or above).

6. Practice

 
     You can buy solutions to all exercises in this book as a ZIP file.
 
Modify the server program in section 7.4 "network programming" to not terminate after one request, but can process any number of requests.
 
The HTML code received in section 7.7 of the "instant program" in a client. If found, download the corresponding resources at the same time. For this exercise, use only the first URL. Ideally, the website and its resources should be saved in two files instead of being written out to the standard output stream at the same time.
 
Create a client / server application to transfer files between two PC s. When the server starts, it should display the IP addresses of all local interfaces and wait for the client to connect. The client should take an IP address of the server and the file name of a local file as command line parameters. The client should send the file to the server, which saves it accordingly. In the process of transmission, the client shall provide users with some visual display of progress.

Tags: C++ boost

Posted by aliasneo86 on Mon, 11 Apr 2022 21:43:53 +0300