我正在编写一个能够处理多个并发连接的简单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
的所有权转移到回调函数,理想情况下无需在堆上创建套接字?
答案 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。
以下是一个完整的示例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)));
}