在std :: thread中增强asio阻止操作,而不是使用异步方法?

时间:2019-03-01 23:51:48

标签: c++ networking boost boost-asio

所以我一直在努力在Boost.Asio上创建一些抽象层。我想自动处理某些批次的操作,例如tcp::resolver::resolve()asio::connect()。如果我同时使用这两个版本的异步版本,则代码将变得非常讨厌,因为我必须“链接”回调。本质上:

  1. 用户调用我的Connect()包装器方法,该方法接受一个主机和一个服务字符串,并且还提供一个在完成连接后调用的回调。
  2. 使用主机和服务字符串参数调用resolver::async_resolve()。将用户的回调绑定到回调以进行解析(以传递在连接后被调用的回调)
  3. 从解析回调(如果成功)中,调用asio::async_connect()。再次,将用户的回调绑定到connect回调。
  4. 在连接回调中,如果成功,则调用用户的回调

由于大量嵌套的lambda,这是令人讨厌的,还是因为函数是分开的,所以这令人讨厌,现在我有一个装有样板的类。在我看来,仅要做这样的事情(还没有编译,因此暂时将其视为伪代码)会容易得多:

   using ConnectCallback = std::function<void(std::shared_ptr<tcp::socket>)>;

   void Connect(std::string const& host, std::string const& service, ConnectCallback cb)
   {
      std::thread{[this, host, service, cb{std::move(cb)}]
      {
         std::shared_ptr<tcp::socket> socket;

         try
         {
            tcp::resolver r{m_context};
            auto endpoints = r.resolve(host, service);

            socket = std::make_shared<tcp::socket>(m_context);
            asio::connect(*socket, endpoints);
         }
         catch (std::exception const&)
         {
            // either resolve or connect failed / timed out
         }

         cb(std::move(socket));
      }}.detach();
   }

对我来说,这要简​​单得多,至少对于启动连接而言,因为我不必担心这么多的回调。唯一的缺点是我不确定如何使用此方法处理超时情况。我在Google上发现的所有与超时相关的解决方案都需要使用async_方法。

是否建议以这种方式执行操作,还是必须坚持使用异步方法?如果是后者,我可以使用什么技术来简化回调链接样板?

1 个答案:

答案 0 :(得分:1)

如果编写处理程序对您来说很烦,您可以考虑使用coroutines。它与异步操作一起使用,使您可以实现超时。

struct Client2 {
    Client2(asio::io_context& io)
    : io(io) {}

    asio::io_context& io;
    asio::ip::tcp::resolver resolver{io};
    asio::ip::tcp::socket sock{io};
    asio::high_resolution_timer timer{io};
    atomic_bool stopped{false};

    void connect (const string& host, const string& service, int timeoutMs)
    {
        boost::asio::spawn(io,std::bind(&Client2::establishConnection,this,host,service,timeoutMs,std::placeholders::_1));
        boost::asio::spawn(io,std::bind(&Client2::doTimeout,this,std::placeholders::_1));
    }
    void establishConnection (string host, string service, int timeoutMs,boost::asio::yield_context yield)
    {
        try {
            timer.expires_after(std::chrono::milliseconds(timeoutMs)); // set timeout
            auto res = resolver.async_resolve(host,service,yield); 
            // resume here when handler for resolving was called
            if (stopped)
                return;
            asio::async_connect(sock,res,yield);
            timer.cancel(); // connection is established, do sth with sock here, cancel timer
        }
        catch (std::exception& ex) {
        }
    }
    void doTimeout (boost::asio::yield_context yield)
    {
        try {
            timer.async_wait(yield); // throw exception when was canceled by startConnecting
        }
        catch (std::exception& ex) {
            return;
        }
        resolver.cancel(); // timeout == timer expired, so cancel resolving and close socket
        sock.close();
        stopped = true;
    }
};

// in main
        asio::io_context io;
        Client2 client{io};
        client.connect("localhost","5444",200);
        thread th([&](){ io.run(); }); // call run from at least 2 threads
        io.run();  // establishConnection and doTimeout can be executed concurrently
        th.join();

我在代码中添加了一些注释。 简而言之:使用了两个协程。在establishConnection中,执行两个异步操作:async_resolveasync_connect。在doTimeout中启动协程timer。当计时器在建立连接之前到期时,我们取消解析并关闭套接字。如果在计时器到期之前建立了连接,则我们取消计时器,然后可以使用sock执行某些操作。

establishConnectiondoTimeout的主体可以作为asio::spawn函数的参数移入lambda。因此,我们只能有一个成员函数,而没有执行3个异步操作的代码处理程序。如果您满意,请开始使用协程。