boost asio async_connect success after close

不打扰是莪最后的温柔 提交于 2019-12-03 10:15:54

As Igor mentions in the comments, the completion handler is already queued.

This scenario is the result of a separation in time between when an operation executes and when a handler is invoked. The documentation for io_service::run(), io_service::run_one(), io_service::poll(), and io_service::poll_one() is specific to mention handlers, and not operations. In the scenario, the socket::async_connect() operation and deadline_timer::async_wait() operation complete in the same event loop iteration. This results in both handlers being added to the io_service for deferred invocation, in an unspecified order.

Consider the following snippet that accentuates the scenario:

void handle_wait(const boost::system::error_code& error)
{
  if (error) return;
  socket_.close();
}

timer_.expires_from_now(boost::posix_time::seconds(30));
timer_.async_wait(&handle_wait);
socket_.async_connect(endpoint_, handle_connect);
boost::this_thread::sleep(boost::posix_time::seconds(60));
io_service_.run_one();

When io_service_.run_one() is invoked, both socket::async_connect() and deadline_timer::async_wait() operations may have completed, causing handle_wait and handle_connect to be ready for invocation from within the io_service in an unspecified order. To properly handle this unspecified order, additional logic need to occur from within handle_wait() and handle_connect() to query the current state, and determine if the other handler has been invoked, rather than depending solely on the status (error_code) of the operation.

The easiest way to determine if the other handler has invoked is:

  • In handle_connect(), check if the socket is still open via is_open(). If the socket is still open, then handle_timer() has not been invoked. A clean way to indicate to handle_timer() that handle_connect() has ran is to update the expiry time.
  • In handle_timer(), check if the expiry time has passed. If this is true, then handle_connect() has not ran, so close the socket.

The resulting handlers could look like the following:

void handle_wait(const boost::system::error_code& error)
{
  // On error, return early.
  if (error) return;

  // If the timer expires in the future, then connect handler must have
  // first.
  if (timer_.expires_at() > deadline_timer::traits_type::now()) return;

  // Timeout has occurred, so close the socket.
  socket_.close();
}

void handle_connect(const boost::system::error_code& error)
{
  // The async_connect() function automatically opens the socket at the start
  // of the asynchronous operation. If the socket is closed at this time then
  // the timeout handler must have run first.
  if (!socket_.is_open()) return;

  // On error, return early.
  if (error) return;

  // Otherwise, a connection has been established.  Update the timer state
  // so that the timeout handler does not close the socket.
  timer_.expires_at(boost::posix_time::pos_infin);
}

Boost.Asio provides some examples for handling timeouts.

I accept twsansbury's answer, just want to add some more info.

About shutdown():

void async_recv_handler( boost::system::error_code ec_recv, std::size_t count )
{
    if ( !m_socket.is_open() )
        return; // first time don't trust to ec_recv
    if ( ec_recv )
    {
        // oops, we have error
        // log
        // close
        return;
    }
    // seems that we are just fine, no error in ec_recv, we can gracefully shutdown the connection
    // but shutdown may fail! this check is working for me
    boost::system::error_code ec_shutdown;
    // second time don't trusting to ec_recv
    m_socket.shutdown( t, ec_shutdown );
    if ( !ec_shutdown )
        return;
    // this error code is expected
    if ( ec_shutdown == boost::asio::error::not_connected )
       return;
    // other error codes are unexpected for me
    // log << ec_shutdown.message()
    throw boost::system::system_error(ec_shutdown);
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!