2

我在 SNMPV2 实现中使用 boost::asio::ip::udp::resolver 来确定主机是否可访问。

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

我有一个测试用例,我在其中测试并行查询不存在的主机名和现有主机名时发生的情况:

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
...

从日志可以看出,具有可达地址的对象被阻塞,直到另一个人的解析完成并出现错误(3秒)。我的假设是 Asio 解析器服务使用一个线程,因此对一台无法访问的主机的一次查询可能会阻止对即将到来的解析请求的处理。

解决方法是在更多线程上运行解析器服务,这可能吗?或者是否有可能拥有像 udp 服务一样在套接字上工作的解析器服务(而不是使用 ::getaddrinfo)?

4

2 回答 2

8

文档中所述,Boost.Asio 将在第一次调用时创建一个额外的线程io_service来模拟异步主机解析resolver::async_resolve()

只有在与不同 s 关联的s 上启动异步解析操作时,创建多个io_service对象才允许并发主机解析。例如,以下代码不会执行并发主机解析,因为两个解析器使用相同的服务: resolverio_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(...);

另一方面,以下将执行并发主机解析,因为每个解析器使用不同的服务:

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(...);

假设resolver每个io_service,为了获得并发,将解析操作分派给不同的解析器成为应用程序的责任。一个简单的工作分配策略,例如循环,可能就足够了。

另一方面,可以将此责任委托给一个io_service,允许它以与 Boost.Asio 内部所做的类似的方式分发将模拟异步主机解析的工作。同步resolver::resolve()成员函数在调用线程中执行工作。因此,应用程序可以创建一个io_service由线程池提供服务的线程池。当需要进行异步主机解析时,将发布一个作业,该作业io_service将创建一个resolver并执行同步解析,并使用结果调用用户处理程序。下面是一个完整的基本示例,其中一个resolver类使用线程池模拟异步主机解析:

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

和带注释的输出:

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
于 2013-09-16T15:57:20.740 回答
1

您需要的是两个io_service ioService,因为每个都由一个线程运行。我的意思是你通过调用来阻止线程的正常执行io_service::run

我认为代码本身是正确的。

于 2013-09-16T09:43:30.070 回答