Boost asio official tutorial

7.1. Overview
 
 This chapter introduces the Boost C++ library Asio, which is the core of asynchronous input and output. The name itself says it all: Asio means asynchronous input/output. This 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 they do not need to block the application while waiting for the task to complete, and can perform other tasks.
 
 A typical example of asynchronous tasks is network applications. If the data is sent, such as to the Internet, it is usually necessary to know whether the data is sent successfully. If there is no library like Boost.Asio, the return value of the function must be evaluated. However, this requires waiting until all data has been sent and a confirmation or error code is obtained. With 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 complete, regardless of success or error, the application will be notified about the corresponding result in the second step. The main difference is that the application does not need to block until the transfer is complete, but can perform other operations during this time.
 7.2. I/O Services and I/O Objects
 
 Applications that use Boost.Asio for asynchronous data processing are based on two concepts: I/O services and I/O objects. The I/O service abstracts the interface of the operating system, allowing asynchronous data processing for the first time, while the I/O object is used to initialize specific operations. Since Boost.Asio only provides a class named boost::asio::io_service as an I/O service, it implements optimized classes for each supported operating system. In addition, the library also contains Several classes of I/O objects. Among them, the class boost::asio::ip::tcp::socket is used to send and receive data through the network, and the class boost::asio::deadline_timer provides a timer to measure the arrival of a certain fixed time point Or a specified period of time has passed. The timer is used in the first example below because it does not require any knowledge about network programming compared with other I/O objects provided by Asio.

#include <boost/asio.hpp> 
#include <iostream> 
 
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, which is used to initialize the I/O object timer. Just like boost::asio::deadline_timer, all I/O objects usually require an I/O service as the first parameter of their constructor. Because the timer functions like an alarm clock, the constructor of boost::asio::deadline_timer can pass in a second parameter to indicate that the alarm stops at a certain point in time or after a certain period of time. The above example specifies a duration of five seconds, and the alarm clock starts timing immediately after the timer is defined.
 
 Although we can call a function that returns after five seconds, by calling the method async_wait() and passing in the name of the handler() function as the only parameter, Asio can start an asynchronous operation. Please note that we only passed the name of the handler() function, and the function itself was not called.
 
 The benefit of async_wait() is that the function call will return 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 perform other operations after calling async_wait() instead of blocking here.
 
 Methods like async_wait() are called non-blocking. I/O objects usually also provide blocking methods that allow the execution flow to remain blocked until a specific operation is completed. For example, you can call the blocking wait() method instead of boost::asio::deadline_timer. Because it blocks the call, it does not need to pass in a function name, but returns after a specified time point or a specified time period.
 
 Looking at the above source code again, you can notice that after calling async_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 after five seconds.
 
 async_wait() will start an asynchronous operation and return immediately, while run() is blocking. Therefore, the program execution will stop after calling run(). The irony is that many operating systems only support asynchronous operations through blocking functions. The following example shows why this limitation is usually not a problem.

The function main() first defines an I/O service io_service, which is used to initialize the I/O object timer. Just like boost::asio::deadline_timer, all I/O objects usually require an I/O service as the first parameter of their constructor. Because the timer functions like an alarm clock, the constructor of boost::asio::deadline_timer can pass in a second parameter to indicate that the alarm stops at a certain point in time or after a certain period of time. The above example specifies a duration of five seconds, and the alarm clock starts timing immediately after the timer is defined.
 
 Although we can call a function that returns after five seconds, by calling the method async_wait() and passing in the name of the handler() function as the only parameter, Asio can start an asynchronous operation. Please note that we only passed the name of the handler() function, and the function itself was not called.
 
 The benefit of async_wait() is that the function call will return 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 perform other operations after calling async_wait() instead of blocking here.
 
 Methods like async_wait() are called non-blocking. I/O objects usually also provide blocking methods that allow the execution flow to remain blocked until a specific operation is completed. For example, you can call the blocking wait() method instead of boost::asio::deadline_timer. Because it blocks the call, it does not need to pass in a function name, but returns after a specified time point or a specified time period.
 
 Looking at the above source code again, you can notice that after calling async_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 after five seconds.
 
 async_wait() will start an asynchronous operation and return immediately, while run() is blocking. Therefore, the program execution will stop after calling run(). The irony is that 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_timer type I/O objects. The first I/O object represents an alarm that is triggered after five seconds, while the second represents an alarm that is 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 will block execution and pass control to the operating system to take over asynchronous processing. With the help of the operating system, the handler1() function will be called after five seconds, and the handler2() function will be called after ten seconds.

At first glance, you may find it a bit strange, why asynchronous processing calls the blocking run() method. However, since the application must prevent execution from being aborted, there is actually no problem in doing so. If run() is not blocking, main() will end and terminate the application. If the application should not be blocked, then run() should be called in a new thread, and it will naturally only block that thread.

Once all asynchronous operations of a particular I/O service are completed, control is returned to the run() method, and then it returns. In the above two examples, the application will end as soon as the alarm time expires.

7.3. Scalability and multithreading

Using libraries like Boost.Asio to develop applications is different from the general C++ style. Those functions that may take a long time to return are no longer called in a sequential manner. Instead of calling blocking functions, Boost.Asio starts an asynchronous operation. The functions that need to be called after the operation is over are implemented as corresponding handles. The disadvantage of this method is that the functions that were originally executed sequentially have become physically separated, making the corresponding code more difficult to understand.

Libraries like Boost.Asio are usually used to make applications more efficient. The application does not need to wait for the completion of a specific function, but can perform other tasks in the meantime, such as starting another operation that takes a long time.

Scalability refers to the ability of an application to effectively obtain benefits from new resources. If those operations with a long execution time should not block other operations, then it is recommended to use Boost.Asio. Since today's PCs usually have multi-core processors, threaded applications can further improve the performance of a Boost.Asio-based application Scalability.

If the run() method is called on an object of type boost::asio::io_service, the associated handle will also be executed in the same thread. By using multiple threads, 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 is now a multi-threaded application. By using the boost::thread class defined in boost/thread.hpp, which comes from the Boost C++ library Thread, we create 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.

The two timers in this example are both set to trigger after five seconds. Since there are two threads, handler1() and handler2() can be executed simultaneously. If the first is still executing when the second timer is triggered, the second handle will be executed in the second thread. If the handle of the first timer has expired, the I/O service is free to choose any thread.

Threads can improve application performance. Because threads are executed on processor cores, it doesn't make sense to create more threads than cores. This ensures that each thread executes on its own core without competing with other threads on the same core.

It should be noted that using threads is not always worthwhile. 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: the 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. Using threads in this situation does not provide much benefit if the individual handles cannot run independently in parallel.

Calling the run() method of the same I/O service multiple times is the recommended way to increase scalability for Boost.Asio-based applications. There is also a different approach: instead of binding multiple threads to a single I/O service, create multiple I/O services. Then each I/O service uses one thread. If the number of I/O services matches the number of processor cores in 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 of 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 also bound to different I/O services.
 
 The function of this app is the same as the previous one. It is beneficial to use multiple I/O services under certain conditions. Each I/O service has its own thread, preferably running on its own processor core, so that each asynchronous operation and its handle can be used. Localized execution. If there is no remote data or function to access, then each I/O service is like a small autonomous application. Local and remote here refer to resources such as caches and memory pages. Because you need to have a special understanding of the underlying hardware, operating system, compiler, and potential bottlenecks before determining the optimization strategy, you should only use multiple I/O services when you know these benefits.
 7.4. Network programming
 
 Although Boost.Asio is a library that can process any kind of data asynchronously, it is mainly used for network programming. This is due to the fact that Boost.Asio has already supported network functions long before adding other I/O objects. The network function is a good example of asynchronous processing, because data transmission through the network may take a long time, so that confirmation or error conditions cannot be directly obtained.
 
 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 and download the'Highscore' homepage; just like what a browser needs when pointing to www.highscore.de made.

#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: the connect_handler() and read_handler() functions are called after the connection is established and the data is received, respectively. So why do you need the resolve_handler() function?
 
 The Internet uses so-called IP addresses to identify each PC. The IP address is actually just a long string of numbers, hard to remember. It is much easier to remember names like www.highscore.de. 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 resolver, and the corresponding I/O object is: boost::asio::ip::tcp::resolver.
 
 Domain name resolution is also a process that requires connecting to the Internet. Some specialized PCs, called DNS servers, act like a phone book. It knows which IP address is assigned to which PC. Because the process itself is transparent, just understand the concept behind it and why the boost::asio::ip::tcp::resolver I/O object is needed. 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, the resolve_handler() function will be called.
 
 Because receiving data requires a successful connection, which in turn requires a successful domain name resolution, these three different asynchronous operations must 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 sock is also used internally in connect_handler() 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, other corresponding parameters are required, such as an iterator pointing to the resolved address or the buffer used to save the received data.
 
 After starting to execute, the application will create an object query of type boost::asio::ip::tcp::resolver::query, representing a query, which contains the name www.highscore.de and the port 80 commonly used on the Internet. This query is passed to the async_resolve() method to resolve the name. Finally, main() only needs to call the run() method of the I/O service and transfer 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 analysis 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 by the second parameter of type boost::asio::ip::tcp::resolver::iterator.
 
 After calling the async_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, call the async_read_some() method on the corresponding socket to start the read data operation. In order to save the received data, a buffer is provided as the first parameter. In the above example, the buffer type 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_handler( ) Function will be called. The exact number of bytes is given by the bytes_transferred parameter of type std::size_t. By the same rule, the handle should first look at the parameter ec to check for receiving errors. If it is received successfully, the data is written out to the standard output stream.
 
 Please note that read_handler() will call the async_read_some() method again after writing the data to std::cout. This is necessary because there is no guarantee that the entire web page can be received in just one asynchronous operation. The alternate calls of async_read_some() and read_handler() are only terminated when the connection is broken, such as when the web server has delivered a complete web page. In this case, an error will be reported inside read_handler() to prevent further output of data to the standard output stream, and further calls to the async_read() method on the socket. At this point the routine will stop because there are no more asynchronous operations.
 
 The previous example is used to retrieve the web page of www.highscore.de, while the next example demonstrates a simple web server. The main difference is that this application does not connect to other PCs, but waits to connect.

 #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-initialized to the specified protocol and port number-is used to wait for incoming connections from other PCs. The initialization is done through the endpoint object. The type of the object 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. It is the port and protocol commonly used by WWW.
 
 After the receiver is initialized, main() first calls the listen() method to put the receiver in the receiving state, and then uses the async_accept() method to wait for the initial connection. The socket used to send and receive data is passed as the first parameter.
 
 When a PC attempts to establish a connection, accept_handler() is called automatically. If the connection request is successful, the free function boost::asio::async_write() is executed to send the information stored in data through the socket. boost::asio::ip::tcp::socket has a method called async_write_some() that can also send data; but it will call the associated handle after sending at least one byte. The handle needs to calculate how many bytes are left, and call async_write_some() repeatedly until all bytes are sent. Using boost::asio::async_write() can avoid this, because this asynchronous operation only ends after all the bytes in the buffer have been sent.
 
 In this example, when all data is sent, the empty function write_handler() will be called. Since all asynchronous operations have completed, the application terminates. Connections with other PCs are also closed accordingly.
 7.5. Develop Boost.Asio extension
 
 Although Boost.Asio mainly supports network functions, it is very easy to add other I/O objects to perform other asynchronous operations. This section will introduce a general layout of Boost.Asio extension. Although this is not required, it provides a viable framework for other extensions as a starting point.
 
 To add new asynchronous operations to Boost.Asio, you need to implement the following three classes:
 
     a class derived from boost::asio::basic_io_object to represent new I/O objects. Developers using this new Boost.Asio extension will only see this I/O object.
 
     A class derived from boost::asio::io_service::service represents a service, which is registered as an I/O service and can be accessed from an I/O object. The distinction between services and I/O objects is important, because at any given point in time, each I/O service can only have one service instance, and a service can be accessed by multiple I/O objects.
 
     A class that does not derive from any other class represents the concrete realization 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 internal data related to the corresponding I/O object.
 
 The Boost.Asio extension developed in this section does not only provide a framework, but simulates an available boost::asio::deadline_timer object. The difference between it and the original boost::asio::deadline_timer is that the duration of the timer is passed as a parameter to the wait() or async_wait() method instead of being passed 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 that requires a service to be instantiated-usually the service developed specifically for this I/O object. When an I/O object is instantiated, the service will be automatically registered as an I/O service through the parent class boost::asio::basic_io_object unless it has been registered before. This ensures that the services used by any I/O object will only be registered once per I/O service.
 
 Within the I/O object, the corresponding service can be accessed through the service reference. The usual access is to forward the method call to the service. Since the service needs to store data for each I/O object, an instance is automatically created for each I/O object that uses the service. This is achieved with the help of the parent class boost::asio::basic_io_object. The actual service implementation is passed as a parameter to any method call, so that the service can know which I/O object initiated 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 method calls are just forwarded to the corresponding service; The actual service implementation of the I/O object can be used as a 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 integrate with Boost.Asio, a service must meet several requirements:
 
     it must be derived from boost::asio::io_service::service. The constructor must accept a reference to an I/O service, which will be passed to the constructor of boost::asio::io_service::service accordingly.
 
     Any service must contain a static public attribute id of type boost::asio::io_service::id. This attribute is used to identify the service within the I/O service.
 
     Two public methods named construct() and destruct() must be defined, and both require a parameter of type implementation_type. implementation_type is usually the type definition of the specific implementation of the service. As the above example shows, it is easy to use a boost::shared_ptr object in construct() 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 be implemented using construct() and destruct() respectively to create and destroy services for each I/O object.
 
     A method called shutdown_service() must be defined; however, it can be private. For general Boost.Asio extensions, it is usually an empty method. Only services that are very tightly integrated with Boost.Asio will use it. But this method must be present so that the extension can compile successfully.
 
 In order to forward the method call to the corresponding service, the method to be forwarded must be defined for the corresponding I/O object. These methods usually have similar names to the methods in the I/O object, such as wait() and async_wait() in the above example. Synchronous methods, such as wait(), just access the specific implementation of the service to call a blocking method, while asynchronous methods, such as async_wait(), call the blocking method in a thread.
 
 The use of asynchronous operations with the assistance of threads is usually accomplished by accessing a new I/O service. The above example contains an attribute named async_io_service_ whose type is boost::asio::io_service. The run() method of this I/O service is started in its own thread, and its thread is created by async_thread_ of type boost::thread inside the constructor of the service. The type of the third attribute async_work_ is boost::scoped_ptr<boost::asio::io_service::work>, which is used to prevent the run() method from returning immediately. Otherwise, this may happen because no other asynchronous operations are being created. Create an object of type boost::asio::io_service::work and bind it to the I/O service. This action also occurs in the service's constructor, preventing the run() method from returning immediately.
 
 A service can also be implemented without accessing its own I/O service-a single thread is sufficient. The reason for using a new I/O service for the newly added threads is that it is simpler: I/O services can be used between threads to communicate with each other very easily. In this example, async_wait() creates a function object of type wait_operation and passes it to the internal I/O service through the post() method. Then, in the thread used to execute the run() method of this internal I/O service, call the overloaded operator()() of the function object. post() provides a simple way to execute a function object in another thread.
 
 The overloaded operator()() operator of wait_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 will be destroyed during the execution of the operator()() operator in this thread. If the service implementation is destroyed in destruct(), the operator()() operator will no longer be able to access it. This situation is prevented by using a weak pointer. From the first chapter we know: 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, operator()() will not access the service implementation, but call the handle with a boost::asio::error::operation_aborted error.

 #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_; 
 }; 


The service implementation timer_impl uses Windows API functions, which can only be compiled and used in Windows. The purpose of this example is only to illustrate a potential realization.
 
 timer_impl provides two basic methods: wait() is used to wait for a few seconds. destroy() is used to cancel a wait operation, which is necessary because for asynchronous operations, the wait() method is called in its own thread. If the I/O object and its service implementation are destroyed, the blocking wait() method must use destroy() to cancel it.
 
 This Boost.Asio extension 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, the usage of this Boost.Asio extension is similar to boost::asio::deadline_timer. In practice, boost::asio::deadline_timer should be used first, because it is already integrated in Boost.Asio. The sole purpose of this extension is to demonstrate how Boost.Asio extends new asynchronous operations.
 
 Directory Monitor is an extension of Boost.Asio in reality. It 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).

 

Port endpoint

what is端口endpoint

When performing network communication, you need to know three items: IP address, communication protocol, and port number. The communication protocol is used to determine how to communicate, and the IP address and port number are used to determine the target. Boost.AsioA corresponding model is provided in it to indicate it. These three items are the port: ip::basic_endpointcontains the IP address and port number, and uses the communication protocol type as the template parameter. The ones that can be used directly are:

ip::tcp::endpoint
ip::udp::endpoint
ip::icmp::endpoint

What you need to know

Local port construction
can be constructed by specifying the protocol and port number. It is usually used to receive new connections, for example:
tcp::endpoint local_ep(ip::tcp::v4(),1024);

The construction of the remote port The
port can be directly constructed for communication when the IP address and port number of the remote host are known, for example:
tcp::endpoint remote_ep(ip::address::from_string("127.0.0.1"),1024);

How to get the port from the host name and service name You
need to use the DNS service to get the IP address corresponding to the host, and etc. Boost.Asioare provided ip::tcp::resolverto get the port

to sum up

The port is equivalent to a specific address, 地址and communication is performed based on this .

Guess you like

Origin blog.csdn.net/sunlin972913894/article/details/103483317