跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客我的书AI学习GitHub 精选镜像AI 生图工具UI配色美学关于
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

TCP 服务器高并发实现:单进程、多进程及多线程模型详解

TCP 服务器高并发实现涉及单进程、多进程及多线程三种模型。单进程模型无法处理并发连接;多进程模型通过 fork 创建子进程处理客户端请求,需解决僵尸进程问题(如双 fork 或忽略 SIGCHLD);多线程模型利用线程共享内存优势,资源消耗低于多进程。文章通过 C++ Socket 编程示例对比了三种模型的代码实现、优缺点及适用场景,帮助开发者理解 TCP 网络编程的核心框架与并发设计思想。

蓝绿部署发布于 2026/3/17更新于 2026/10/989 浏览
TCP 服务器高并发实现:单进程、多进程及多线程模型详解

TCP 服务器整体流程

一个最基础的 TCP 服务器,需要经历以下步骤:

socket() bind() listen() accept() read()/write() close()

流程图可以理解为:

创建套接字 → 绑定端口 → 开始监听 → 等待客户端连接 → 收发数据 → 关闭连接

我们知道 TCP 是连接的、可靠的传输层协议,所以每一个客户端在访问服务器的时候都会建立连接(也就是三次握手)。在客户端没有申请建立连接的时候,服务器要始终保持监听状态(调用系统调用接口 listen),因为用户可能在任意时间对服务器进行访问。一旦客户端进行申请连接(调用系统调用接口 connect),服务器就得与客户端进行连接(调用系统调用接口 accept),然后与客户端进行通信,通信结束后进行关闭。

为什么有 listenfd 还要 accept 生成新的 sockfd?

这是一个关于 TCP 网络编程中的常见问题。我们通过 socket 已经创建了一个 listenfd,为什么还要系统调用 accept 再创建一个 sockfd 呢?

我们可以用生活中的例子来理解:商场的美食区派专人拉客(张三),吸引顾客进店后交给店内员工(李四)接待。张三继续出去拉客,李四负责服务。这样张三一直负责监听新顾客,李四负责具体服务。

这就是 TCP 的设计思想:

  • listenfd:专门负责接收新连接
  • sockfd:专门负责和客户端通信

单进程版本

TCP 服务端

class Server {
public:
    Server(uint16_t server_port, std::string server_ip) : server_port_(server_port), server_ip_(server_ip) { }
    void init() {
        listenfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd_ < 0) {
            printf("listenfd_ create error!!!\n");
            exit(1);
        }
        printf("listenfd_ create successful!!!, listenfd_:%d\n", listenfd_);
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port_);
        inet_pton(AF_INET, server_ip_.c_str(), &(server.sin_addr));
        if (bind(listenfd_, (struct sockaddr *)&server, sizeof(server)) < 0) {
            printf("bind fail !!!\n");
            exit(2);
        }
        printf("bind successful!!!\n");
        if (listen(listenfd_, 10) < 0) {
            printf("listen fail!!!\n");
            exit(3);
        }
        printf("listen successful!!!\n");
    }
    void service(int sockfd, std::string client_ip, uint16_t client_port) {
        while (1) {
            char buffer[1024];
            ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);
            if (s > 0) {
                buffer[s] = 0;
                printf("client say:%s\n", buffer);
                std::string info = "server say:";
                info += buffer;
                write(sockfd, info.c_str(), info.size());
            } else if (s == 0) {
                printf("client quit !!!\n");
                break;
            } else {
                printf("read error !!!\n");
                break;
            }
        }
    }
    void start() {
        while (1) {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listenfd_, (struct sockaddr *)&client, &len);
            if (sockfd < 0) {
                printf("accept fail!!!\n");
            }
            printf("get a new link !!!, sockfd : %d\n", sockfd);
            uint16_t client_port = ntohs(client.sin_port);
            char client_ip[16];
            inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof client_ip);
            service(sockfd, client_ip, client_port);
            close(sockfd);
        }
    }
    ~Server() {
        close(listenfd_);
    }
private:
    int listenfd_;
    uint16_t server_port_;
    std::string server_ip_;
};

TCP 客户端

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("\n\t Usage: %s serverip serverport!\n");
        exit(1);
    }
    uint16_t server_port = atoi(argv[2]);
    std::string server_ip = argv[1];
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        printf("sockfd fail !!!\n");
        exit(2);
    }
    printf("sock successful !!!, sockfd : %d\n", sockfd);
    struct sockaddr_in server;
    memset(&server, 0, sizeof server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr);
    if (connect(sockfd, (struct sockaddr *)&server, sizeof server) < 0) {
        printf("connect fail!!!\n");
    }
    while (1) {
        std::string message;
        std::getline(std::cin, message);
        write(sockfd, message.c_str(), message.size());
        char buffer[1024];
        ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);
        if (s > 0) {
            buffer[s] = 0;
            printf("%s\n", buffer);
        } else if (s == 0) {
            printf("server close\n");
            break;
        }
    }
    close(sockfd);
    return 0;
}

这样一个简单的单进程版本的基于 TCP 的 socket 网络编程就成功实现了。但是存在如下问题:由于我们的服务器现在是单进程的,当一个客户端连接到服务器之后,会调用 service 进行服务,而 service 是循环执行,只有当客户端退出的时候,service 才能退出。这就导致我们无法继续监听,另一个客户端访问时没有反应,只有当一个客户端退出之后,这个客户端才得以访问。作为一个服务器,这样的问题是不可以存在的,所以需要改为多进程版本。

多进程版本 TCP 服务器

双 fork

class Server {
public:
    Server(uint16_t server_port, std::string server_ip) : server_port_(server_port), server_ip_(server_ip) { }
    void init() {
        listenfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd_ < 0) {
            printf("listenfd_ create error!!!\n");
            exit(1);
        }
        printf("listenfd_ create successful!!!, listenfd_:%d\n", listenfd_);
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port_);
        inet_pton(AF_INET, server_ip_.c_str(), &(server.sin_addr));
        if (bind(listenfd_, (struct sockaddr *)&server, sizeof(server)) < 0) {
            printf("bind fail !!!\n");
            exit(2);
        }
        printf("bind successful!!!\n");
        if (listen(listenfd_, 10) < 0) {
            printf("listen fail!!!\n");
            exit(3);
        }
        printf("listen successful!!!\n");
    }
    void service(int sockfd, std::string client_ip, uint16_t client_port) {
        while (1) {
            char buffer[1024];
            ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);
            if (s > 0) {
                buffer[s] = 0;
                printf("client say:%s\n", buffer);
                std::string info = "server say:";
                info += buffer;
                write(sockfd, info.c_str(), info.size());
            } else if (s == 0) {
                printf("client quit !!!\n");
                break;
            } else {
                printf("read error !!!\n");
                break;
            }
        }
    }
    void start() {
        while (1) {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listenfd_, (struct sockaddr *)&client, &len);
            if (sockfd < 0) {
                printf("accept fail!!!\n");
            }
            printf("get a new link !!!, sockfd : %d\n", sockfd);
            uint16_t client_port = ntohs(client.sin_port);
            char client_ip[16];
            inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof client_ip);
            pid_t id = fork();
            if (id == 0) {
                close(listenfd_);
                if (fork() > 0) {
                    exit(0);
                }
                service(sockfd, client_ip, client_port);
                close(sockfd);
                exit(0);
            }
            close(sockfd);
            pid_t ret = waitpid(id, nullptr, 0);
        }
    }
    ~Server() {
        close(listenfd_);
    }
private:
    int listenfd_;
    uint16_t server_port_;
    std::string server_ip_;
};

通过这样的方式就可以很好的保证多个客户端都可以享受到服务器的服务。

解释说明:首先我们创建子进程之后,父进程必须关掉 sockfd。这是因为随着客户端的访问人数增多,如果我们不关闭,就会导致文件描述符一直增多,并且我们将任务交给子进程处理之后,父进程就不需要再继续占用这个文件描述符了。而在子进程中,继续创建子进程(孙子进程),然后让子进程直接退出,这样父进程就可以直接获取到退出结果,也就不需要继续等待。对于孙子进程,当它的父进程(也就是子进程)退出之后,孙子进程就会被托孤,交给操作系统进行回收。

简而言之:

1️⃣ 父进程必须关闭 sockfd

否则:

  • 文件描述符会不断增加
  • 造成资源泄漏
2️⃣ 双 fork 防止僵尸进程

父进程 └── 子进程 └── 孙子进程 当子进程退出:

  • 孙子进程变成孤儿进程
  • 由 init 接管
  • 自动回收

忽略 SIGCHLD 信号

我们还可以不用父进程进行等待,利用信号机制,通过对 SIGCHLD 信号进行忽略,这样子进程在退出之后,也不会出现僵尸进程,同时父进程还可以继续监听。

class Server {
public:
    Server(uint16_t server_port, std::string server_ip) : server_port_(server_port), server_ip_(server_ip) { }
    void init() {
        listenfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd_ < 0) {
            printf("listenfd_ create error!!!\n");
            exit(1);
        }
        printf("listenfd_ create successful!!!, listenfd_:%d\n", listenfd_);
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port_);
        inet_pton(AF_INET, server_ip_.c_str(), &(server.sin_addr));
        if (bind(listenfd_, (struct sockaddr *)&server, sizeof(server)) < 0) {
            printf("bind fail !!!\n");
            exit(2);
        }
        printf("bind successful!!!\n");
        if (listen(listenfd_, 10) < 0) {
            printf("listen fail!!!\n");
            exit(3);
        }
        printf("listen successful!!!\n");
    }
    void service(int sockfd, std::string client_ip, uint16_t client_port) {
        while (1) {
            char buffer[1024];
            ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);
            if (s > 0) {
                buffer[s] = 0;
                printf("client say:%s\n", buffer);
                std::string info = "server say:";
                info += buffer;
                write(sockfd, info.c_str(), info.size());
            } else if (s == 0) {
                printf("client quit !!!\n");
                break;
            } else {
                printf("read error !!!\n");
                break;
            }
        }
    }
    void start() {
        signal(SIGCHLD, SIG_IGN);
        while (1) {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listenfd_, (struct sockaddr *)&client, &len);
            if (sockfd < 0) {
                printf("accept fail!!!\n");
            }
            printf("get a new link !!!, sockfd : %d\n", sockfd);
            uint16_t client_port = ntohs(client.sin_port);
            char client_ip[16];
            inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof client_ip);
            pid_t id = fork();
            if (id == 0) {
                close(listenfd_);
                service(sockfd, client_ip, client_port);
                close(sockfd);
                exit(0);
            }
            close(sockfd);
        }
    }
    ~Server() {
        close(listenfd_);
    }
private:
    int listenfd_;
    uint16_t server_port_;
    std::string server_ip_;
};

signal(SIGCHLD, SIG_IGN);

让内核自动回收子进程。

多线程版本 TCP 服务器

当客户越来越多时,我们可以通过多线程的方式。注意类成员函数默认隐藏一个 this 指针,而多线程的处理函数中要求函数签名为 void* (*)(void*),因此会造成类型不匹配的问题。为了解决这个问题我们要让它成为一个全局的函数,所以要增加 static。而且全局函数想要调用类内方法是不可以的,所以我们增加了一个 ThreadData 结构体传递 this 指针。

struct ThreadData {
    ThreadData(int sockfd, int16_t client_port, std::string client_ip, Server *srv)
        : sockfd_(sockfd), client_port_(client_port), client_ip_(client_ip), srv_(srv) { }
    int sockfd_;
    int16_t client_port_;
    std::string client_ip_;
    Server *srv_;
};

class Server {
public:
    Server(uint16_t server_port, std::string server_ip) : server_port_(server_port), server_ip_(server_ip) { }
    void init() {
        listenfd_ = socket(AF_INET, SOCK_STREAM, 0);
        if (listenfd_ < 0) {
            printf("listenfd_ create error!!!\n");
            exit(1);
        }
        printf("listenfd_ create successful!!!, listenfd_:%d\n", listenfd_);
        struct sockaddr_in server;
        memset(&server, 0, sizeof server);
        server.sin_family = AF_INET;
        server.sin_port = htons(server_port_);
        inet_pton(AF_INET, server_ip_.c_str(), &(server.sin_addr));
        if (bind(listenfd_, (struct sockaddr *)&server, sizeof(server)) < 0) {
            printf("bind fail !!!\n");
            exit(2);
        }
        printf("bind successful!!!\n");
        if (listen(listenfd_, 10) < 0) {
            printf("listen fail!!!\n");
            exit(3);
        }
        printf("listen successful!!!\n");
    }
    void service(int sockfd, std::string client_ip, uint16_t client_port) {
        while (1) {
            char buffer[1024];
            ssize_t s = read(sockfd, buffer, sizeof(buffer) - 1);
            if (s > 0) {
                buffer[s] = 0;
                printf("client say:%s\n", buffer);
                std::string info = "server say:";
                info += buffer;
                write(sockfd, info.c_str(), info.size());
            } else if (s == 0) {
                printf("client quit !!!\n");
                break;
            } else {
                printf("read error !!!\n");
                break;
            }
        }
    }
    static void *routine(void *arg) {
        pthread_detach(pthread_self());
        ThreadData *td = (ThreadData *)arg;
        td->srv_->service(td->sockfd_, td->client_ip_, td->client_port_);
        close(td->sockfd_);
        delete td;
        return nullptr;
    }
    void start() {
        while (1) {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            int sockfd = accept(listenfd_, (struct sockaddr *)&client, &len);
            if (sockfd < 0) {
                printf("accept fail!!!\n");
            }
            printf("get a new link !!!, sockfd : %d\n", sockfd);
            uint16_t client_port = ntohs(client.sin_port);
            char client_ip[16];
            inet_ntop(AF_INET, &(client.sin_addr), client_ip, sizeof client_ip);
            pthread_t tid;
            ThreadData *td = new ThreadData(sockfd, client_port, client_ip, this);
            pthread_create(&tid, nullptr, routine, td);
        }
    }
    ~Server() {
        close(listenfd_);
    }
private:
    int listenfd_;
    uint16_t server_port_;
    std::string server_ip_;
};

三种模型对比总结

模型并发能力资源消耗适用场景
单进程❌低理解
多进程✅高中小并发
多线程✅中常见服务器

我们从最简单的单进程版本,一步一步改造成多进程、多线程版本。代码变多了,结构也复杂了,但核心其实很简单:让服务器同时服务多个客户端。单进程能跑起来,多进程能并发,多线程更高效。现在相信我们对 TCP 服务器的整体框架就有了一定清晰的认识了。

目录

  1. TCP 服务器整体流程
  2. 为什么有 listenfd 还要 accept 生成新的 sockfd?
  3. 单进程版本
  4. TCP 服务端
  5. TCP 客户端
  6. 多进程版本 TCP 服务器
  7. 双 fork
  8. 1️⃣ 父进程必须关闭 sockfd
  9. 2️⃣ 双 fork 防止僵尸进程
  10. 忽略 SIGCHLD 信号
  11. 多线程版本 TCP 服务器
  12. 三种模型对比总结

更多推荐文章

查看全部
  • B/S 架构:三层模型、演进与取舍
  • 大语言模型词表裁剪方法与实践
  • Ubuntu 22.04 系统下 libwebkit2gtk-4.1-0 安装指南
  • 本地部署 OpenClaw 实现 AI Agent 自动化
  • 深入理解 LRU 与 LFU 缓存算法实现
  • 弃用 MobaXterm,拥抱开源终端 Tabby
  • Python 实现 AI 绘画用户评价自动分类与分析报告生成
  • 免费部署 openClaw 龙虾机器人教程
  • 大模型的起源、现状与未来趋势解析
  • 本地电脑使用 Ollama 部署大模型及 Chatbox 界面指南
  • GESP C++ 二级认证真题与解析(单选题 1-15)
  • Java 并发核心:synchronized 与 volatile 深度解析
  • Ubuntu 下 llama.cpp 编译构建与性能调优实战
  • AI 写作技巧提升网文创作效率
  • 鸿蒙金融理财项目:生态合作与用户运营优化
  • 基于 OpenClaw 与飞书构建 7x24 小时服务器运维机器人
  • 国内如何升级 GitHub Copilot 到专业版
  • Python 采集《雪中悍刀行》弹幕生成词云实例
  • GitHub 全界面中文化插件安装与配置指南
  • AnythingLLM + Whisper:工程化语音转文本优化

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online