多线程TCP服务器客户端断开即退出问题求助
多线程TCP服务器客户端断开后程序退出问题排查
问题描述
实现了一个最多支持4个客户端连接的多线程TCP服务器类,使用nc连接服务器后按Ctrl+C断开客户端,整个服务器程序直接退出,无法继续等待新客户端连接。核心实现代码如下:
server_loop函数
void server_loop() { std::cout << "Server started on port " << port_ << "\n"; while (is_running_) { int client_socket = accept(server_socket_, nullptr, nullptr); if (client_socket < 0) { std::cerr << "ERRNO: " << errno << std::endl; if(errno != 53) { std::cerr << "Error: cannot accept client connection\n"; } continue; } // Acquire a lock on the client_sockets_mutex_ before accessing the client_sockets_ vector { std::lock_guard<std::mutex> lock(client_sockets_mutex_); if (client_sockets_.size() >= MAX_CLIENTS) { std::cerr << "Error: maximum number of clients reached\n"; close(client_socket); continue; } client_sockets_.push_back(client_socket); std::cout << "Client connected (" << client_sockets_.size() << "/" << MAX_CLIENTS << ")\n"; } // Start a new thread to handle the client communication std::thread(&TcpServer::handle_client, this, client_socket).detach(); } }
handle_client函数
void handle_client(int client_socket) { // loop until the client disconnects while (true) { // generate a new ID and send it to the client unsigned int id = get_unique_id(); std::string id_str = std::to_string(id) + "\n"; int res = send(client_socket, id_str.c_str(), id_str.size(), 0); if (res < 0) { std::cerr << "Error sending data to client: " << strerror(errno) << std::endl; break; } // wait for 1 second before sending the next ID std::this_thread::sleep_for(std::chrono::seconds(1)); } // remove the client socket from the list of connected clients { std::lock_guard<std::mutex> lock(client_sockets_mutex_); auto it = std::find(client_sockets_.begin(), client_sockets_.end(), client_socket); if (it != client_sockets_.end()) { client_sockets_.erase(it); std::cout << "Client disconnected (" << client_sockets_.size() << "/" << MAX_CLIENTS << ")\n"; } } // close the client socket close(client_socket); }
问题原因
当客户端按Ctrl+C断开时,客户端进程退出并关闭socket连接,此时服务器端handle_client线程中的send调用尝试向已关闭的socket写入数据,会触发SIGPIPE信号。该信号的默认处理行为是直接终止整个进程,导致服务器程序退出。
解决方案
有两种可靠的处理方式:
1. 全局忽略SIGPIPE信号
在服务器启动阶段(比如server_loop函数开始前)添加信号处理代码,将SIGPIPE设为忽略:
#include <signal.h> // 在server_loop调用前执行 signal(SIGPIPE, SIG_IGN);
此时send触发SIGPIPE时,进程不会被终止,而是send返回-1,errno被设置为EPIPE,代码中已有的错误判断逻辑会正常执行,处理客户端断开流程。
2. 给send调用添加MSG_NOSIGNAL标志
每次调用send时加上该标志,避免触发SIGPIPE信号:
int res = send(client_socket, id_str.c_str(), id_str.size(), MSG_NOSIGNAL);
这种方式下,向已关闭socket写数据时不会发送SIGPIPE,send同样返回-1且errno为EPIPE,后续断开处理逻辑正常运行。
两种方式均能解决问题,推荐第一种全局忽略的方式,无需修改每个send调用,实现更简洁。
内容的提问来源于stack exchange,提问作者QQQ
相关产品推荐
相关产品推荐

