我有一个使用Boost.Asio进行TCP和UDP套接字通信的应用程序。我明白了' A' in" Asio"代表 Asynchronous ,因此库倾向于鼓励您尽可能使用异步I / O.我有一些情况下,优选同步套接字读取。但是,与此同时,我想在所述接收呼叫上设置超时,因此无法无限期地进行读取阻塞。
这似乎是Boost.Asio用户中一个非常常见的问题,其中包含以下有关该主题的Stack Overflow问题:
甚至可能还有更多。对于如何使用超时实现同步操作,甚至有examples in the documentation。他们归结为将同步操作转换为异步操作,然后与asio::deadline_timer
并行启动。然后,计时器的到期处理程序可以在超时到期时取消异步读取。这看起来像这样(从上面链接的例子中获取的片段):
std::size_t receive(const boost::asio::mutable_buffer& buffer,
boost::posix_time::time_duration timeout, boost::system::error_code& ec)
{
// Set a deadline for the asynchronous operation.
deadline_.expires_from_now(timeout);
// Set up the variables that receive the result of the asynchronous
// operation. The error code is set to would_block to signal that the
// operation is incomplete. Asio guarantees that its asynchronous
// operations will never fail with would_block, so any other value in
// ec indicates completion.
ec = boost::asio::error::would_block;
std::size_t length = 0;
// Start the asynchronous operation itself. The handle_receive function
// used as a callback will update the ec and length variables.
socket_.async_receive(boost::asio::buffer(buffer),
boost::bind(&client::handle_receive, _1, _2, &ec, &length));
// Block until the asynchronous operation has completed.
do io_service_.run_one(); while (ec == boost::asio::error::would_block);
return length;
}
这实际上是一个相对干净的解决方案:启动异步操作,然后手动轮询asio::io_service
以一次执行一个异步处理程序,直到async_receive()
完成或计时器到期。
但是,套接字的底层I / O服务已经在一个或多个后台线程中运行的情况呢?在这种情况下,不能保证异步操作的处理程序将由上面代码片段中的前台线程运行,因此run_one()
将不会返回,直到某些稍后的,可能不相关的处理程序执行。这会使套接字读起来反应迟钝。
asio::io_service
有一个poll_one()
函数可以在不阻塞的情况下检查服务的队列,但是我没有看到阻止前台线程的好方法(模拟同步调用)行为)直到处理程序执行,除了没有后台线程已经执行asio::io_service::run()
的情况。
我看到两种可能的解决方案,我都不喜欢:
使用条件变量或类似构造在启动异步操作后生成前台线程块。在async_receive()
调用的处理程序中,通知条件变量以取消阻塞线程。这会导致每次读取都有一些锁定,我想避免这种情况,因为我希望在UDP套接字读取上实现最大可能的吞吐量。否则,它是可行的,并且可能是我会做的,除非有一种优越的方法。
确保套接字有自己的asio::io_service
,而后台线程没有运行该SO_RCVTIMEO
。这使得在需要的情况下使用套接字的异步I / O变得更加困难。
有关其他方法以安全方式实现此目的的任何想法吗?
旁白:以前的SO问题有一些答案,主张使用/boost/asio/detail/impl/socket_ops.ipp
套接字选项来实现套接字读取超时。这在理论上听起来很棒,但它似乎至少在我的平台上工作(Ubuntu 12.04,Boost v1.55)。我可以设置套接字超时,但它不会给Asio带来预期的效果。相关代码位于size_t sync_recvfrom(socket_type s, state_type state, buf* bufs,
size_t count, int flags, socket_addr_type* addr,
std::size_t* addrlen, boost::system::error_code& ec)
{
if (s == invalid_socket)
{
ec = boost::asio::error::bad_descriptor;
return 0;
}
// Read some data.
for (;;)
{
// Try to complete the operation without blocking.
signed_size_type bytes = socket_ops::recvfrom(
s, bufs, count, flags, addr, addrlen, ec);
// Check if operation succeeded.
if (bytes >= 0)
return bytes;
// Operation failed.
if ((state & user_set_non_blocking)
|| (ec != boost::asio::error::would_block
&& ec != boost::asio::error::try_again))
return 0;
// Wait for socket to become ready.
if (socket_ops::poll_read(s, 0, ec) < 0)
return 0;
}
}
:
recvfrom()
如果套接字读取超时,则上面对EAGAIN
的调用将返回EWOULDBLOCK
或boost::asio::error::try_again
,后者将转换为boost::asio::error::would_block
或poll_read()
。在这种情况下,上面的代码将调用int poll_read(socket_type s, state_type state, boost::system::error_code& ec)
{
if (s == invalid_socket)
{
ec = boost::asio::error::bad_descriptor;
return socket_error_retval;
}
pollfd fds;
fds.fd = s;
fds.events = POLLIN;
fds.revents = 0;
int timeout = (state & user_set_non_blocking) ? 0 : -1;
clear_last_error();
int result = error_wrapper(::poll(&fds, 1, timeout), ec);
if (result == 0)
ec = (state & user_set_non_blocking)
? boost::asio::error::would_block : boost::system::error_code();
else if (result > 0)
ec = boost::system::error_code();
return result;
}
函数,对于我的平台看起来像:
poll()
我剪掉了为其他平台有条件编译的代码。正如您所看到的,如果套接字不是非阻塞套接字,它最终会以无限超时调用SO_RCVTIMEO
,因此阻塞直到套接字有数据被读取(并导致超时尝试)。因此,{{1}}选项无效。
答案 0 :(得分:7)
Boost.Asio对futures
的支持可能会提供一个优雅的解决方案。当提供异步操作boost::asio::use_future
值作为其完成处理程序时,启动函数将返回将接收操作结果的std::future
对象。此外,如果操作以失败告终,error_code
将转换为system_error
并通过future
传递给来电者。
在Boost.Asio C ++ 11 Futures datytime client example中,专用线程运行io_service
,主线程启动异步操作,然后在完成操作时同步等待,如下所示:
std::array<char, 128> recv_buf;
udp::endpoint sender_endpoint;
std::future<std::size_t> recv_length =
socket.async_receive_from(
boost::asio::buffer(recv_buf),
sender_endpoint,
boost::asio::use_future);
// Do other things here while the receive completes.
std::cout.write(
recv_buf.data(),
recv_length.get()); // Blocks until receive is complete.
使用future
时,使用超时实现同步读取的整体方法与以前相同。不使用同步读取,而是使用异步读取并异步等待计时器。唯一的小改动是,而不是在io_service
上阻塞或者不定期检查谓词,而是调用future::get()
来阻塞,直到操作成功完成或失败(例如超时)
如果C ++ 11不可用,则可以为Boost.Thread future
自定义异步操作的返回类型,如this回答中所示。