SO_RCVTIME and SO_RCVTIMEO not affecting Boost.Asio operations

匿名 (未验证) 提交于 2019-12-03 03:03:02

问题:

Below is my code

boost::asio::io_service io; boost::asio::ip::tcp::acceptor::reuse_address option(true); boost::asio::ip::tcp::acceptor accept(io); boost::asio::ip::tcp::resolver resolver(io); boost::asio::ip::tcp::resolver::query query("0.0.0.0", "8080"); boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query); accept.open(endpoint.protocol()); accept.set_option(option); accept.bind(endpoint); accept.listen(30);  boost::asio::ip::tcp::socket ps(io);  accept.accept(ps);  struct timeval tv; tv.tv_sec = 1; tv.tv_usec = 0; //setsockopt(ps.native(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); setsockopt(ps.native(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); char buf[1024]; ps.async_receive(boost::asio::buffer(buf, 1024), boost::bind(fun)); io.run(); 

When I use Telnet to connect, but not sending data, it does not disconnect from a Telnet timeout.Will need to do to make setsockopt kick in? Thanks!

I have modified SO_RCVTIMEO to SO_SNDTIMEO. Still unable to timeout in the specified time

回答1:

Using SO_RCVTIMEO and SO_SNDTIMEO socket options with Boost.Asio will rarely produce the desired behavior. Consider using either of the following two patterns:

Composed Operation With async_wait()

One can compose an asynchronous read operation with timeout by using a Boost.Asio timer and an async_wait() operation with a async_receive() operation. This approach is demonstrated in the Boost.Asio timeout examples, something similar to:

// 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()).   }); 

Be aware that it is possible for both asynchronous operations to complete in the same iteration, making both completion handlers ready to run with success. Hence, the reason why both completion handlers need to update and check state. See this answer for more details on how to manage state.

Use std::future

Boost.Asio's provides support for C++11 futures. When boost::asio::use_future is provided as the completion handler to an asynchronous operation, the initiating function will return a std::future that will be fulfilled once the operation completes. As std::future supports timed waits, one can leverage it for timing out an operation. Do note that as the calling thread will be blocked waiting for the future, at least one other thread must be processing the io_service to allow the async_receive() operation to progress and fulfill the promise:

// Use an asynchronous operation so that it can be cancelled on timeout. std::future<: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 } 

Why SO_RCVTIMEO Will Not Work

System Behavior

The SO_RCVTIMEO documentation notes that the option only affects system calls that perform socket I/O, such as read() and recvmsg(). It does not affect event demultiplexers, such as select() and poll(), that only watch the file descriptors to determine when I/O can occur without blocking. Furthermore, when a timeout does occur, the I/O call fails returning -1 and sets errno to EAGAIN or EWOULDBLOCK.

Specify the receiving or sending timeouts until reporting an error. [...] if no data has been transferred and the timeout has been reached then -1 is returned with errno set to EAGAIN or EWOULDBLOCK [...] Timeouts only have effect for system calls that perform socket I/O (e.g., read(), recvmsg(), [...]; timeouts have no effect for select(), poll(), epoll_wait(), and so on.

When the underlying file descriptor is set to non-blocking, system calls performing socket I/O will return immediately with EAGAIN or EWOULDBLOCK if resources are not immediately available. For a non-blocking socket, SO_RCVTIMEO will not have any affect, as the call will return immediately with success or failure. Thus, for SO_RCVTIMEO to affect system I/O calls, the socket must be blocking.

Boost.Asio Behavior

First, asynchronous I/O operations in Boost.Asio will use an event demultiplexer, such as select() or poll(). Hence, SO_RCVTIMEO will not affect asynchronous operations.

Next, Boost.Asio's sockets have the concept of two non-blocking modes (both of which default to false):

  • native_non_blocking() mode that roughly corresponds to the file descriptor's non-blocking state. This mode affects system I/O calls. For example, if one invokes socket.native_non_blocking(true), then recv(socket.native_handle(), ...) may fail with errno set to EAGAIN or EWOULDBLOCK. Anytime an asynchronous operation is initiated on a socket, Boost.Asio will enable this mode.
  • non_blocking() mode that affects Boost.Asio's synchronous socket operations. When set to true, Boost.Asio will set the underlying file descriptor to be non-blocking and synchronous Boost.Asio socket operations can fail with boost::asio::error::would_block (or the equivalent system error). When set to false, Boost.Asio will block, even if the underlying file descriptor is non-blocking, by polling the file descriptor and re-attempting system I/O operations if EAGAIN or EWOULDBLOCK are returned.

The behavior of non_blocking() prevents SO_RCVTIMEO from producing desired behavior. Assuming socket.receive() is invoked and data is neither available nor received:

  • If non_blocking() is false, the system I/O call will timeout per SO_RCVTIMEO. However, Boost.Asio will then immediately block polling on the file descriptor to be readable, which is not affected by SO_RCVTIMEO. The final result is the caller blocked in socket.receive() until either data has been received or failure, such as the remote peer closing the connection.
  • If non_blocking() is true, then the underlying file descriptor is also non-blocking. Hence, the system I/O call will ignore SO_RCVTIMEO, immediately return with EAGAIN or EWOULDBLOCK, causing socket.receive() to fail with boost::asio::error::would_block.

Ideally, for SO_RCVTIMEO to function with Boost.Asio, one needs native_non_blocking() set to false so that SO_RCVTIMEO can take affect, but also have non_blocking() set to true to prevent polling on the descriptor. However, Boost.Asio does not support this:

socket::native_non_blocking(bool mode)

If the mode is false, but the current value of non_blocking() is true, this function fails with boost::asio::error::invalid_argument, as the combination does not make sense.



回答2:

Since you are receiving data, you may want to set: SO_RCVTIMEO instead of SO_SNDTIMEO

Although mixing boost and system calls may not produce the expected results.

For reference:

SO_RCVTIMEO

Sets the timeout value that specifies the maximum amount of time an input function waits until it completes. It accepts a timeval structure with the number of seconds and microseconds specifying the limit on how long to wait for an input operation to complete. If a receive operation has blocked for this much time without receiving additional data, it shall return with a partial count or errno set to [EAGAIN] or [EWOULDBLOCK] if no data is received. The default for this option is zero, which indicates that a receive operation shall not time out. This option takes a timeval structure. Note that not all implementations allow this option to be set.

This option however only has effect on read operations, not on other low level function that may wait on the socket in an asynchronous implementation (e.g. select and epoll) and it seems that it does not affect asynchronous asio operations as well.

I found an example code from boost that may work for your case here.

An over simplified example (to be compiled in c++11):

#include  #include  #include   void myclose(boost::asio::ip::tcp::socket& ps) { ps.close(); }  int main() {   boost::asio::io_service io;   boost::asio::ip::tcp::acceptor::reuse_address option(true);   boost::asio::ip::tcp::acceptor accept(io);   boost::asio::ip::tcp::resolver resolver(io);   boost::asio::ip::tcp::resolver::query query("0.0.0.0", "8080");   boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);   accept.open(endpoint.protocol());   accept.set_option(option);   accept.bind(endpoint);   accept.listen(30);   boost::asio::ip::tcp::socket ps(io);   accept.accept(ps);   char buf[1024];   boost::asio::deadline_timer timer(io, boost::posix_time::seconds(1));   timer.async_wait(boost::bind(myclose, boost::ref(ps)));    ps.async_receive(boost::asio::buffer(buf, 1024),            [](const boost::system::error_code& error,               std::size_t bytes_transferred )            {              std::cout 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!