在 Boost.Asio 中使用 SO_RCVTIMEO 和 SO_SNDTIMEO 套接字选项很少会产生所需的行为。考虑使用以下两种模式之一:
与async_wait()的组合操作
可以通过使用 Boost.Asio 计时器和带有 async_receive() 操作的 async_wait() 操作来组合带有超时的异步读取操作。这种方法在 Boost.Asio timeout examples 中得到了演示,类似于:
// Start a timeout for the read.
boost::asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(1));
timer.async_wait(
[&socket, &timer](const boost::system::error_code& error)
{
// On error, such as cancellation, return early.
if (error) return;
// Timer has expired, but the read operation's completion handler
// may have already ran, setting expiration to be in the future.
if (timer.expires_at() > boost::asio::deadline_timer::traits_type::now())
{
return;
}
// The read operation's completion handler has not ran.
boost::system::error_code ignored_ec;
socket.close(ignored_ec);
});
// Start the read operation.
socket.async_receive(buffer,
[&socket, &timer](const boost::system::error_code& error,
std::size_t bytes_transferred)
{
// Update timeout state to indicate the handler has ran. This
// will cancel any pending timeouts.
timer.expires_at(boost::posix_time::pos_infin);
// On error, such as cancellation, return early.
if (error) return;
// At this point, the read was successful and buffer is populated.
// However, if the timeout occurred and its completion handler ran first,
// then the socket is closed (!socket.is_open()).
});
请注意,两个异步操作可以在同一个迭代中完成,从而使两个完成处理程序都准备好成功运行。因此,两个完成处理程序都需要更新和检查状态的原因。有关如何管理状态的更多详细信息,请参阅this 答案。
使用std::future
Boost.Asio 提供support for C++11 futures。当 boost::asio::use_future 被提供为异步操作的完成处理程序时,启动函数将返回一个 std::future,一旦操作完成,它将被执行。由于std::future 支持定时等待,因此可以利用它来使操作超时。请注意,由于调用线程将被阻塞以等待将来,因此必须至少有一个其他线程正在处理 io_service 以允许 async_receive() 操作进行并履行承诺:
// Use an asynchronous operation so that it can be cancelled on timeout.
std::future<std::size_t> read_result = socket.async_receive(
buffer, boost::asio::use_future);
// If timeout occurs, then cancel the read operation.
if (read_result.wait_for(std::chrono::seconds(1)) ==
std::future_status::timeout)
{
socket.cancel();
}
// Otherwise, the operation completed (with success or error).
else
{
// If the operation failed, then read_result.get() will throw a
// boost::system::system_error.
auto bytes_transferred = read_result.get();
// process buffer
}
为什么SO_RCVTIMEO 不起作用
系统行为
SO_RCVTIMEO 文档指出,该选项仅影响执行套接字 I/O 的系统调用,例如 read() 和 recvmsg()。它不影响事件解复用器,例如select() 和poll(),它们只监视文件描述符以确定何时可以在不阻塞的情况下发生 I/O。此外,当确实发生超时时,I/O 调用将无法返回 -1,并将 errno 设置为 EAGAIN 或 EWOULDBLOCK。
指定接收或发送超时直到报告错误。 [...] 如果没有数据传输并且已经达到超时,则返回 -1 并将 errno 设置为 EAGAIN 或 EWOULDBLOCK [...] 超时仅对执行套接字 I 的系统调用有效/O(例如,read()、recvmsg()、[...];超时对select()、poll()、epoll_wait() 等无效。
当底层文件描述符设置为非阻塞时,如果资源不能立即可用,执行套接字 I/O 的系统调用将立即返回 EAGAIN 或 EWOULDBLOCK。对于非阻塞套接字,SO_RCVTIMEO 不会有任何影响,因为调用将立即返回成功或失败。因此,SO_RCVTIMEO 要影响系统 I/O 调用,套接字必须是阻塞的。
Boost.Asio 行为
首先,Boost.Asio 中的异步 I/O 操作将使用事件解复用器,例如 select() 或 poll()。因此,SO_RCVTIMEO 不会影响异步操作。
接下来,Boost.Asio的sockets有两种非阻塞模式的概念(两者都默认为false):
-
native_non_blocking() 模式,大致对应文件描述符的非阻塞状态。此模式会影响系统 I/O 调用。例如,如果调用socket.native_non_blocking(true),则recv(socket.native_handle(), ...) 可能会因errno 设置为EAGAIN 或EWOULDBLOCK 而失败。每当在套接字上启动异步操作时,Boost.Asio 都会启用此模式。
-
non_blocking() 影响 Boost.Asio 的同步套接字操作的模式。当设置为true 时,Boost.Asio 会将底层文件描述符设置为非阻塞,并且同步 Boost.Asio 套接字操作可能会失败并出现boost::asio::error::would_block(或等效的系统错误)。当设置为false 时,Boost.Asio 将通过轮询文件描述符并在返回EAGAIN 或EWOULDBLOCK 时重新尝试系统I/O 操作来阻塞,即使底层文件描述符是非阻塞的。
non_blocking() 的行为阻止 SO_RCVTIMEO 产生所需的行为。假设socket.receive() 被调用并且数据既不可用也不接收:
- 如果
non_blocking() 为假,系统I/O 调用将按SO_RCVTIMEO 超时。但是,Boost.Asio 将立即阻止对文件描述符的轮询以使其可读,这不受SO_RCVTIMEO 的影响。最终结果是调用者在socket.receive() 中被阻塞,直到接收到数据或失败,例如远程对等方关闭连接。
- 如果
non_blocking() 为真,则底层文件描述符也是非阻塞的。因此,系统 I/O 调用将忽略 SO_RCVTIMEO,立即返回 EAGAIN 或 EWOULDBLOCK,导致 socket.receive() 失败并返回 boost::asio::error::would_block。
理想情况下,要让SO_RCVTIMEO 与 Boost.Asio 一起工作,需要将 native_non_blocking() 设置为 false 以便 SO_RCVTIMEO 可以生效,但还需要将 non_blocking() 设置为 true 以防止对描述符进行轮询。但是,Boost.Asio 没有support this:
socket::native_non_blocking(bool mode)
如果模式为false,但non_blocking() 的当前值为true,则此函数将失败并显示boost::asio::error::invalid_argument,因为组合没有意义。