我正在写一个同步客户端。部分是Connection
对象,负责实际发送和接收数据。整个库使用Boost ASIO ip::tcp::socket
类编写。
我有一个测试,其中客户端调用服务器上的方法(休眠2秒),超时为1秒。我的代码检测到执行时间超过了请求的时间,但没有及时返回。相反,它在2整秒后返回。
我已将问题缩小到receive
方法:
void Connection::receive(const mutable_buffers_1& buffers, const DurationType& timeout)
{
// to make sure it isn't 0 by mistake
auto actualTimeout = std::max(DurationType(milliseconds(1)), timeout);
SocketReceiveTimeoutOption timeoutOption(actualTimeout);
error_code ec;
_socket.set_option(timeoutOption, ec);
RPC_LOG(TRACE) << "Setting timeout " << actualTimeout << " returned: " << ec.message();
RPC_LOG(TRACE) << "Receiving...";
if (_socket.receive(buffers, MSG_WAITALL, ec) != buffer_size(buffers))
{
throw RpcCommunicationError("Did not receive the expected number of bytes from connection");
}
RPC_LOG(TRACE) << "Received! With error code: " << ec.message();
}
DurationType
只是一个便利的typedef:
typedef boost::chrono::system_clock ClockType;
typedef ClockType::time_point::duration DurationType;
SocketReceiveTimeoutOption
是为套接字实现的选项:
template <int Name>
class SocketTimeoutOption
{
public:
#ifdef BSII_WINDOWS
SocketTimeoutOption(const DurationType& timeout) : _value(static_cast<DWORD>(boost::chrono::duration_cast<boost::chrono::milliseconds>(timeout).count())) {}
#else
SocketTimeoutOption(const DurationType& timeout) : _value(Utils::toTimeval(timeout)) {}
#endif
// Get the level of the socket option.
template <typename Protocol>
int level(const Protocol&) const
{
return SOL_SOCKET;
}
// Get the name of the socket option.
template <typename Protocol>
int name(const Protocol&) const
{
return Name;
}
// Get the address of the timeout data.
template <typename Protocol>
void* data(const Protocol&)
{
return &_value;
}
// Get the address of the timeout data.
template <typename Protocol>
const void* data(const Protocol&) const
{
return &_value;
}
// Get the size of the boolean data.
template <typename Protocol>
std::size_t size(const Protocol&) const
{
return sizeof(_value);
}
private:
#ifdef BSII_WINDOWS
DWORD _value;
#else
timeval _value;
#endif
};
typedef SocketTimeoutOption<SO_RCVTIMEO> SocketReceiveTimeoutOption;
typedef SocketTimeoutOption<SO_SNDTIMEO> SocketSendTimeoutOption;
最后
namespace Utils
{
inline
timeval toTimeval(const DurationType& duration)
{
timeval val;
auto seconds = boost::chrono::duration_cast<boost::chrono::seconds>(duration); // TODO: make sure this is truncated down in case there's fractional seconds
val.tv_sec = static_cast<long>(seconds.count());
auto micro = boost::chrono::duration_cast<boost::chrono::microseconds>(duration - seconds);
val.tv_usec = static_cast<long>(micro.count());
return val;
}
}
问题在于,即使我指定1s超时,receive
方法仍然需要整整2秒。这是日志:
2014-09-14 10:27:53.348383 |追踪| 0x007f24e50ae7c0 |设置超时999917107纳秒返回:成功
2014-09-14 10:27:53.348422 |追踪| 0x007f24e50ae7c0 |接收...
2014-09-14 10:27:55.349152 |追踪| 0x007f24e50ae7c0 |收到!错误代码:成功
正如您所看到的,设置超时工作,但receive
方法仍需要2秒。
相同的代码在Windows上运行正常。
答案 0 :(得分:0)
socket::receive()
将阻止,直到:
对于阻塞同步操作,如果底层操作系统操作返回非严重错误,例如指示操作将阻止或应再次尝试的操作,则Boost.Asio将在poll()
中阻止等待准备就绪的文件描述符。对poll()
的阻止调用不受SO_RCVTIMEO
套接字选项的影响。一旦文件描述符准备就绪,Boost.Asio将重新尝试操作。
因此,原始问题中的场景如下:
Time | Client | Server
-----+----------------------------------------+-------------------------------
| socket.connect(...); | acceptor.accept(...);
0.00 | socket.set_option(timeout(second(1))); | sleep(seconds(2));
0.01 | socket.receive(...); |
0.02 | |-- recv(...); |
1.02 | | // timeout, errno = EAGAIN |
1.03 | |-- poll(socket); |
2.00 | | // data available, poll unblocks | socket.write(...);
2.01 | `-- recv(...);// success |
要获得所需的超时行为,请执行以下操作:
recv()
函数立即在非阻塞套接字上返回,而不管SO_RCVTIMEO
套接字选项。