【问题标题】:Transfer ownership of boost::asio::socket stack variable转移 boost::asio::socket 堆栈变量的所有权
【发布时间】:2016-06-26 11:55:10
【问题描述】:

我正在编写一个能够处理多个并发连接的简单 tcp 套接字服务器。这个想法是主监听线程将阻塞接受和卸载套接字句柄到工作线程(在线程池中),以从那里异步处理通信。

void server::run() {
  {
    io_service::work work(io_service);

    for (std::size_t i = 0; i < pool_size; i++)
      thread_pool.push_back(std::thread([&] { io_service.run(); }));

    boost::asio::io_service listener;
    boost::asio::ip::tcp::acceptor acceptor(listener, ip::tcp::endpoint(ip::tcp::v4(), port));

    while (listening) {
      boost::asio::ip::tcp::socket socket(listener);
      acceptor.accept(socket);
      io_service.post([&] {callback(std::move(socket));});
    }
  }

  for (ThreadPool::iterator it = thread_pool.begin(); it != thread_pool.end(); it++)
    it->join();
}

我在堆栈上创建socket,因为我不想在while(listening) 循环内重复分配内存。

回调函数callback的原型如下:

void callback(boost::asio::socket socket);

据我了解,调用callback(std::move(socket)) 会将socket 的所有权转移给callback。但是,当我尝试从callback 内部调用socket.receive() 时,我收到Bad file descriptor 错误,所以我认为这里有问题。

如何将socket 的所有权转移给回调函数,最好不必在堆上创建套接字?

【问题讨论】:

  • 错误是循环迭代结束后可能会调用您的 lambda。这意味着 socket 将超出范围,并且在对它的引用上调用 std::move() 是未定义的。
  • 我不知道在不为套接字分配空间的情况下安全地执行此操作的方法。原因是io_service::post()copies the handler as required。您可能不安全地通过将处理程序设为类并定义复制构造函数来移动套接字来做到这一点,但请注意,这违反了复制的语义。为什么您如此不愿意在堆上分配时间或空间?在任何一种情况下,鉴于您正在创建和接受套接字,分配似乎可以忽略不计。

标签: c++ multithreading sockets boost boost-asio


【解决方案1】:

可能会调用未定义的行为,因为 lambda 可能会通过悬空引用在先前销毁的套接字上调用 std::move()。例如,考虑包含套接字的循环结束其当前迭代的情况,导致socket 在调用 lambda 之前被销毁:

 Main Thread                       | Thread Pool
-----------------------------------+----------------------------------
tcp::socket socket(...);           |
acceptor.accept(socket);           |
io_service.post([&socket] {...});  |
~socket(); // end iteration        |
... // next iteration              | callback(std::move(socket));

要解决这个问题,需要将socket 所有权转移给处理程序,而不是在处理程序内转移所有权。根据文档,Handlers 必须是 CopyConstructible,因此它们的参数,包括不可复制的 socket,也必须是。然而,如果 Asio 可以消除对处理程序的复制构造函数的所有调用并且定义了BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS,则可以放宽这一要求。

#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
#include <boost/asio.hpp>

void callback(boost::asio::ip::tcp::socket socket);

...

// Transfer ownership of socket to the handler.
io_service.post(
  [socket=std::move(socket)]() mutable
  {
    // Transfer ownership of socket to `callback`.
    callback(std::move(socket));
  });

有关 Asio 类型检查的更多详细信息,请参阅this 答案。


这是一个完整的例子 demonstrating 一个 socket 的所有权被转移到一个处理程序:

#include <functional> // std::bind
#include <utility>    // std::move
#include <vector>     // std::vector
#define BOOST_ASIO_DISABLE_HANDLER_TYPE_REQUIREMENTS
#include <boost/asio.hpp>

const auto noop = std::bind([]{});

void callback(boost::asio::ip::tcp::socket socket)
{
  const std::string actual_message = "hello";
  boost::asio::write(socket, boost::asio::buffer(actual_message));
}

int main()
{
  using boost::asio::ip::tcp;

  // Create all I/O objects.
  boost::asio::io_service io_service;
  tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
  tcp::socket client_socket(io_service);

  // Connect the sockets.
  client_socket.async_connect(acceptor.local_endpoint(), noop);
  {
    tcp::socket socket(io_service);
    acceptor.accept(socket);
    // Transfer ownership of socket to the handler.
    assert(socket.is_open()); 
    io_service.post(
      [socket=std::move(socket)]() mutable
      {
        // Transfer ownership of socket to `callback`.
        callback(std::move(socket));
      });
    assert(!socket.is_open()); 
  } // ~socket

  io_service.run();

  // At this point, sockets have been conencted, and `callback`
  // should have written data to `client_socket`.
  std::vector<char> buffer(client_socket.available());
  boost::asio::read(client_socket, boost::asio::buffer(buffer));

  // Verify the correct message was read.
  const std::string expected_message = "hello";
  assert(std::equal(
    begin(buffer), end(buffer),
    begin(expected_message), end(expected_message)));
}

【讨论】:

  • 酷,我不知道你可以在 lambda 捕获中放置一个初始化程序。
  • @rhashimoto 那是 c++14
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-11
  • 1970-01-01
  • 2018-06-08
  • 2011-11-13
  • 1970-01-01
相关资源
最近更新 更多