匹配boost :: deadline_timer回调到相应的wait_async

时间:2014-10-07 11:29:48

标签: c++ boost boost-asio

考虑这个简短的代码片段,其中一个boost :: deadline_timer中断另一个:

#include <iostream>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/asio.hpp>

static boost::asio::io_service io;
boost::asio::deadline_timer timer1(io);
boost::asio::deadline_timer timer2(io);

static void timer1_handler1(const boost::system::error_code& error)
{
    std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:Operation canceled." << std::endl;        
}        

static void timer1_handler2(const boost::system::error_code& error)
{
    std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:success." << std::endl;        
}        

static void timer2_handler1(const boost::system::error_code& error)
{
    std::cout << __PRETTY_FUNCTION__ << " time:" << time(0) << " error:" << error.message() << " expect:success." << std::endl;        
    std::cout << "cancel and restart timer1. Bind to timer1_handler2" << std::endl;
    timer1.cancel();
    timer1.expires_from_now(boost::posix_time::milliseconds(10000));
    timer1.async_wait(boost::bind(timer1_handler2, boost::asio::placeholders::error));        
}        

int main()
{
    std::cout << "Start timer1. Bind to timer1_handler1." << std::endl;
    timer1.expires_from_now(boost::posix_time::milliseconds(2000));
    timer1.async_wait(boost::bind(timer1_handler1, boost::asio::placeholders::error));        

    std::cout << "Start timer2. Bind to timer2_handler1. Will interrupt timer1." << std::endl;
    timer2.expires_from_now(boost::posix_time::milliseconds(2000));
    timer2.async_wait(boost::bind(timer2_handler1, boost::asio::placeholders::error));        

    std::cout << "Run the boost io service." << std::endl;
    io.run();

    return 0;
}

如果timer2的时间在2秒左右变化,有时timer1_handler1会报告成功,有时会取消操作。这可能是在一个简单的例子中确定的,因为我们知道timer2的设置时间。

./timer1
Start timer1. Bind to timer1_handler1.
Start timer2. Bind to timer2_handler1. Will interrupt timer1.
Run the boost io service.
void timer1_handler1(const boost::system::error_code&) time:1412680360 error:Success expect:Operation canceled.
void timer2_handler1(const boost::system::error_code&) time:1412680360 error:Success expect:success.
cancel and restart timer1. Bind to timer1_handler2
void timer1_handler2(const boost::system::error_code&) time:1412680370 error:Success expect:success.

这表示一个更复杂的系统,其中timer1正在实现超时,而timer2实际上是一个异步套接字。偶尔我观察到一个方案,其中timer1被取消太晚了,并且第一个处理程序在第二个async_wait()被调用之后返回,从而产生虚假的超时。

显然,我需要将处理程序回调与相应的async_wait()调用进行匹配。有没有方便的方法呢?

2 个答案:

答案 0 :(得分:4)

通过使用官方Boost timeout示例中使用的方法,解决构造问题的一种方便方法是管理由多个非链式异步操作组成的高级异步操作。在其中,处理程序通过检查当前状态来做出决策,而不是将处理程序逻辑与预期或提供的状态耦合。

在开发解决方案之前,确定处理程序执行的所有可能情况非常重要。运行io_service时,事件循环的单次迭代将执行准备运行的所有操作,并且在操作完成后,用户的完成处理程序将排队{{1} }表示操作的状态。然后error_code将调用排队的完成处理程序。因此,在单次迭代中,所有准备运行的操作在完成处理程序之前以未指定的顺序执行,并且未指定调用完成处理程序的顺序。例如,当从io_serviceasync_read_with_timeout()编写async_read()操作时,其中任一操作仅在其他操作的完成处理程序中被取消,以下情况是可能的:

  • async_wait()已投放且async_read()尚未准备好投放,然后调用async_wait()的完成处理程序并取消async_read(),从而导致async_wait()使用async_wait()错误运行的完成处理程序。
  • boost::asio::error::operation_aborted尚未准备好投放并async_read()投放,然后调用async_wait()的完成处理程序并取消async_wait(),从而导致async_read()使用async_read()错误运行的完成处理程序。
  • boost::asio::error::operation_abortedasync_read()运行,然后首先调用async_wait()的完成处理程序,但async_read()操作已经完成且无法取消,因此async_wait()的完成处理程序将运行且没有错误。
  • async_wait()async_read()运行,然后首先调用async_wait()的完成处理程序,但async_wait()操作已经完成且无法取消,因此async_read()的完成处理程序将运行且没有错误。

完成处理程序async_read()表示操作的状态,并不反映其他完成处理程序产生的状态变化;因此,当error_code成功时,可能需要检查当前状态以执行条件分支。但是,在引入附加状态之前,值得花些精力来检查更高级别操作的目标以及已有的状态。对于此示例,我们定义error_code的目标是在达到截止日期之前未收到数据时关闭套接字。对于状态,插座是打开还是关闭;计时器提供到期时间;系统时钟提供当前时间。在检查目标和可用状态信息之后,可以建议:

  • async_read_with_timeout()的处理程序应该只在计时器的当前过期时间过去时关闭套接字。
  • async_wait()的处理程序应将计时器的到期时间设置为将来。

使用该方法,如果async_read()的完成处理程序在async_read()之前运行,则async_wait()将被取消或async_wait()完成处理程序不会关闭连接,因为当前的到期时间是将来的。另一方面,如果async_wait()的完成处理程序在async_wait()之前运行,则async_read()将被取消或async_read()的完成处理程序可以检测到套接字已关闭。

以下是针对各种用例的完整最小示例demonstrating

async_read()

及其输出:

#include <cassert>
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread.hpp>

class client
{
public:

  // This demo is only using status for asserting code paths.  It is not
  // necessary nor should it be used for conditional branching.
  enum status_type
  {
    unknown,
    timeout,
    read_success,
    read_failure
  };

public:

  client(boost::asio::ip::tcp::socket& socket)
    : strand_(socket.get_io_service()),
      timer_(socket.get_io_service()),
      socket_(socket),
      status_(unknown)
  {}

  status_type status() const { return status_; }

  void async_read_with_timeout(boost::posix_time::seconds seconds)
  {
    strand_.post(boost::bind(
        &client::do_async_read_with_timeout, this, seconds));
  }

private:

  void do_async_read_with_timeout(boost::posix_time::seconds seconds)
  {
    // Start a timeout for the read.
    timer_.expires_from_now(seconds);
    timer_.async_wait(strand_.wrap(boost::bind(
        &client::handle_wait, this,
        boost::asio::placeholders::error)));

    // Start the read operation.
    boost::asio::async_read(socket_,  
        boost::asio::buffer(buffer_),
        strand_.wrap(boost::bind(
          &client::handle_read, this,
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred)));
  }

  void handle_wait(const boost::system::error_code& error)
  {
    // On error, such as cancellation, return early.
    if (error)
    {
      std::cout << "timeout cancelled" << std::endl;
      return;
    }

    // The timer may have expired, but it is possible that handle_read()
    // ran succesfully and updated the timer's expiration:
    // - a new timeout has been started.  For example, handle_read() ran and
    //   invoked do_async_read_with_timeout().
    // - there are no pending timeout reads.  For example, handle_read() ran
    //   but did not invoke do_async_read_with_timeout();
    if (timer_.expires_at() > boost::asio::deadline_timer::traits_type::now())
    {
      std::cout << "timeout occured, but handle_read ran first" << std::endl;
      return;
    }

    // Otherwise, a timeout has occured and handle_read() has not executed, so
    // close the socket, cancelling the read operation.
    std::cout << "timeout occured" << std::endl;
    status_ = client::timeout;
    boost::system::error_code ignored_ec;
    socket_.close(ignored_ec);
  }

  void handle_read(
    const boost::system::error_code& error,
    std::size_t bytes_transferred)
  {
    // Update timeout state to indicate handle_read() has ran.  This
    // cancels any pending timeouts.
    timer_.expires_at(boost::posix_time::pos_infin);

    // On error, return early.
    if (error)
    {
      std::cout << "read failed: " << error.message() << std::endl;
      // Only set status if it is unknown.
      if (client::unknown == status_) status_ = client::read_failure;
      return;
    }

    // The read was succesful, but if a timeout occured and handle_wait()
    // ran first, then the socket is closed, so return early.
    if (!socket_.is_open())
    {
      std::cout << "read was succesful but timeout occured" << std::endl;
      return;
    }

    std::cout << "read was succesful" << std::endl;
    status_ = client::read_success;
  }

private:

  boost::asio::io_service::strand strand_;
  boost::asio::deadline_timer timer_;
  boost::asio::ip::tcp::socket& socket_;
  char buffer_[1];
  status_type status_;
};

// This example is not interested in the connect handlers, so provide a noop
// function that will be passed to bind to meet the handler concept
// requirements.
void noop() {}

/// @brief Create a connection between the server and client socket.
void connect_sockets(
  boost::asio::ip::tcp::acceptor& acceptor,
  boost::asio::ip::tcp::socket& server_socket,
  boost::asio::ip::tcp::socket& client_socket)
{
  boost::asio::io_service& io_service = acceptor.get_io_service();
  acceptor.async_accept(server_socket, boost::bind(&noop));
  client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop));
  io_service.reset();
  io_service.run();
  io_service.reset();
}

int main()
{
  using boost::asio::ip::tcp;
  boost::asio::io_service io_service;
  tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));

  // Scenario 1: timeout
  // The server writes no data, causing a client timeout to occur.
  {
    std::cout << "[Scenario 1: timeout]" << std::endl;
    // Create and connect I/O objects.
    tcp::socket server_socket(io_service);
    tcp::socket client_socket(io_service);
    connect_sockets(acceptor, server_socket, client_socket);

    // Start read with timeout on client.
    client client(client_socket);
    client.async_read_with_timeout(boost::posix_time::seconds(0));

    // Allow do_read_with_timeout to intiate actual operations.
    io_service.run_one();    

    // Run timeout and read operations.
    io_service.run();
    assert(client.status() == client::timeout);
  }

  // Scenario 2: no timeout, succesful read
  // The server writes data and the io_service is ran before the timer 
  // expires.  In this case, the async_read operation will complete and
  // cancel the async_wait.
  {
    std::cout << "[Scenario 2: no timeout, succesful read]" << std::endl;
    // Create and connect I/O objects.
    tcp::socket server_socket(io_service);
    tcp::socket client_socket(io_service);
    connect_sockets(acceptor, server_socket, client_socket);

    // Start read with timeout on client.
    client client(client_socket);
    client.async_read_with_timeout(boost::posix_time::seconds(10));

    // Allow do_read_with_timeout to intiate actual operations.
    io_service.run_one();

    // Write to client.
    boost::asio::write(server_socket, boost::asio::buffer("test"));

    // Run timeout and read operations.
    io_service.run();
    assert(client.status() == client::read_success);
  }

  // Scenario 3: no timeout, failed read
  // The server closes the connection before the timeout, causing the
  // async_read operation to fail and cancel the async_wait operation.
  {
    std::cout << "[Scenario 3: no timeout, failed read]" << std::endl;
    // Create and connect I/O objects.
    tcp::socket server_socket(io_service);
    tcp::socket client_socket(io_service);
    connect_sockets(acceptor, server_socket, client_socket);

    // Start read with timeout on client.
    client client(client_socket);
    client.async_read_with_timeout(boost::posix_time::seconds(10));

    // Allow do_read_with_timeout to intiate actual operations.
    io_service.run_one();

    // Close the socket.
    server_socket.close();

    // Run timeout and read operations.
    io_service.run();
    assert(client.status() == client::read_failure);
  }

  // Scenario 4: timeout and read success
  // The server writes data, but the io_service is not ran until the
  // timer has had time to expire.  In this case, both the await_wait and
  // asnyc_read operations complete, but the order in which the
  // handlers run is indeterminiate.
  {
    std::cout << "[Scenario 4: timeout and read success]" << std::endl;
    // Create and connect I/O objects.
    tcp::socket server_socket(io_service);
    tcp::socket client_socket(io_service);
    connect_sockets(acceptor, server_socket, client_socket);

    // Start read with timeout on client.
    client client(client_socket);
    client.async_read_with_timeout(boost::posix_time::seconds(0));

    // Allow do_read_with_timeout to intiate actual operations.
    io_service.run_one();

    // Allow the timeout to expire, the write to the client, causing both
    // operations to complete with success.
    boost::this_thread::sleep_for(boost::chrono::seconds(1));
    boost::asio::write(server_socket, boost::asio::buffer("test"));

    // Run timeout and read operations.
    io_service.run();
    assert(   (client.status() == client::timeout)
           || (client.status() == client::read_success));
  }
}

答案 1 :(得分:3)

您可以boost::bind完成处理程序的其他参数,这些参数可用于标识源。