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

如何基于TCP/IP实现支持多客户端接入的多线程C/S通信系统

TCP服务端多线程改造方案

原有单连接服务端的核心问题是listen backlog设置过小、仅接受一次连接就退出,无法处理多客户端并发。直接使用POSIX线程库改造即可,线程调度由操作系统内核自动完成,不需要手动实现调度逻辑,核心思路是主线程专职监听新连接,每建立一个客户端连接就创建独立线程处理消息收发。

核心改造逻辑

  • 监听逻辑调整:将listen的等待队列长度从1改为128(常规服务端配置),主线程进入死循环调用accept等待新连接,不再接受一个连接就退出
  • 线程模型:每个新连接对应一个独立处理线程,将客户端套接字作为参数传入线程函数,线程内完成消息接收、打印后主动关闭客户端套接字
  • 资源回收:设置线程为分离状态,线程退出后内核自动回收资源,不需要主线程调用pthread_join等待
  • 错误修复:删除原代码中TCP场景下无效的unlink调用(该逻辑仅用于Unix域套接字清理,TCP下使用会误删本地文件)

改造后完整代码

服务端代码(server.c)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>

#define SIZE 1024

// 客户端消息处理线程函数
void *client_handler(void *arg) {
    int client_sock = *(int *)arg;
    free(arg); // 释放提前分配的套接字内存
    char msg[SIZE] = {0};

    // 接收客户端消息
    ssize_t recv_len = recv(client_sock, msg, SIZE - 1, 0);
    if (recv_len > 0) {
        printf("收到客户端消息: %s", msg);
    }

    close(client_sock);
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("启动参数错误,用法: ./server 端口号\n");
        return 1;
    }

    int psock;
    struct sockaddr_in addr;

    // 初始化监听套接字
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡IP,不需要单独指定IP
    addr.sin_port = htons(atoi(argv[1]));
    psock = socket(AF_INET, SOCK_STREAM, 0);
    
    // 端口复用,避免重启时端口占用
    int opt = 1;
    setsockopt(psock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    bind(psock, (struct sockaddr *)&addr, sizeof(addr));
    listen(psock, 128); // 监听队列长度设为128

    printf("等待客户端连接,监听端口: %s\n", argv[1]);

    // 循环接受新连接
    while (1) {
        struct sockaddr_in cli_addr;
        socklen_t cli_addr_len = sizeof(cli_addr);
        // 单独分配内存存客户端套接字,避免多线程变量覆盖
        int *client_sock = malloc(sizeof(int));
        *client_sock = accept(psock, (struct sockaddr *)&cli_addr, &cli_addr_len);
        
        if (*client_sock < 0) {
            perror("接受连接失败");
            free(client_sock);
            continue;
        }
        printf("新客户端连接建立\n");

        pthread_t tid;
        pthread_create(&tid, NULL, client_handler, client_sock);
        pthread_detach(tid); // 设置线程分离,自动回收资源
    }

    close(psock);
    return 0;
}

客户端代码(client.c)

原客户端逻辑基本可用,仅删除不存在的头文件引用即可,支持多开客户端测试并发:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define SIZE 1024

int main(int argc, char *argv[]) {
    if (argc != 3) {
        printf("启动参数错误,用法: ./client 服务端IP 端口号\n");
        return 1;
    }

    int sock;
    char msg[SIZE] = {0};
    struct sockaddr_in addr;

    addr.sin_family = AF_INET;
    inet_aton(argv[1], &addr.sin_addr);
    addr.sin_port = htons(atoi(argv[2]));
    sock = socket(AF_INET, SOCK_STREAM, 0);
    
    if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
        perror("连接服务端失败");
        close(sock);
        return 1;
    }

    printf("请输入要发送的消息: ");
    fgets(msg, sizeof(msg), stdin);
    send(sock, msg, strlen(msg) + 1, 0);

    close(sock);
    return 0;
}

编译运行指令

注意服务端编译需要链接线程库,加-lpthread参数:

# 编译服务端
gcc server.c -o server -lpthread
# 启动服务端,监听1234端口
./server 1234

# 新开多个终端,分别编译启动客户端即可测试并发
gcc client.c -o client
./client 127.0.0.1 1234

注意事项

  • 不需要手动实现线程调度逻辑,Linux内核会根据CPU负载自动完成线程的调度与资源分配
  • 传递客户端套接字给线程时不要直接传局部变量地址,否则主线程快速accept时会覆盖内存值,导致线程拿到错误的套接字,用单独malloc的内存传递即可避免该问题
  • 代码默认用INADDR_ANY绑定所有网卡IP,属于无固定IP绑定的实现,不需要单独指定服务端监听IP,符合简易实现需求
  • 如果需要支持客户端长连接发多条消息,只要把线程处理函数里的recv改成循环接收即可,现有框架不用调整

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 02:42:44