news 2026/7/3 9:32:48

IO多路复用技术

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
IO多路复用技术

1.select

思路:告诉内核监听这些fd --- 时间延迟为NULL 一直阻塞 --- 有事情发生,返回ret即表示有多少fd发生事件 --- 首先检查监听fd是否发生,若接入则然后通过遍历利用FD_ISSET检测哪些fd发生事件

实现客服端的代码

#include <sys/select.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1) { perror("BIND"); exit(-1); } //listen ret=listen(lfd,128); if(ret==-1) { perror("listen"); exit(-1); } //IO多路复用 fd_set rdset,tmp; FD_ZERO(&rdset); //全部设置为0 FD_SET(lfd,&rdset); //把监听到的字符设置为1 int maxfd=lfd; while(1) { tmp=rdset; //调用select让内核检测哪些fd有数据 ret=select(maxfd+1,&tmp,NULL,NULL,NULL); if(ret==-1) { perror("select"); exit(-1); } else if(ret==0) { continue; } else if(ret>0) { //说明数据发生了改变 if(FD_ISSET(lfd,&tmp)) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); //将新的fd加入即合理 FD_SET(cfd,&rdset); //更新最大的文件描述符 maxfd=maxfd>cfd?maxfd:cfd; } for(int i=lfd+1;i<=maxfd;i++) { if(FD_ISSET(i,&tmp)) { //说明其对应的客户端发来了数据 char buf[1024]={0}; int len=read(i,buf,sizeof(buf)); if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); close(i); FD_CLR(i,&rdset); //清空 } else if(len>0) { printf("recv data : %s\n", buf); write(i,buf,strlen(buf)+1); } } } } } return 0; }

缺点:用户区和内核区需要来回拷贝;每次调用select需要遍历所有fd;支持的数量太少,仅1024比特;每次需要重置。

2.poll——解决了select支持数量有限且需要重置的问题

思路:封装一个结构体数组,先把fd都初始化为-1表示空闲,检测事件也进行初始化 --- 调用poll让内核检查哪些fd发生事件,返回的ret值表示几个fd改变 --- 然后先检查lfd是否发生事件,发生则表示有客服端接入,遍历找到数组里空闲的文件描述符赋值监听fd --- 遍历结构体数组中的fd,若发生事件则进行通信。

实现客服端的代码

#include <poll.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); //listen ret=listen(lfd,128); //初始化结构体2 struct pollfd fds[1024]; for(int i=0;i<1024;i++) { fds[i].fd=-1; fds[i].events=POLLIN; } fds[0].fd=lfd; int nfds=0; while(1) { //调用poll让内核检测哪些fd有数据 ret=poll(fds,nfds+1,-1); if(ret==-1) { perror("select"); exit(-1); } else if(ret==0) { continue; } else if(ret>0) { //说明数据发生了改变 if(fds[0].revents&POLLIN) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); //将新的fd加入即合理,从头遍历一遍看哪个位置的文件描述符不可用 for(int i=1;i<1024;i++) { if(fds[i].fd==-1) { fds[i].fd=cfd; fds[i].events=POLLIN; break; } } //更新最大的文件描述符索引 nfds=nfds>cfd?nfds:cfd; } for(int i= 1;i<=nfds;i++) { if(fds[i].revents&POLLIN) { //说明其对应的客户端发来了数据 char buf[1024]={0}; int len=read(fds[i].fd,buf,sizeof(buf)); if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); close(fds[i].fd); fds[i].fd=-1; //清空 } else if(len>0) { printf("recv data : %s\n", buf); write(fds[i].fd,buf,strlen(buf)+1); } } } } } return 0; }

3.epoll

思路:先利用 epoll_create()创建一个实例 --- 初始化一个结构体epoll_event,并利用 epoll_ctl 函数把监听fd加入实例中 --- 通过调用 epoll_wait 函数检测哪些fd发生事件,创建一个epoll_event结构体数组用来保存内核检测后返回的数据 --- 函数返回值表示几个fd发生事件,遍历这几个结构体数据中的fd,若监听fd改变接入客户端则epoll_ctl将其加入到最初的实例中,其他fd则进行通信

实现服务端的代码:

#include <sys/epoll.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1) { perror("BIND"); exit(-1); } //listen ret=listen(lfd,128); if(ret==-1) { perror("listen"); exit(-1); } //创建一个epoll实例 int epfd=epoll_create(100); //将监听的加入进去 struct epoll_event epev; epev.events=EPOLLIN; epev.data.fd=lfd; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev); struct epoll_event epevs[1024];//保存内核检测后返回的数据 while(1) { //调用epoll_wait让内核检测哪些fd有数据 ret=epoll_wait(epfd,epevs,1024,-1); if(ret==-1) { perror("epoll_wait"); exit(-1); } printf("ret6 id: %d\n",ret); for(int i=0;i<ret;i++) { if(epevs[i].data.fd==lfd) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); epev.events=EPOLLIN; epev.data.fd=cfd; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev); } else { if(epevs[i].events&EPOLLOUT) { continue; } //说明其对应的客户端发来了数据 char buf[1024]={0}; int len=read(epevs[i].data.fd,buf,sizeof(buf)); if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL); close(epevs[i].data.fd); } else if(len>0) { printf("recv data : %s\n", buf); write(epevs[i].data.fd,buf,strlen(buf)+1); } } } } close(lfd); close(epfd); return 0; }

两种工作模式:

LT模式(水平触发):支持阻塞和非阻塞,只要文件描述符准备就绪了,内核就会一直通知

ET模式(边沿触发):支持非阻塞,只会通知一次,需要单独设置。

#include <sys/epoll.h> #include <unistd.h> #include <stdio.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <stdlib.h> #include <fcntl.h> int main() { //创建socket int lfd=socket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_family=AF_INET; saddr.sin_addr.s_addr=INADDR_ANY; saddr.sin_port=htons(9999); int ret=bind(lfd,(struct sockaddr*)&saddr,sizeof(saddr)); if(ret==-1) { perror("BIND"); exit(-1); } //listen ret=listen(lfd,128); if(ret==-1) { perror("listen"); exit(-1); } //创建一个epoll实例 int epfd=epoll_create(100); //将监听的加入进去 struct epoll_event epev; epev.events=EPOLLIN | EPOLLET; epev.data.fd=lfd; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev); struct epoll_event epevs[1024];//保存内核检测后返回的数据 while(1) { //调用epoll_wait让内核检测哪些fd有数据 ret=epoll_wait(epfd,epevs,1024,-1); if(ret==-1) { perror("epoll_wait"); exit(-1); } printf("ret6 id: %d\n",ret); for(int i=0;i<ret;i++) { if(epevs[i].data.fd==lfd) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int len=sizeof(clientaddr); int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len); //设置为非阻塞 int flag=fcntl(cfd,F_GETFL); flag|=O_NONBLOCK; fcntl(cfd,F_SETFL,flag); epev.events=EPOLLIN; epev.data.fd=cfd; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev); } else { if(epevs[i].events&EPOLLOUT) { continue; } //循环读取全部数据 char buf[5]; int len=0; while((len=read(epevs[i].data.fd,buf,sizeof(buf)))>0) { printf("len=%d ,recv data : %s\n",len, buf); write(epevs[i].data.fd,buf,len); } if(len==-1) { perror("read"); exit(-1); } else if(len==0) { printf("client closed...\n"); epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL); close(epevs[i].data.fd); } } } } close(lfd); close(epfd); return 0; }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/2 23:41:23

思路及解答用for循环

这个问题&#xff0c;如果直接使⽤ for 循环&#xff0c;超级简单&#xff0c;重拳出击&#xff0c;时间复杂度为 O(n) 。代码如下&#xff1a; java public class Solution {public int Sum_Solution(int n) {int sum 0;for (int i 1; i < n; i) {sum i;}return sum;}…

作者头像 李华
网站建设 2026/7/2 22:17:48

抖音内容智能采集器:从零开始构建你的专属数字资源库

抖音内容智能采集器&#xff1a;从零开始构建你的专属数字资源库 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback suppor…

作者头像 李华
网站建设 2026/7/2 14:12:33

python调用大模型api来进行对话

Openai的接口调用 pip包下载 1 pip install openai 配置sk&#xff0c;url 1 2 OPENAI_API_KEY sk-xxxxx OPENAI_BASE_URL https://api.openai.com/v1 接口调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import os fr…

作者头像 李华
网站建设 2026/7/3 6:40:01

何为实体-属性-值的设计方式

EAV&#xff08;Entity-Attribute-Value&#xff09;模型&#xff0c;我们先来了解一下。 EAV 把所有业务抽象成&#xff1a; 数据结构示例&#xff0c;如下所示&#xff1a; Entity: Customer Attributes:name stringphone stringlevel enumValues:(001…

作者头像 李华
网站建设 2026/7/3 1:19:34

剑指offer-62、⼆叉搜索树的第k个结点

题⽬描述给定⼀棵⼆叉搜索树&#xff0c;请找出其中的第 k ⼩的 TreeNode 结点。示例1 输⼊&#xff1a;{5,3,7,2,4,6,8},3 返回值&#xff1a;{4}思路及解答二叉搜索树的关键性质二叉搜索树具有一个重要特性&#xff1a;中序遍历&#xff08;左-根-右&#xff09;BST会得到一个…

作者头像 李华
网站建设 2026/7/2 8:07:17

codex、ccswitch卸载干净后重装配置deepseek

1.卸载ccswitch ①若ccswitch是通过文件.msi下载的&#xff0c;则进入控制面板&#xff0c;选择程序和功能&#xff0c;点击ccswitch卸载&#xff1b; ②进入目录C:\Users\用户名\&#xff0c;删除.cc-switch 2.卸载codex ①在电脑左下角找到codex&#xff0c;右击卸载 ②…

作者头像 李华