You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

多线程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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.30 03:25:13