我有一个异步使用boost::asio
的客户端和服务器。我想添加一些超时来关闭连接,如果出现问题可能会重试。
我最初的想法是,每当我调用async_
函数时,我都应该在我希望异步操作完成后启动deadline_timer
到期。现在我想知道在每种情况下是否完全必要。
例如:
async_resolve
可能会使用系统的解析器,其中包含超时(例如RES_TIMEOUT
中的resolv.h
可能会被/etc/resolv.conf
中的配置覆盖。通过添加我自己的计时器,我可能会与用户希望他的解析器工作方式发生冲突。
对于async_connect
,connect(2)
系统调用内置了某种超时
等
那么哪些(如果有的话)async_
调用可以保证在“合理”的时间范围内调用它们的处理程序?如果一个操作[可以|确实]超时,那么处理程序会传递basic_errors::timed_out
错误或其他错误吗?
答案 0 :(得分:32)
所以我做了一些测试。根据我的结果,很明显它们依赖于底层的OS实现。作为参考,我使用Fedora内核进行了测试:2.6.35.10-74.fc14.x86_64
。
最重要的是,async_resolve()
看起来是可能能够在不设置deadline_timer
的情况下离开的唯一情况。在其他所有情况下,实际上都需要合理的行为。
<强> async_resolve()
强>
对async_resolve()
的调用导致4次查询相隔5秒。处理程序在请求后20秒被调用,错误为boost::asio::error::host_not_found
。
我的解析器默认为5秒的超时,尝试2次(resolv.h
),因此它似乎发送了两倍的配置查询次数。通过在options timeout
中设置options attempts
和/etc/resolv.conf
,可以修改此行为。在每种情况下,无论attempts
设置为什么,发送的查询数都是双倍的,之后调用处理程序时出现host_not_found
错误。
对于测试,单个配置的名称服务器是黑洞路由。
<强> async_connect()
强>
使用黑洞路由目标调用async_connect()
会导致在~189秒后调用处理程序并显示错误boost::asio::error::timed_out
。
堆栈发送了初始SYN和5次重试。第一次重试在3秒后发送,重试超时每次加倍(3 + 6 + 12 + 24 + 48 + 96 = 189)。重试次数可以更改:
% sysctl net.ipv4.tcp_syn_retries
net.ipv4.tcp_syn_retries = 5
选择默认值5以符合RFC 1122(4.2.3.5):
SYN的[重传定时器] 段必须设置得足够大 提供该段的转播 至少3分钟。该 应用程序可以关闭连接 (即放弃公开尝试) 当然更快。
3分钟= 180秒,虽然RFC似乎没有指定上限。没有什么可以阻止实现永远重试。
<强> async_write()
强>
只要套接字的发送缓冲区未满,就会立即调用此处理程序。
我的测试建立了一个TCP连接并设置了一个计时器,以便在一分钟后调用async_write()
。在建立连接但在async_write()
呼叫之前的那一刻,我尝试了各种各样的混乱:
/etc/init.d/network stop
无论我做了什么,下一个async_write()
都会立即调用其处理程序报告成功。
在防火墙欺骗RST的情况下,连接立即关闭,但我无法知道,直到我尝试下一步操作(这会立即报告boost::asio::error::connection_reset
)。在其他情况下,连接将保持打开状态,并且不会向我报告错误,直到它最终在17-18分钟后超时。
async_write()
的最坏情况是主机正在重新传输并且发送缓冲区已满。如果缓冲区已满,async_write()
将不会调用其处理程序,直到重新传输超时。 Linux默认为15次重传:
% sysctl net.ipv4.tcp_retries2
net.ipv4.tcp_retries2 = 15
重传之间的时间在每次之后增加(并且基于许多因素,例如特定连接的估计往返时间),但是被钳制在2分钟。因此,对于默认的15次重传和最坏情况的2分钟超时,上限为30分钟,以便调用async_write()
处理程序。调用它时,错误设置为boost::asio::error::timed_out
。
<强> async_read()
强>
只要建立连接且没有收到数据,就不应该调用它的处理程序。我没有时间去测试它。
答案 1 :(得分:10)
这两个调用可能会有超时权限,可以让你的处理程序得到支持,但是你可能会在这些超时之前花费很长时间。 (我知道我已经让一个连接只是坐下来尝试连接一个连接呼叫超过10分钟与boost::asio
之前杀死进程)。此外,async_read
和async_write
来电也没有与之相关的超时,因此,如果您希望对读写进行超时,则仍需要deadline_timer
。