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

基于fork()的TCP多客户端服务器异常问题求助

TCP多客户端连接异常分析与修复

核心异常原因

你的问题大概率是以下几种情况之一,或多者并存:

1. 父进程accept()被子进程退出信号打断

子进程处理完客户端连接退出时,会向父进程发送SIGCHLD信号。默认情况下,这个信号会直接打断父进程中阻塞的accept()调用,导致accept()返回EINTR错误。如果你的错误处理逻辑直接退出而非重启accept(),就会出现"Unable to accept!"并回到命令提示符。

2. 服务器主循环逻辑错误

如果父进程在fork()后没有回到accept()循环,反而执行了exit()或return,会直接导致父进程终止。此时第一个客户端由子进程处理,但父进程已退出,监听套接字被释放,后续客户端连接时会触发错误。

3. 套接字未正确关闭

  • 子进程未关闭监听套接字:子进程会继承父进程的监听套接字文件描述符,不关闭会导致端口引用计数异常,父进程退出后无法正常释放端口。
  • 父进程未关闭客户端套接字:父进程在fork()后必须立即关闭客户端连接的套接字,否则会造成文件描述符泄漏,最终耗尽系统资源。

代码排查与修复要点

修复信号中断问题

针对SIGCHLD打断accept()的情况,有两种处理方式:

  • 方式一:忽略SIGCHLD信号
    在服务器启动时添加:

    signal(SIGCHLD, SIG_IGN);
    

    这种方式会让系统自动回收子进程资源,避免信号打断accept()。

  • 方式二:处理SIGCHLD回收子进程
    自定义信号处理函数,同时在accept()出错时判断EINTR并重启调用:

    void sigchld_handler(int sig) {
        // 非阻塞回收所有退出的子进程,避免僵尸进程
        while (waitpid(-1, nullptr, WNOHANG) > 0);
    }
    
    // 服务器初始化时设置信号处理
    signal(SIGCHLD, sigchld_handler);
    
    // 修改accept逻辑,处理EINTR错误
    int client_fd;
    while ((client_fd = accept(listen_fd, nullptr, nullptr)) == -1) {
        if (errno == EINTR) {
            continue; // 被信号打断,重新执行accept
        }
        perror("Unable to accept!");
        exit(EXIT_FAILURE);
    }
    

修正主循环与套接字关闭逻辑

确保父进程维持accept()循环,且父子进程正确关闭不需要的套接字:

while (1) {
    int client_fd = accept(listen_fd, nullptr, nullptr);
    // 处理accept错误(含EINTR)
    if (client_fd == -1) {
        if (errno == EINTR) continue;
        perror("Unable to accept!");
        break;
    }

    pid_t pid = fork();
    if (pid == -1) {
        perror("fork failed");
        close(client_fd);
        continue;
    }

    if (pid == 0) {
        // 子进程:处理客户端,关闭监听套接字
        close(listen_fd);
        // 这里添加你的客户端处理逻辑
        handle_client(client_fd);
        close(client_fd);
        exit(EXIT_SUCCESS);
    } else {
        // 父进程:关闭客户端套接字,回到accept循环
        close(client_fd);
        // 绝对不能在这里调用exit或return!
    }
}

验证步骤

  1. 启动服务器后,用ps aux命令查看父进程是否存活:如果父进程已退出,说明主循环逻辑有误。
  2. 在perror("Unable to accept!")前打印errno值:printf("accept errno: %d\n", errno);,如果输出为4(EINTR),则确认是信号打断问题。
  3. 用ps aux | grep defunct检查是否有僵尸进程,若存在,说明子进程未被正确回收。

内容的提问来源于stack exchange,提问作者wojtek5739g

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 01:57:38