当前位置: 首页 > news >正文

Linux 软件编程(十二)网络编程:TCP 并发服务器构建与 IO 多路复用

在网络编程中,服务器需要应对多个客户端的连接与请求,单循环服务器因同一时刻仅能处理一个客户端任务,无法满足高并发场景。而 TCP 并发服务器可实现多客户端同时处理 。

一、TCP 并发服务器

(一)单循环与并发服务器区别

  • 单循环服务器:同一时刻仅能处理一个客户端任务,处理完当前客户端才能响应下一个,效率低。
  • 并发服务器:可同时处理多个客户端任务,提升服务端资源利用率与响应能力。

(二)TCP 连接特性

TCP 基于 “三次握手” 建立 一对一连接 ,可靠传输数据,但需高效并发模型支撑多客户端交互。

二、TCP 服务端并发模型

(一)多进程模型

  1. 原理:服务端通过 fork() 创建子进程,父进程负责监听新连接,子进程处理客户端交互。
  2. 特点
    • 资源开销大:进程有独立地址空间,创建、切换成本高。
    • 安全性高:进程间资源隔离,一个进程异常一般不影响其他进程。
  3. 代码示例
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>#define SER_PORT 50000
#define SER_IP "192.168.0.149"struct sockaddr_in seraddr;void hander(int handnum)
{wait(NULL);
}int init_tcp_recv()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("sockfd error");return -1;}seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SER_PORT);seraddr.sin_addr.s_addr = inet_addr(SER_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");return -1;}ret =listen(sockfd, 10);if(ret < 0){perror("listen error");return -1;}return sockfd;
}int main(int argc, char const *argv[])
{signal(SIGCHLD, hander);int sockfd = init_tcp_recv();struct sockaddr_in cliaddr;socklen_t lenaddr = sizeof(cliaddr);while(1){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &lenaddr);if(connfd < 0){perror("accept error");return -1;}pid_t pid = fork();if(pid > 0){}else if(pid == 0){char buff[1024] = {0};while(1){memset(buff, 0, sizeof(buff));int ret = recv(connfd, buff, sizeof(buff), 0);if(ret < 0){perror("error recv");break;}else if(0 == ret){printf("[%s:%d]: client off\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));close(connfd);break;}printf("[%s:%d]:%s\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port), buff);strcat(buff, "---->ok");int cnt = send(connfd, buff, strlen(buff), 0);if(cnt < 0){perror("send error");close(connfd);break;}}}}close(sockfd);return 0;}

(二)多线程模型

  1. 原理:借助 pthread_create() 创建线程,主线程负责 accept 新连接,子线程处理客户端数据收发。
  2. 特点
    • 资源开销小:线程共享进程地址空间,创建、切换成本低于进程,相同资源环境下并发量更高。
    • 需注意同步:线程共享资源,如需客户端地址信息等,需用锁机制(如互斥锁)避免竞争,否则易引发数据混乱,或者创建结构体将客户端信息和通信套接字放在里面,然后线程传参时第四个参数传入结构体的地址,然后进行访问内容。
  3. 伪代码示例
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <pthread.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include<pthread.h>#define SER_PORT 50000
#define SER_IP "192.168.0.149"struct sockaddr_in seraddr;int init_tcp_recv()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("sockfd error");return -1;} //允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:int optval = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SER_PORT);seraddr.sin_addr.s_addr = inet_addr(SER_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");return -1;}ret =listen(sockfd, 10);if(ret < 0){perror("listen error");return -1;}return sockfd;
}// struct sockaddr_in cliaddr;
// socklen_t lenaddr = sizeof(cliaddr);void *recv_senf(void *arg)
{int connfd = *((int *)arg);char buff[1024] = {0};while(1){memset(buff, 0, sizeof(buff));int ret = recv(connfd, buff, sizeof(buff), 0);if(ret < 0){perror("error recv");break;}else if(0 == ret){printf("client off\n");close(connfd);break;}printf("%s\n", buff);strcat(buff, "---->ok");int cnt = send(connfd, buff, strlen(buff), 0);if(cnt < 0){perror("send error");close(connfd);break;} }return NULL;
}int main(int argc, char const *argv[])
{int sockfd = init_tcp_recv();if(sockfd < 0){return -1;}pthread_t tid;while(1){int connfd = accept(sockfd, NULL, NULL);if(connfd < 0){perror("accept error");return -1;}pthread_create(&tid, NULL, recv_senf, &connfd);pthread_detach(tid);}close(sockfd);return 0;}

(三)线程池模型

  1. 背景:多线程 / 多进程模型中,频繁创建、销毁线程 / 进程会产生大量时间消耗。线程池基于 生产者 - 消费者模型 与 任务队列 ,预先创建一定数量线程,复用线程处理任务,减少资源开销。
  2. 原理
    • 生产者(主线程):负责 accept 客户端连接,将任务(如处理客户端请求)放入任务队列。
    • 消费者(线程池中的次线程):从任务队列取出任务并执行,执行完可继续获取新任务,无需频繁创建销毁。
  3. 模型示意图
    主线程作为生产者生产任务(如客户端连接处理任务),放入任务队列;多个次线程作为消费者,从队列取任务执行,实现任务高效复用与并发处理。

(四)IO 多路复用模型

  1. 核心思想:让一个进程 / 线程 复用多个文件描述符(fd)的读写操作 ,不创建新进程 / 线程,通过一个进程监测、处理多个文件(如 socket 连接)的读写事件。
  2. 应用场景:需同时监听多个 fd (如 stdinconnfd 等),避免阻塞 IO 导致程序 “卡主”,典型函数有 selectpollepoll 。
(1)select 函数
  • 函数与操作步骤
    • 创建文件描述符集合:用 fd_set 类型,通过 FD_ZERO 清空集合,FD_SET 添加关注的 fd 。
    • 传递集合给内核监测:调用 select 函数,内核监测集合中 fd 的事件(读、写、异常等),监测期间进程阻塞。
    • 处理事件:内核监测到事件后,select 解除阻塞,通过 FD_ISSET 判断具体 fd 事件,执行对应读写操作。
  • 关键函数与 select 原型
  • void FD_CLR(int fd, fd_set *set); 
    int  FD_ISSET(int fd, fd_set *set); 
    void FD_SET(int fd, fd_set *set); 
    void FD_ZERO(fd_set *set); 

    int select(int nfds, fd_set *readfds, fd_set *writefds,
    fd_set *exceptfds, struct timeval *timeout);
    功能:传递文件描述符结合表给内核并等待获取事件结果
    参数:
    nfds : 关注的最大文件描述符+1
    readfds:读事件的文件描述符集合
    writefds:写事件的文件描述符集合
    exceptfds:其他事件的文件描述符集合
    timeout:设置select监测时的超时时间
    NULL : 不设置超时时间(select一直阻塞等待)

            返回值:
    成功:返回内核监测的到达事件的个数
    失败:-1
    0 : 超时时间到达,但没有事件发生,则返回

  • 伪代码示例(结合 TCP 服务端)
#include <sys/select.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/time.h>
#include<string.h>
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */#include <sys/socket.h>
#include <netinet/ip.h> /* superset of previous */
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include<pthread.h>#define SER_PORT 50000
#define SER_IP "192.168.0.149"struct sockaddr_in seraddr;int init_tcp_recv()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if(sockfd < 0){perror("sockfd error");return -1;} //允许绑定处于TIME_WAIT状态的地址,避免端口占用问题:int optval = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SER_PORT);seraddr.sin_addr.s_addr = inet_addr(SER_IP);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");return -1;}ret =listen(sockfd, 10);if(ret < 0){perror("listen error");return -1;}return sockfd;
}int main(int argc, char const *argv[])
{int sockfd = init_tcp_recv();if(sockfd < 0){return -1;}fd_set rdfds;fd_set rdfdstem;FD_ZERO(&rdfds);FD_ZERO(&rdfdstem);FD_SET(sockfd, &rdfds);int maxfd = sockfd; char buff[1024] = {0};while(1){rdfdstem = rdfds;int ret = select(maxfd + 1, &rdfds, NULL, NULL, NULL);if(ret < 0){perror("select error");return -1;}if(FD_ISSET(sockfd, &rdfdstem)){int connfd = accept(sockfd, NULL, NULL);if(connfd < 0){perror("accept error");return -1;}FD_SET(connfd, &rdfds);maxfd = maxfd > connfd ? maxfd : connfd;}for(int i = sockfd + 1;i < maxfd + 1;++i){if(FD_ISSET(i, &rdfdstem)){memset(buff, 0, sizeof(buff));ssize_t cnt  = recv(i, buff, sizeof(buff), 0);if(cnt < 0){perror("recv error");FD_CLR(i, &rdfds);close(i);continue;}else if(cnt == 0){FD_CLR(i, &rdfds);close(i);continue;}printf("%s\n", buff);strcat(buff, "---->ok");cnt = send(i, buff, strlen(buff), 0);if(cnt < 0){perror("send error");FD_CLR(i, &rdfds);close(i);continue;}}}         }close(sockfd);return 0;
}

  • 特点
    • 需维护 fd 集合,每次 select 后需重新添加 fd(因内核会修改集合)。
    • fd 数量受限(受系统默认限制,如 1024 ),高并发场景可能不够用。
(2)poll/epoll 
  • poll:与 select 类似,通过 struct pollfd 数组传递监测的 fd 及事件,解决 select 中 fd 数量受限问题,但本质仍需遍历 fd 判断事件,高并发下效率一般。
  • epoll:Linux 下高效的 IO 多路复用机制,通过 红黑树 维护监测的 fd回调机制 通知事件,无需遍历所有 fd ,高并发场景(如大量客户端连接)效率远高于 select/poll 。

三、总结

TCP 并发服务器构建有多种模式:

  • 多进程模型资源开销大但安全;
  • 多线程模型轻量但需注意同步;
  • 线程池模型优化线程资源管理,适配高频任务场景;
  • IO 多路复用(select/poll/epoll )让单进程实现多 fd 监测,高效处理并发连接。
http://www.dtcms.com/a/353390.html

相关文章:

  • redis---set详解
  • Tortoisegit配置ssh教程
  • Vue3 新特性 defineModel 全面解析:让 v-model 写法更优雅
  • 项目智能家居---OrangePi全志H616
  • GitHub 宕机自救指南:保障开发工作连续性
  • 蓝桥杯算法之基础知识(3)——Python的idle的快捷键设置(idle改键)
  • 信任,AI+或人机环境系统智能的纽带
  • 深入解析EDCA通道与参数配置:优化Wi-Fi服务质量的关键策略
  • 新手向:网络编程完全指南
  • Jetson 分区知识全解与 OTA 升级实战
  • Containerd 安装与配置指南
  • 如何验证二叉搜索树:两种高效方法详解
  • 光伏设计平台:按组件数量铺设光伏板,精准控制投资成本
  • 推荐系统王树森(四)特征交叉+行为序列
  • 智能体前沿-主动信息获取理论基础
  • 汇川SV660A 伺服EMC电源滤波的安装要求及使用方法
  • Swift 解法详解 LeetCode 364:嵌套列表加权和 II
  • 【ConcurrentHashMap】实现原理和HashMap、Redis哈希的区别
  • 【Linux网络】网络基础
  • 如何高效地学习:从“死记硬背”到“内化创新”
  • 第二章从事件驱动到信号
  • ESP32使用场景及大规模物联网IoT
  • 【高级机器学习】3. Convex Optimisation
  • 海康相机的 HB 模式功能详解
  • 深入解析 OpenGL 着色器:顶点着色器与片段着色器
  • 无人驾驶叉车的核心作用:技术赋能下如何重塑工业物流的运作逻辑
  • Chrome插件学习笔记(四)
  • 豆包分析linux top
  • 李飞飞谈 AI 世界模型:技术内涵与应用前景
  • 深度学习——卷积神经网络CNN(原理:基本结构流程、卷积层、池化层、全连接层等)