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

时间:2016-06-26 11:55:10

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

我正在编写一个能够处理多个并发连接的简单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。但是,当我尝试从socket.receive()内部调用callback时,我收到Bad file descriptor错误,因此我认为这里有问题。

如何将socket的所有权转移到回调函数,理想情况下无需在堆上创建套接字?

1 个答案:

答案 0 :(得分:2)

可能正在调用未定义的行为,因为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类型检查的更多详细信息,请参阅this answer。

以下是一个完整的示例demonstratingsocket的所有权转移到处理程序:

#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)));
}