#TCP 并发;#多进程;#多线程;#线程池;#IO 多路复用;#select;#epoll
前言
TCP 协议是面向连接的流式传输协议,基础单循环 TCP 服务器逻辑简单:socket→bind→listen→accept→收发数据→close,但是accept之后同一时间只能处理一个客户端连接,当第二个客户端接入时必须等待上一个客户端断开连接,服务能力严重受限。
为实现一台服务器同时和多个客户端通信,需要构建TCP 并发服务器。UDP 本身基于无连接,recvfrom 可以直接接收任意客户端报文,天然具备并发能力;TCP 需要额外并发模型来处理多路连接。常见 4 种实现方式:多进程模型、多线程模型、线程池模型、IO 多路复用模型。
1 单循环 TCP 服务器(非并发)
单循环服务器:服务器处理完一个客户端全部通信,断开之后,才会再次调用accept接收下一个客户端。
- 缺点:无法同时服务多个客户端;一个客户端长时间占用连接,其他客户端全部阻塞等待。
- 适用场景:设备连接数量极少的简单场景。
2 多进程并发服务器
2.1 实现思路
伪代码示例
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);
}
}
优缺点
✅优点:进程之间资源完全隔离,一个客户端异常崩溃不会影响其他客户端;稳定性高。 ❌缺点:
3 多线程并发服务器
3.1 实现思路
伪代码示例
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);
参数说明:
返回值:就绪事件 fd 的个数;超时返回 0;出错返回‑1。
select 编程逻辑
5.3 poll
select 存在文件描述符数量上限限制,poll 优化该问题,使用数组保存待监控 fd,原理和 select 类似。
5.4 epoll
Linux 独有 IO 多路复用,性能最优,适合高并发大量连接场景。 工作流程:
IO 多路复用优缺点
✅优点:无需大量创建进程线程,内存占用低,单机可以支撑上万并发连接; ❌缺点:编程逻辑复杂;select 有 fd 上限;epoll 属于 Linux 平台特有,可移植性差。
小结
表格
| 多进程 | 高 | 强 | 低 | 连接数少,追求稳定 |
| 多线程 | 中 | 弱 | 中 | 中等并发业务 |
| 线程池 | 中 | 弱 | 中 | 连接频繁创建销毁场景 |
| IO 多路复用 epoll | 极低 | 无 | 极高 | 高并发服务器 |
补充:UDP 通信天然并发,不需要以上并发模型;TCP 面向连接,每一个客户端对应一个 connfd,必须借助上面模型实现多客户端同时服务。




