Trying to understand Boost.Asio custom service implementation

后端 未结 1 1505
醉话见心
醉话见心 2021-01-30 02:08

I\'m thinking about writing a custom Asio service on top of an existing proprietary 3rd party networking protocol that we are currently using.

According to Highscore A

1条回答
  •  有刺的猬
    2021-01-30 02:21

    In short, consistency. The services attempt to meet user expectations set forth by the services Boost.Asio provides.

    Using an internal io_service provides a clear separation of ownership and control of handlers. If a custom service posts its internal handlers into the user's io_service, then execution of the service's internal handlers becomes implicitly coupled with the user's handlers. Consider how this would impact user expectations with the Boost.Asio Logger Service example:

    • The logger_service writes to the file stream within a handler. Thus, a program that never processes the io_service event loop, such as one that only uses the synchronous API, would never have log messages written.
    • The logger_service would no longer be thread-safe, potentially invoking undefined behavior if the io_service is processed by multiple threads.
    • The lifetime of the logger_service's internal operations is constrained by that of the io_service. For example, when a service's shutdown_service() function is invoked, the lifetime of the owning io_service has already ended. Hence, messages could not be logged via logger_service::log() within shutdown_service(), as it would attempt to post an internal handler into the io_service whose lifetime has already ended.
    • The user may no longer assume a one-to-one mapping between an operation and handler. For example:

      boost::asio::io_service io_service;
      debug_stream_socket socket(io_service);
      boost::asio::async_connect(socket, ..., &connect_handler);
      io_service.poll();
      // Can no longer assume connect_handler has been invoked.
      

      In this case, io_service.poll() may invoke the handler internal to the logger_service, rather than connect_handler().

    Furthermore, these internal threads attempt to mimic the behavior used internally by Boost.Asio itself:

    The implementation of this library for a particular platform may make use of one or more internal threads to emulate asynchronicity. As far as possible, these threads must be invisible to the library user.


    Directory Monitor example

    In the directory monitor example, an internal thread is used to prevent indefinitely blocking the user's io_service while waiting for an event. Once an event has occurred, the completion handler is ready to be invoked, so the internal thread post the user handler into the user's io_service for deferred invocation. This implementation emulates asynchronicity with an internal thread that is mostly invisible to the user.

    For details, when an asynchronous monitor operation is initiated via dir_monitor::async_monitor(), a basic_dir_monitor_service::monitor_operation is posted into the internal io_service. When invoked, this operation invokes dir_monitor_impl::popfront_event(), a potentially blocking call. Hence, if the monitor_operation is posted into the user's io_service, the user's thread could be indefinitely blocked. Consider the affect on the following code:

    boost::asio::io_service io_service;
    boost::asio::dir_monitor dir_monitor(io_service); 
    dir_monitor.add_directory(dir_name); 
    // Post monitor_operation into io_service.
    dir_monitor.async_monitor(...);
    io_service.post(&user_handler);
    io_service.run();
    

    In the above code, if io_service.run() invokes monitor_operation first, then user_handler() will not be invoked until dir_monitor observes an event on the dir_name directory. Therefore, dir_monitor service's implementation would not behave in a consistent manner that most users expect from other services.

    Asio Logger Service

    The use of an internal thread and io_service:

    • Mitigates the overhead of logging on the user's thread(s) by performing potentially blocking or expensive calls within the internal thread.
    • Guarantees the thread-safety of std::ofstream, as only the single internal thread writes to the stream. If logging was done directly within logger_service::log() or if logger_service posted its handlers into the user's io_service, then explicit synchronization would be required for thread-safety. Other synchronization mechanisms may introduce greater overhead or complexity into the implementation.
    • Allows for services to log messages within shutdown_service(). During destruction, the io_service will:

      1. Shutdown each of its services.
      2. Destroy all uninvoked handlers that were scheduled for deferred invocation in the io_service or any of its associated strands.
      3. Destroy each of its services.


      As the lifetime of the user's io_service has ended, its event queue is neither being processed nor can additional handlers be posted. By having its own internal io_service that is processed by its own thread, logger_service enables other services to log messages during their shutdown_service().


    Additional Considerations

    When implementing a custom service, here are a few points to consider:

    • Block all signals on internal threads.
    • Never invoke the user's code directly.
    • How to track and post user handlers when an implementation is destroyed.
    • Resource(s) owned by the service that are shared between the service's implementations.

    For the last two points, the dir_monitor I/O object exhibits behavior that users may not expect. As the single thread within the service invokes a blocking operation on a single implementation's event queue, it effectively blocks operations that could potentially complete immediately for their respective implementation:

    boost::asio::io_service io_service;
    boost::asio::dir_monitor dir_monitor1(io_service); 
    dir_monitor1.add_directory(dir_name1); 
    dir_monitor1.async_monitor(&handler_A);
    
    boost::asio::dir_monitor dir_monitor2(io_service); 
    dir_monitor2.add_directory(dir_name2); 
    dir_monitor2.async_monitor(&handler_B);
    // ... Add file to dir_name2.
    
    {
      // Use scope to enforce lifetime.
      boost::asio::dir_monitor dir_monitor3(io_service); 
      dir_monitor3.add_directory(dir_name3); 
      dir_monitor3.async_monitor(&handler_C);
    }
    io_service.run();
    

    Although the operations associated with handler_B() (success) and handler_C() (aborted) would not block, the single thread in basic_dir_monitor_service is blocked waiting for a change to dir_name1.

    0 讨论(0)
提交回复
热议问题