我使用boost asio来处理http请求和应答,为了避免async_resolve不调用它的回调处理程序,我设置了一个超时,就像这样:
void resolve()
{
resolver_.async_resolve(query,strand_.wrap(boost::bind(&connection::handle_resolve,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::iterator)));
int cancel_num = timer_.expires_from_now(boost::posix_time::seconds(resolve_timeout_));
timer_.async_wait(strand_.wrap(boost::bind(&connection::handle_resolve_timeout,
shared_from_this(),
boost::asio::placeholders::error)));
}
void connection::handle_resolve_timeout(const boost::system::error_code& err)
{
if (err != boost::asio::error::operation_aborted)
{
resolver_.cancel();
}
}
void connection::handle_resolve(const boost::system::error_code& err,
boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
timer_.cancel();
if(!err)
{
boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
socket_.async_connect(endpoint,strand_.wrap(boost::bind(&connection::handle_connect,
shared_from_this(),
boost::asio::placeholders::error,
++endpoint_iterator)));
//to distinct the type of timeout ,0:connect timeout,1:read timeout
int flag = 0;
int cancel_num = timer_.expires_from_now(boost::posix_time::seconds(connect_timeout_));
timer_.async_wait(strand_.wrap(boost::bind(&connection::handle_timeout,
shared_from_this(),
boost::asio::placeholders::error,
flag)));
}
else if(err != boost::asio::error::operation_aborted)
{
status_ = resolve_error;
check_.do_finish(shared_from_this());
}
else
{
FASTCHECK_INFO("resolve is canceled\n");
}
}
当解决超时时,我发现handle_resolve_timeout被调用,但是handle_resolve没有返回boost::asio::error::operation_aborted,为什么,我很困惑,有人可以帮我解释一下吗?
根据 boost-users 邮件列表的讨论,
resolver::cancel()
只能取消待处理的、排队的解析请求,而不能取消当前正在执行的请求。
作为对 @Cubbi 答案的延迟跟进,在该问题发布几个月后,在 Boost 问题跟踪器 上也提出了此错误。 Asio 解析器 API 有点令人困惑,因为它表明任何
async_resolve()
操作都可以根据需要取消。我自己也有同样的问题。事实证明,在 Asio 实现中,对 async_resolve()
的调用会在幕后对 getaddrinfo()
进行同步系统调用。我在最近的代码广播中讨论了这个有趣的行为。
附注并进一步解释一下:
boost::asio::ip::tcp::resolver::async_resolve()
创建一个后台线程来执行实际的名称解析,这些名称解析是使用系统 C 库的 getaddrinfo()
调用一一执行的。即使您在应用程序中创建多个 resolver
对象,所有异步解析请求都会被序列化并转发到该单个线程。
该实现的优点是,对
async_resolve()
的调用不会阻塞任何其他 async()
方法,尽管 getaddrinfo()
会阻塞。但缺点是,所有对 async_resolve()
的调用都会互相阻塞!因此,如果应用程序需要解析 100 个主机名,并且 DNS 出现故障并且查找平均需要 5 秒,则应用程序启动时间将为 500 秒。此外,如果当前无法访问特定的 DNS 服务器,则该查找将挂起,直到 getaddrinfo()
超时。不,resolver::cancel()
对当前查找没有影响,因为 getaddrinfo()
不可取消。
有几种方法可以改善这种情况:
io_context
,在其中执行同步地址解析。然后,至少可以有与该解析器线程池中的线程一样多的并行
getaddrinfo()
调用。
detach()
该线程并忘记它。下一个名称解析将创建一个新线程。这将允许应用程序立即
cancel()
当前活动的查找(例如,当截止日期计时器触发时)并立即转到下一个查找(将在新线程中启动)。尽管如此,分离的“守护进程”线程仍会尝试完成其
getaddrinfo()
调用,如果这样做,结果通常会缓存在系统中的某个位置。当应用程序在几秒钟后重试该特定传出连接时,缓存的结果将用于立即应答解析器请求,然后连接可以成功。
glibc
的系统,
boost::asio::ip::tcp::resolver
可以升级为使用 GNU 扩展
getaddrinfo_a()
,它真正实现了名称解析的并行化。不幸的是,
getaddrinfo_a()
也存在与
select()
系统调用类似的弱点,因为调用
gai_suspend()
(读取后台
getaddrinfo_a()
请求的一个或多个结果)的开销随着未完成的名称解析的数量呈二次方增加。更不幸的是,目前尚不清楚
getaddrinfo_a()
是如何(有效)实际实施的。实现它的 RedHat 人员的白皮书表明,它使用围绕
getaddrinfo()
的线程池,至少对于更复杂的情况是这样。所以实际上,使用
getaddrinfo_a()
可能与上面的解决方案 1 一样好或坏。