Boost::Asio TCP服务器新客户端连接时旧客户端被断开的问题排查
我来帮你分析这个问题,你的代码里有两个关键错误导致了新客户端连接时旧客户端被断开的现象,咱们一步步来看:
问题1:static的Acceptor导致监听混乱
先看你的PhysicalServer::listen函数:
static tcp::acceptor acceptor { context, {tcp::v4(), port} };
这里把acceptor声明成了static,这意味着不管你为哪个端口调用listen,这个acceptor只会被初始化一次。哪怕你是为同一个端口多次启动监听线程,后续的线程都会复用第一次创建的acceptor实例。这种情况会导致监听逻辑混乱,甚至出现连接时的异常行为——比如新连接的请求可能被错误的线程处理,或者旧的socket连接被意外中断。
修复方案:
去掉static修饰,让每个listen线程拥有自己的acceptor实例。同时要确保同一个端口只启动一个监听线程,避免重复绑定端口:
void dnw::PhysicalServer::startListening(const Port port) { auto& is_listening = std::get<bool>(ports[port]); if (is_listening) { // 已经在监听该端口,直接返回,避免重复启动线程 return; } is_listening = true; std::thread thread {listen, this, port}; thread.detach(); } void dnw::PhysicalServer::listen(const Port port) { using namespace boost::asio::ip; // 去掉static,每个监听线程创建独立的acceptor tcp::acceptor acceptor {context, {tcp::v4(), port}}; while(std::get<bool>(ports[port])) { Socket socket {context}; acceptor.accept(socket); // 后续处理逻辑... } }
问题2:Vector扩容导致Socket引用失效
你在处理新连接时,把socket移动到vector中,然后用std::ref(sockets.back())传给线程:
sockets.push_back(std::move(socket)); std::thread thread {std::get<Processor>(ports[port]), std::ref(sockets.back()), std::ref(*this)};
这里有个隐患:当vector的容量不足时,push_back会触发扩容,vector会重新分配内存并把旧元素移动到新地址。这时候之前线程中持有的std::ref(sockets.back())(对应旧客户端的socket)就会指向无效的内存区域,后续对这个socket的write操作就会抛出错误,表现为旧客户端“被断开”。
修复方案:
改用std::shared_ptr来存储socket,这样即使vector扩容,指针的地址也不会改变,线程中的引用始终有效:
- 先修改
PhysicalServer中ports的类型,把vector的元素改成std::shared_ptr<Socket>:
// 假设原来的ports定义是这样,现在修改vector的类型 std::unordered_map<Port, std::tuple<bool, std::vector<std::shared_ptr<Socket>>, Processor>> ports;
- 然后修改
listen函数中的socket存储逻辑:
void dnw::PhysicalServer::listen(const Port port) { using namespace boost::asio::ip; tcp::acceptor acceptor {context, {tcp::v4(), port}}; while(std::get<bool>(ports[port])) { Socket socket {context}; acceptor.accept(socket); auto& sockets = std::get<std::vector<std::shared_ptr<Socket>>>(ports[port]); // 把socket包装成shared_ptr,避免扩容失效 auto socket_ptr = std::make_shared<Socket>(std::move(socket)); sockets.push_back(socket_ptr); // 线程中传递shared_ptr的引用,或者直接传递shared_ptr std::thread thread {std::get<Processor>(ports[port]), std::ref(*socket_ptr), std::ref(*this)}; thread.detach(); } }
额外注意点
另外,你的客户端代码中用了boost::asio::read(socket, boost::asio::buffer(data)),这个函数会阻塞直到整个buffer被填满。如果服务器发送的数据不足256字节,客户端会一直卡在read调用上。如果你的业务不需要固定长度的数据包,可以改用boost::asio::read_some,或者在协议中明确约定数据包的长度,避免不必要的阻塞。
按照上面的方案修改后,应该就能解决新客户端连接导致旧客户端断开的问题了。
备注:内容来源于stack exchange,提问作者darkonaito_

