欢迎光临
我们一直在努力

Linux入门DAY36(TCP 并发服务器)

#TCP 并发;#多进程;#多线程;#线程池;#IO 多路复用;#select;#epoll

前言

TCP 协议是面向连接的流式传输协议,基础单循环 TCP 服务器逻辑简单:socket→bind→listen→accept→收发数据→close,但是accept之后同一时间只能处理一个客户端连接,当第二个客户端接入时必须等待上一个客户端断开连接,服务能力严重受限。

为实现一台服务器同时和多个客户端通信,需要构建TCP 并发服务器。UDP 本身基于无连接,recvfrom 可以直接接收任意客户端报文,天然具备并发能力;TCP 需要额外并发模型来处理多路连接。常见 4 种实现方式:多进程模型、多线程模型、线程池模型、IO 多路复用模型。

1 单循环 TCP 服务器(非并发)

单循环服务器:服务器处理完一个客户端全部通信,断开之后,才会再次调用accept接收下一个客户端。

  • 缺点:无法同时服务多个客户端;一个客户端长时间占用连接,其他客户端全部阻塞等待。
  • 适用场景:设备连接数量极少的简单场景。

2 多进程并发服务器

2.1 实现思路

  • 父进程完成socket、bind、listen,循环调用accept阻塞等待客户端连接;
  • 当accept返回通信套接字connfd,调用fork()创建子进程;
  • 子进程:关闭监听套接字sockfd,循环recv/send和客户端完成数据交互,通信结束关闭connfd,子进程退出;
  • 父进程:关闭通信套接字connfd,回到循环继续accept等待下一个客户端;
  • 需要处理僵尸进程,父进程使用wait/waitpid回收子进程资源。
  • 伪代码示例

    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    bind(sockfd,…);
    listen(sockfd,20);
    while(1){
    int connfd = accept(sockfd,NULL,NULL);
    pid_t pid = fork();
    if(pid > 0){
    //父进程
    close(connfd);
    }else if(pid == 0){
    //子进程,处理客户端业务
    close(sockfd);
    while(1){
    recv(connfd,buf,sizeof(buf),0);
    send(connfd,buf,len,0);
    }
    close(connfd);
    exit(0);
    }
    }

    优缺点

    ✅优点:进程之间资源完全隔离,一个客户端异常崩溃不会影响其他客户端;稳定性高。 ❌缺点:

  • 创建进程开销大,需要分配完整 0‑4G 虚拟内存;
  • 并发数量受系统进程上限限制,高并发场景资源消耗严重;
  • 需要处理僵尸进程回收,代码复杂度上升。
  • 3 多线程并发服务器

    3.1 实现思路

  • 主线程完成socket、bind、listen;
  • 循环调用accept获取客户端通信描述符connfd;
  • 每接入一个客户端,调用pthread_create创建新线程,把connfd传入线程函数;
  • 子线程内部执行recv/send完成和客户端通信,通信结束关闭套接字;
  • 设置线程为分离属性 pthread_detach,线程退出后操作系统自动回收资源,不需要主线程调用pthread_join阻塞等待。
  • 伪代码示例

    void *client_handle(void *arg){
    int connfd = *(int*)arg;
    free(arg);
    char buf[1024]={0};
    while(1){
    recv(connfd,buf,sizeof(buf),0);
    send(connfd,buf,strlen(buf),0);
    }
    close(connfd);
    pthread_exit(NULL);
    }

    int main(void){
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    bind(sockfd,…);
    listen(sockfd,20);
    while(1){
    int *pfd = malloc(sizeof(int));
    *pfd = accept(sockfd,NULL,NULL);
    pthread_t tid;
    pthread_create(&tid,NULL,client_handle,pfd);
    pthread_detach(tid); //设置分离属性
    }
    return 0;
    }

    优缺点

    ✅优点:

  • 线程只分配独立栈空间,创建销毁开销远小于进程;
  • 同一进程内线程共享堆、全局数据,线程间通信简单。 ❌缺点:
  • 线程共享进程地址空间,一个线程崩溃会导致整个进程全部退出,安全性弱于多进程;
  • 大量客户端频繁连接断开,会频繁创建销毁线程,带来性能损耗。
  • 4 线程池并发服务器

    4.1 背景

    多线程模型每当客户端连接到来就创建线程,客户端断开销毁线程。高频连接场景,频繁创建销毁线程会带来 CPU 开销。

    线程池:生产者‑消费者模式。

    • 主线程(生产者):调用accept接收客户端连接,把客户端connfd放入任务队列;
    • 工作线程(消费者):线程启动时预先创建 N 个工作线程,阻塞读取任务队列,拿到connfd之后处理客户端收发业务。

    优缺点

    ✅优点:线程预先创建,避免频繁创建销毁线程的开销;可控制最大并发线程数量,防止系统资源耗尽。 ❌缺点:需要实现线程安全的任务队列,需要互斥锁、条件变量完成队列同步,代码逻辑复杂。

    5 IO 多路复用(select/poll/epoll)

    5.1 核心思想

    IO 多路复用:单个进程 / 线程,不创建进程、线程,由内核同时监视多个文件描述符 IO 事件(读、写、异常)。 当某个文件描述符发生可读 / 可写事件,内核通知应用程序,应用程序再去执行读写操作。 监视对象包含:监听套接字sockfd、各个客户端通信套接字connfd。

    5.2 select

    select将待监测的文件描述符集合传递给内核,内核阻塞等待,当集合中任意 fd 发生 IO 事件,返回就绪事件数量;用户态遍历集合判断哪个 fd 就绪。

    关键宏:

    • FD_ZERO:清空 fd 集合
    • FD_SET:把 fd 加入集合
    • FD_CLR:从集合移除 fd
    • FD_ISSET:判断 fd 是否就绪

    函数原型:

    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

    参数说明:

  • nfds:待检测最大文件描述符 +1;
  • readfds:监听读事件集合;writefds写事件集合;exceptfds异常事件;
  • timeout:超时时间,NULL 代表永久阻塞。
  • 返回值:就绪事件 fd 的个数;超时返回 0;出错返回‑1。

    select 编程逻辑
  • 创建 socket,bind,listen;
  • 初始化 fd 集合,把监听套接字sockfd加入读集合;
  • while 循环调用 select 交给内核监控;
  • 如果sockfd就绪,代表有新客户端连接,调用 accept 得到 connfd,把 connfd 加入监控集合;
  • 如果普通 connfd 就绪,执行 recv/send 处理客户端数据;
  • 客户端断开,将 fd 从集合清除,close 关闭套接字。
  • 5.3 poll

    select 存在文件描述符数量上限限制,poll 优化该问题,使用数组保存待监控 fd,原理和 select 类似。

    5.4 epoll

    Linux 独有 IO 多路复用,性能最优,适合高并发大量连接场景。 工作流程:

  • epoll_create:创建 epoll 实例;
  • epoll_ctl:添加 / 删除 / 修改需要监控的文件描述符;
  • epoll_wait:阻塞等待 IO 事件就绪,内核直接返回就绪 fd 列表,不需要用户遍历全部 fd。
  • IO 多路复用优缺点

    ✅优点:无需大量创建进程线程,内存占用低,单机可以支撑上万并发连接; ❌缺点:编程逻辑复杂;select 有 fd 上限;epoll 属于 Linux 平台特有,可移植性差。

    小结

  • 多进程:稳定性高,开销大,适合并发量不大,对隔离性要求高;
  • 多线程:开销比进程小,但是线程崩溃会让整个服务挂掉;适合中等并发;
  • 线程池:预先创建线程,减少频繁创建销毁开销,需要实现线程安全队列;
  • IO 多路复用 select/poll/epoll:单进程支撑高并发,epoll 性能最好,高并发网络服务器首选。
  • 表格

    并发模型资源开销隔离性并发上限适用场景
    多进程 连接数少,追求稳定
    多线程 中等并发业务
    线程池 连接频繁创建销毁场景
    IO 多路复用 epoll 极低 极高 高并发服务器

    补充:UDP 通信天然并发,不需要以上并发模型;TCP 面向连接,每一个客户端对应一个 connfd,必须借助上面模型实现多客户端同时服务。

    赞(0)
    未经允许不得转载:171主机测评 » Linux入门DAY36(TCP 并发服务器)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址