How to run boost asio resolver service on more thr

2019-02-19 06:41发布

问题:

I am using boost::asio::ip::udp::resolver in an SNMPV2 implementation to determine wheather a host is reachable or not.

using Resolver = boost::asio::ip::udp::resolver;
Resolver resolver(ioService);
Resolver::query query(connectOptions.getHost(),
        connectOptions.getPort());
Resolver::iterator endpointIterator;
BOOST_LOG_SEV(logger, Severity::debug) << "Waiting for async resolve";
endpointIterator = resolver.async_resolve(query, yield);
BOOST_LOG_SEV(logger, Severity::debug) << "Async resolve done";
if (endpointIterator == Resolver::iterator{}) { // unreachable host
    using namespace boost::system;
    throw system_error{error_code{SnmpWrapperError::BadHostname}};
}

I have one test case where I test what is happening when a non existent hostname and an exitent hostname is queried paralell:

2013-09-16 10:45:28.687001: [DEBUG   ] 0x88baf8 SnmpConnection: connect                                                                                              
2013-09-16 10:45:28.687396: [DEBUG   ] 0x88baf8 SnmpConnection: host: non_existent_host_name_                                                                        
2013-09-16 10:45:28.687434: [DEBUG   ] 0x88baf8 SnmpConnection: port: 1611                                                                                           
2013-09-16 10:45:28.687456: [DEBUG   ] 0x88baf8 SnmpConnection: Waiting for async resolve                                                                            
2013-09-16 10:45:28.687675: [DEBUG   ] 0x88c608 SnmpConnection: connect                                                                                              
2013-09-16 10:45:28.687853: [DEBUG   ] 0x88c608 SnmpConnection: host: 127.0.0.1                                                                                      
2013-09-16 10:45:28.687883: [DEBUG   ] 0x88c608 SnmpConnection: port: 1611                                                                                           
2013-09-16 10:45:28.687904: [DEBUG   ] 0x88c608 SnmpConnection: Waiting for async resolve                                                                            
2013-09-16 10:45:31.113527: [ERROR   ] 0x88baf8 SnmpConnection: Host not found (authoritative)                                                                       
2013-09-16 10:45:31.113708: [DEBUG   ] 0x88c608 SnmpConnection: Async resolve done                                                                                   
2013-09-16 10:45:31.113738: [DEBUG   ] 0x88c608 SnmpConnection: Connecting to 127.0.0.1:1611
...

From the logs it can be seen that the object with the reachable address is blocked until the other one's resolve is finished with the error (3 seconds). My assumption is that Asio resolver service uses one thread, so one query towards one unreachable host might block the processing of upcoming resolve requests.

Workaround would be to run the resolver service on more threads, is that possible? Or would it be possible to have a resolver service which works on sockets like the udp service does (instead of using ::getaddrinfo)?

回答1:

What you need are two io_service ioService, because each one is run by a thread. By that I mean that you block the normal execution of a thread by calling io_service::run.

I think that the code itself is correct.



回答2:

As noted in the documentation, Boost.Asio will create an an additional thread per io_service to emulate asynchronous host resolution on the first call to resolver::async_resolve().

Creating multiple io_service objects will allow for concurrent host resolution only when initiating asynchronous resolution operations on resolvers associated with different io_services. For example, the following code will not perform concurrent host resolution, as both resolvers use the same service:

boost::asio::io_service service1;
boost::asio::ip::udp::resolver resolver1(service1); // using service1
boost::asio::ip::udp::resolver resolver2(service1); // using service1
resolver1.async_resolve(...); 
resolver2.async_resolve(...);

On the other hand, the following will perform concurrent host resolution, as each resolver is using a different service:

boost::asio::io_service service1;
boost::asio::io_service service2;
boost::asio::ip::udp::resolver resolver1(service1); // using service1
boost::asio::ip::udp::resolver resolver2(service2); // using service2
resolver1.async_resolve(...); 
resolver2.async_resolve(...);

Assuming a resolver per io_service, to obtain concurrency, it becomes the applications responsibility to dispatch resolution operations to different resolvers. A simple work distribution strategy, such as round-robin, may suffice.

On the other hand, one can delegate this responsibility to an io_service, allowing it to distribute work that will emulate asynchronous host resolution in a similar manner to what Boost.Asio does internally. The synchronous resolver::resolve() member function performs work in the calling thread. Thus, an application could create an io_service that is serviced by a thread pool. When asynchronous host resolution needs to occur, a job is posted into the io_service that will create create a resolver and perform synchronous resolution, invoking the user handler with the results. Below is a complete basic example where a resolver class emulates asynchronous host resolution with a thread pool:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/optional.hpp>
#include <boost/thread.hpp>

/// @brief Type used to emulate asynchronous host resolution with a 
///        dedicated thread pool.
class resolver
{
public:
  resolver(const std::size_t pool_size)
    : work_(boost::ref(io_service_))
  {
    // Create pool.
    for (std::size_t i = 0; i < pool_size; ++i)
      threads_.create_thread(
        boost::bind(&boost::asio::io_service::run, &io_service_));
  }

  ~resolver()
  {
    work_ = boost::none;
    threads_.join_all();
  }

  template <typename QueryOrEndpoint, typename Handler>
  void async_resolve(QueryOrEndpoint query, Handler handler)
  {
    io_service_.post(boost::bind(
        &resolver::do_async_resolve<QueryOrEndpoint, Handler>, this,
        query, handler));
  }

private:
  /// @brief Resolve address and invoke continuation handler.
  template <typename QueryOrEndpoint, typename Handler>
  void do_async_resolve(const QueryOrEndpoint& query, Handler handler)
  {
    typedef typename QueryOrEndpoint::protocol_type protocol_type;
    typedef typename protocol_type::resolver        resolver_type;

    // Resolve synchronously, as synchronous resolution will perform work
    // in the calling thread.  Thus, it will not use Boost.Asio's internal
    // thread that is used for asynchronous resolution.
    boost::system::error_code error;
    resolver_type resolver(io_service_);
    typename resolver_type::iterator result = resolver.resolve(query, error);

    // Invoke user handler.
    handler(error, result);
  }

private:
  boost::asio::io_service io_service_;
  boost::optional<boost::asio::io_service::work> work_;
  boost::thread_group threads_;
};

template <typename ProtocolType>
void handle_resolve(
    const boost::system::error_code& error,
    typename ProtocolType::resolver::iterator iterator)
{
  std::stringstream stream;
  stream << "handle_resolve:\n"
            "  " << error.message() << "\n";
  if (!error)
    stream << "  " << iterator->endpoint() << "\n";

  std::cout << stream.str();
  std::cout.flush();
}

int main()
{
  // Resolver will emulate asynchronous host resolution with a pool of 5
  // threads.
  resolver resolver(5);

  namespace ip = boost::asio::ip;
  resolver.async_resolve( 
      ip::udp::resolver::query("localhost", "12345"),
      &handle_resolve<ip::udp>);
  resolver.async_resolve(
      ip::tcp::resolver::query("www.google.com", "80"),
      &handle_resolve<ip::tcp>);
  resolver.async_resolve(
      ip::udp::resolver::query("www.stackoverflow.com", "80"),
      &handle_resolve<ip::udp>);
  resolver.async_resolve(
      ip::icmp::resolver::query("some.other.address", "54321"),
      &handle_resolve<ip::icmp>);
}

And annotated output:

handle_resolve:
  Success
  127.0.0.1:12345   // localhost
handle_resolve:
  Service not found // bogus
handle_resolve:
  Success
  173.194.77.147:80 // google
handle_resolve:
  Success
  198.252.206.16:80 // stackoverflow