繁体   English   中英

如何安全地取消Boost ASIO异步接受操作?

[英]How to safely cancel a Boost ASIO asynchronous accept operation?

我在Boost ASIO文档和StackOverflow中读到的所有内容都表明我可以通过在acceptor套接字上调用close来停止async_accept操作。 但是,当我尝试执行此操作时,我在async_accept处理程序中出现间歇性not_socket错误。 我做错了什么或者Boost ASIO不支持这个吗?

(相关问题: 这里这里 。)

(注意:我在Windows 7上运行并使用Visual Studio 2015编译器。)

我面临的核心问题是接受传入连接的async_accept操作和我close调用之间的竞争条件。 即使使用明确或隐含的链,也会发生这种情况。

请注意, 调用close 之前,我对async_accept 调用严格发生 我得出结论,竞争条件是在我的呼叫close和Boost ASIO中接受传入连接的底层代码之间。

我已经包含了证明问题的代码。 程序重复创建一个接受器,连接它,并立即关闭接受器。 它希望async_accept操作成功完成或取消。 任何其他错误都会导致程序中止,这就是我间歇性地看到的。

对于同步,程序使用显式链。 然而, close调用与async_accept操作的效果不同步,因此有时接受器在接受传入连接之前关闭,有时它会在之后关闭,有时也不会 - 因此问题。

这是代码:

#include <algorithm>
#include <boost/asio.hpp>
#include <cstdlib>
#include <future>
#include <iostream>
#include <memory>
#include <thread>

int main()
{
  boost::asio::io_service ios;
  auto work = std::make_unique<boost::asio::io_service::work>(ios);

  const auto ios_runner = [&ios]()
  {
    boost::system::error_code ec;
    ios.run(ec);
    if (ec)
    {
      std::cerr << "io_service runner failed: " << ec.message() << '\n';
      abort();
    }
  };

  auto thread = std::thread{ios_runner};

  const auto make_acceptor = [&ios]()
  {
    boost::asio::ip::tcp::resolver resolver{ios};
    boost::asio::ip::tcp::resolver::query query{
      "localhost",
      "",
      boost::asio::ip::resolver_query_base::passive |
      boost::asio::ip::resolver_query_base::address_configured};
    const auto itr = std::find_if(
      resolver.resolve(query),
      boost::asio::ip::tcp::resolver::iterator{},
      [](const boost::asio::ip::tcp::endpoint& ep) { return true; });
    assert(itr != boost::asio::ip::tcp::resolver::iterator{});
    return boost::asio::ip::tcp::acceptor{ios, *itr};
  };

  for (auto i = 0; i < 1000; ++i)
  {
    auto acceptor = make_acceptor();
    const auto saddr = acceptor.local_endpoint();

    boost::asio::io_service::strand strand{ios};
    boost::asio::ip::tcp::socket server_conn{ios};

    // Start accepting.
    std::promise<void> accept_promise;
    strand.post(
      [&]()
    {
      acceptor.async_accept(
        server_conn,
        strand.wrap(
          [&](const boost::system::error_code& ec)
          {
            accept_promise.set_value();
            if (ec.category() == boost::asio::error::get_system_category()
              && ec.value() == boost::asio::error::operation_aborted)
              return;
            if (ec)
            {
              std::cerr << "async_accept failed (" << i << "): " << ec.message() << '\n';
              abort();
            }
          }));
    });

    // Connect to the acceptor.
    std::promise<void> connect_promise;
    strand.post(
      [&]()
    {
      boost::asio::ip::tcp::socket client_conn{ios};
      {
        boost::system::error_code ec;
        client_conn.connect(saddr, ec);
        if (ec)
        {
          std::cerr << "connect failed: " << ec.message() << '\n';
          abort();
        }
        connect_promise.set_value();
      }
    });
    connect_promise.get_future().get();   // wait for connect to finish

    // Close the acceptor.
    std::promise<void> stop_promise;
    strand.post([&acceptor, &stop_promise]()
    {
      acceptor.close();
      stop_promise.set_value();
    });
    stop_promise.get_future().get();   // wait for close to finish
    accept_promise.get_future().get(); // wait for async_accept to finish
  }

  work.reset();
  thread.join();
}

这是样本运行的输出:

async_accept failed (5): An operation was attempted on something that is not a socket

括号中的数字表示程序运行的成功迭代次数。

更新#1:根据Tanner Sansbury的回答,我添加了一个std::promise来表示async_accept处理程序的完成。 这对我所看到的行为没有影响。

更新#2: not_socket错误从呼叫发起到setsockopt ,从call_setsockopt ,从socket_ops::setsockopt在文件boost\\asio\\detail\\impl\\socket_ops.ipp (升压版本1.59)。 这是完整的电话:

socket_ops::setsockopt(new_socket, state,
  SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT,
  &update_ctx_param, sizeof(SOCKET), ec);

关于setsockopt Microsoft 文档SO_UPDATE_ACCEPT_CONTEXT

使用侦听套接字的上下文更新接受套接字。

我不确定这究竟是什么意思,但听起来如果关闭侦听套接字会失败。 这表明,在Windows上,无法安全地close当前正在为async_accept操作运行完成处理程序的async_accept

我希望有人可以告诉我,我错了,有一种方法可以安全地关闭一个忙碌的接受者。

示例程序不会取消async_accept操作。 建立连接后,将在内部发布async_accept操作以完成。 此时,该操作不再可取消,并且不受acceptor.close()影响。

观察到的问题是未定义行为的结果。 该程序无法满足async_acceptpeer参数的生命周期要求:

将接受新连接的套接字。 对象的所有权由调用者保留,调用者必须保证它在调用处理程序之前有效。

特别是,对等套接字server_connfor循环中具有自动范围。 async_accept操作未完成时,循环可以开始新的迭代,导致server_conn被破坏并违反生命周期要求。 考虑通过以下任一方式扩展server_conn的生命周期:

  • 在accept处理程序中设置std::future并等待相关的std::promise然后继续循环的下一次迭代
  • 通过智能指针管理server_conn并将所有权传递给accept处理程序

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM