news 2026/9/8 16:01:55

Linux网络编程-TCP并发服务器

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux网络编程-TCP并发服务器

单循环服务器:只能处理一个客户端任务的服务器。
并发服务器:可以同时处理多个客户端任务的服务器(一对多)。
UDP服务端:具备并发性能
TCP服务端:建立连接,单循环服务器。
TCP并发服务器构建方式:

  • 多进程

进程资源开销大,在有限平台下,并发量比较小。

  • 多线程

资源开销小,在相同平台下,并发量比多进程大。

  • 线程池

多线程模型在客户端建立连接时,创建线程和销毁线程有时间消耗。
生产者-消费者设计模式
主线程:生产任务
线程池:执行任务
任务队列

  • IO多路复用

在不创建新进程/线程的前提下,使用一个进程同时检测多路IO。
IO:对文件读写
sockfd

1.多进程

//socket(); //bind(); //listen(); int init_http_server()//TCP连接初始化 { int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("socket"); return -1; } struct sockaddr_in server_addr; server_addr.sin_family = AF_INET; server_addr.sin_port = htons(PORT); server_addr.sin_addr.s_addr = inet_addr(IP); if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("bind"); return -1; } if (listen(sockfd, MAX_CLIENT) < 0) { perror("listen"); return -1; } return sockfd; } //接收建立连接的客户端,并接收信息(回应) struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); while(1) { int connfd = accept(sockfd, (struct sockaddr *)&client_addr, &client_len); if(connfd < 0) { perror("accept"); continue; } printf("client ip: %s, port: %d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); pid_t pid = fork(); if (pid > 0) { } else if (0 == pid) { char buf[1024] = {0}; while (1) { recv(); send(); } }

2.多线程

void* thread_func(void* arg) { char buf[1024]; int connfd = *(int*)arg; free(arg); while(1) { memset(buf, 0, sizeof(buf)); ssize_t size = recv(connfd, buf, sizeof(buf), 0); if(size < 0) { perror("recv error"); break; } else if(size == 0) { printf("client close\n"); break; } printf("recv: %s\n", buf); strcat(buf,"--->ok"); size = send(connfd, buf, strlen(buf), 0); if(size < 0) { perror("send error"); close(connfd); break; } } return NULL; } //while里 int *pconnfd = malloc(sizeof(int)); *pconnfd = connfd; pthread_create(&tid,NULL,thread_func,pconnfd); pthread_detach(tid);

3.IO多路复用

IO多路复用实现方式:
1. 创建文件描述符集合
2. 将关注的文件描述符添加到集合
3. 将文件描述符传递给内核,内核开始检测IO事件
4. 内核返回到达事件的结果

1.select

select 是 IO 多路复用中最早出现的实现方式,虽然它解决了单进程同时监测多路 IO 的问题,但在实际使用中仍存在以下明显的性能瓶颈和功能限制:

缺点:

1. 使用数组(位图)保存文件描述符集合,最多允许监测1024个文件描述符;
2. 将集合表创建在应用层,存在应用层和内核层数据的反复拷贝;
3. 事件到达时,内核返回整个集合表,应用层需要遍历;
4. select只能工作在水平触发模式(低速模式),不能工作在边沿触发模式(高速模式);

1. 创建文件描述符集合(位图(数组)--》1024(文件描述符1024个,0-1023)
fd_set fds;
FD_ZERO(&fds);
2. 将要监测的文件描述符加入集合
FD_SET(fd, &fds);
3. 将集合传递给内核开始监测IO事件,并且返回监测的结果
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
功能:将文件描述符传递给内核开始监测IO事件,并返回监测到的结果
参数:
nfds:监测的文件描述符最大值+1
readfds:要监测的读事件的文件描述符集合
writefds:要监测的写事件的文件描述符集合
exceptfds:要监测的出错事件的文件描述符集合
timeout:超时时间
NULL :不设置超时时间:阻塞等到
返回值:
成功:实际到达的IO事件的个数
失败:-1
0 :设置超时事件,超时时间到没有事件到达
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);

fd_set set; fd_set tmp; FD_ZERO(&set); FD_SET(fd,&set); FD_SET(0,&set); int maxfd = fd > 0 ? fd : 0; while(1) { tmp = set; int cnt = select(maxfd+1, &amp;tmp, NULL, NULL, NULL); if(cnt &lt; 0) { perror("select error"); return -1; } if(FD_ISSET(fd,&amp;tmp)) { memset(buf,0,sizeof(buf)); ssize_t size = read(fd,buf,sizeof(buf)); if(size &lt; 0) { perror("read error"); FD_CLR(fd,&amp;set); close(fd); } else if(size == 0)//已经进入了if循环里面说明管道有数据,但是size返回是0,说明管道写端被关闭 { printf("pipe closed\n"); FD_CLR(fd,&amp;set); close(fd); } printf("FIFO: %s\n",buf); } if(FD_ISSET(0,&amp;tmp)) { fgets(buf,sizeof(buf),stdin); printf("read from stdin: %s\n",buf); } } //1.如果写端进程执行了 close(fd),哪怕此时缓冲区是空的,内核也会把该 fd 标记为“可读”,目的是让你去“收尸”。此时 select 会立即返回,触发 FD_ISSET,你执行 read,读到 0,得知对方已断开 //2.select 检查管道 fd 发现:缓冲区为空,但写端还在。内核会把这个 fd 标记为“不可读”,于是 select 会直接阻塞(睡觉),并不会触发 FD_ISSET(fd, &tmp)。

当在终端输入数据结束后按下回车键,此时内核会将位图0号位置1,再继续向后遍历(一共遍历maxfd+1次),若管道有数据则将管道对应的文件描述符置1,遍历结束,内核将位图返回给应用层,通过FD_ISSET进行业务处理。

2.poll

特点:

1. 使用链表保存文件描述符集合,允许监测的文件描述符没有上限限制;
2. 创建的文件描述符集合在应用层,需要应用层和内核层的反复数据拷贝;
3. 应用层对于返回的事件结果需要遍历查找;
4. poll只能工作在水平触发模式(低速模式,高电平时),无法工作在边沿触发模式(高速模式,电平变化时)。

3.epoll

特点:
1. 使用树形结构(红黑树)保存文件描述符集合,没有文件描述符上限限制,并且查找效率高;
2. 文件描述符集合创建在内核层,避免了应用层和内核层的反复数据拷贝;
3. 直接返回到达事件的文件描述符集合;
4. epoll可以工作在水平触发模式(低速模式),也可以工作在边沿触发模式(高速模式);

1000并发量以内select与epoll性能差不多,epoll机制比select更复杂一些

1. 创建文件描述符集合

int epoll_create(int size);
功能:创建epoll的文件描述符集合
参数:
size :允许监测的文件描述符的个数
返回值:
成功:返回文件描述符句柄。
失败:-1

int epfds = epoll_create(MAX_FD_CNT); if(epfds < 0) { perror("epoll_create error"); return -1; }

2. 添加文件描述符到集合(内核创建的红黑树里

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:对文件描述符集合中的文件描述符做操作
参数:
epfd:epoll创建的文件描述符集合
op:操作方法:
EPOLL_CTL_ADD :添加操作
EPOLL_CTL_MOD :修改操作
EPOLL_CTL_DEL :删除操作
fd :要操作的文件描述符
event:epoll事件结构体
typedef union epoll_data {
void *ptr;
int fd; //要监测的文件描述符
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events:事件类型
EPOLLIN: 读事件
EPOLLOUT :写事件
返回值:
成功:0
失败:-1

void add_epoll_fd(int epfd, int fd, int events) { struct epoll_event ev; ev.data.fd = fd; ev.events = events; int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev); if(ret < 0) { perror("epoll_ctl error"); return; } } void del_epoll(int epfd,int fd) { int ret = epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL); if(ret < 0) { perror("epoll_ctl error"); return; } return; }

3. 通知内核开始监测事件

int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
功能:通知内核开始监测事件
参数:
epfd:监测的文件描述符句柄(集合
events :保存epoll返回的到达事件的集合
maxevents:最多监测的事件个数
timeout:设置超时时间
-1 :不设置超时,一直阻塞等待
返回值:
成功:实际到达的事件的个数
失败:-1
0 :超时时间到达,却没有事件返回时

int epfds = epoll_create(MAX_FD_CNT); struct epoll_event events[MAX_FD_CNT]; int cnt = epoll_wait(epfds, events, MAX_FD_CNT, -1); if(cnt < 0) { perror("epoll_wait error"); return -1; }

  • select每次都要重新传参:每次调用select,内核都必须把用户态传进来的 fd 集合全部遍历一遍(线性扫描),还要重新构建内部数据结构。

  • epoll一次注册,永久有效epoll_ctlfd的红黑树建在内核里,epoll_wait只需要直接去查看就绪链表(rdllist)是否为空就行了。如果没有事件,进程阻塞;如果有,内核直接把就绪链表里的数据拷贝到用户态数组里。

sockfd (监听) → 注册 EPOLLIN → 客户端连接到达 → 全连接队列非空 → epoll 触发 sockfd 可读
→ 循环检测到 evs[i].data.fd == sockfd → 调用 accept → 得到 connfd → 注册 connfd 的 EPOLLIN
→ 后续客户端消息会触发 connfd 的可读事件,由其他分支处理 recv

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/8 16:00:52

2026苏州代理记账公司深度解析:靠谱优质机构服务对比与选择指南

苏州中小微企业财税服务市场观察记账报税是企业经营中的基础刚需&#xff0c;对初创企业和中小微机构来说尤其如此。苏州市场主体数量众多&#xff0c;制造业、服务业、科技类企业发展活跃&#xff0c;每年新增大量创业公司&#xff0c;受人员成本和专业能力限制&#xff0c;多…

作者头像 李华
网站建设 2026/9/8 16:00:37

用Qwen3.8-Max搭建电商商品资料智能体检助手

上个月朋友找我帮忙审核一批准备上架的电商资料&#xff0c;六个文档加一张商品主图&#xff0c;按他们运营的说法&#xff0c;人工过一遍至少要半小时&#xff0c;还总担心漏掉细节。我实在不想对着Excel和Word来回切着看&#xff0c;干脆用Qwen3.8-Max搭了一个"商品资料…

作者头像 李华
网站建设 2026/9/8 16:00:31

开源终端AI编程代理opencode:从安装配置到高级玩法全攻略

1. 写在前面&#xff1a;这个叫opencode的命令行工具&#xff0c;正在重新定义“写代码”最近在技术社区里频繁刷到“opencode”这个词&#xff0c;热度飙得非常快。如果你和我一样是那种每天要在终端里泡好几个小时的老开发&#xff0c;可能已经感觉到&#xff0c;继Claude Co…

作者头像 李华
网站建设 2026/9/8 15:59:52

科研绘图新思维:用视觉语法打造会说话的数据图

1. 从“配图”到“叙事”&#xff1a;科研绘图的角色早已变了先说个现状&#xff1a;今天你在任何一本主流期刊里随便翻开一篇文章&#xff0c;最先抓住你的&#xff0c;大概率不是标题也不是摘要&#xff0c;而是图表。图表承担的工作已经从“把数据摆出来”升级为“把结论讲出…

作者头像 李华
网站建设 2026/9/8 15:59:31

从零开发微信小程序:技术选型、常见坑与上线全流程解析

做了这么多年小程序相关的工作&#xff0c;几乎每周都能遇到新人问同一个问题&#xff1a;“我自己也想搞一个小程序&#xff0c;到底怎么从零开始&#xff1f;”问的人里有学生、有做电商的运营、有传统软件工程师&#xff0c;也有纯粹想接单赚钱的个人开发者。你会发现一个问…

作者头像 李华