转移boost::asio::socket堆栈变量的所有权

Transfer ownership of boost::asio::socket stack variable

本文关键字:变量 堆栈 所有权 socket boost asio 转移      更新时间:2023-10-16

我正在编写一个能够处理多个并发连接的简单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可能会通过悬挂引用在先前销毁的套接字上调用std::move()。例如,考虑包含套接字的循环在调用lambda之前结束其当前迭代,导致socket被破坏的情况:

 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类型检查的更多详细信息,请参阅此答案。


下面是一个完整的例子,演示了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)));
}