linux 多路复用---epoll
rbr(红黑树):需要检测的文件描述符
rdlist:有数据传递的文件描述符
epoll API:
/* #include <sys/epoll.h> //创建一个新的 epoll 实例,在内核中创建了一个数据,这个数据中有两个比较重要的数据, 一个是需要检测的文件描述符的信息(红黑树),还有一个是就绪列表,存放检测到数据发生改变的文件描述符的信息(双向链表) int epoll_create(int size); - 参数: size: 目前没有意义了,随便写一个数, 必须要大于0 - 返回值: -1: 调用失败 >0: 文件描述符,操作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 }; 常见的epoll检测事件: - EPOLLIN - EPOLLOUT - EPOLLERR //对 epoll 实例进行管理, 添加文件描述符信息,删除信息, 修改信息(读-读写) int epoll_ctl(int epfd, int op, struct eopll_event *event); - 参数: - epfd: epoll实例对应的文件描述符 - op: 要进行什么操作 EPOLL_CTL_ADD: 添加 EPOLL_CTL_MOD: 修改 EPOLL_CTL_DEL: 删除 - fd: 要检测的文件描述符 - event: 检测文件描述符什么事情(读取、写入) //检测函数 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); - 参数: - epfd: epol实例对应的文件描述符 - events: 传出参数,保存了发生变化的文件描述符的信息 - maxevents: 第二个参数结构体数组的大小 - timeout: 阻塞事件 - 0: 不阻塞 - -1: 阻塞,直到检测到fd数据发生变化,解除阻塞 - >0: 阻塞的时长(毫秒) - 返回值: - 成功: 返回发送变化的文件描述符个数 > 0 - 失败: -1 */
epoll代码编写:
1 #include <stdio.h> 2 #include <arpa/inet.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <sys/epoll.h> 7 8 int main() 9 { 10 //创建 socket 11 int lfd = socket(PF_INET, SOCK_STREAM, 0); 12 struct sockaddr_in saddr; 13 saddr.sin_port = htons(9999); 14 saddr.sin_family = AF_INET; 15 saddr.sin_addr.s_addr = INADDR_ANY; 16 //绑定 17 bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); 18 //监听 19 listen(lfd, 8); 20 //调用epoll_create()创建一个epoll实例 21 int epfd = epoll_create(10); 22 //将监听的文件描述符相关的检测信息添加到 epoll实例中 23 struct epoll_event epev; 24 epev.events = EPOLLIN; 25 epev.data.fd = lfd; 26 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev); 27 struct epoll_event epevs[1024]; 28 while(1) 29 { 30 int ret = epoll_wait(epfd, epevs, 1024, -1);//检测 31 if(ret == -1) 32 { 33 perror("epoll_wait"); 34 exit(-1); 35 } 36 printf("ret = %d\n",ret); 37 //将客户端sleep改为小的睡眠值,否则返回为1 -> 同一时刻连接一个客户端 38 for(int i = 0; i < ret; i++) 39 { 40 int curfd = epevs[i].data.fd; 41 if(curfd == lfd) 42 { 43 //监听的文件描述符有数据到达,有客户端连接 44 struct sockaddr_in cliaddr; 45 int len = sizeof(cliaddr); 46 int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); 47 //epev.events = EPOLLIN; 48 epev.events = EPOLLIN | EPOLLOUT;/// 49 epev.data.fd = cfd; 50 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); 51 } 52 else 53 { 54 if(epevs[i].events & EPOLLOUT)///不同情况 需要不同处理 55 { 56 continue;/// 57 } 58 //有数据到达,需要通信 59 char buf[1024] = {0}; 60 int len = read(curfd, buf, sizeof(buf)); 61 if(len == -1) 62 { 63 perror("read"); 64 exit(-1); 65 } 66 else if(len == 0) 67 { 68 printf("client closed...\n"); 69 epoll_ctl(epfd, EPOLL_CTL_DEL, curfd, NULL); 70 close(curfd); 71 } 72 else if(len > 0) 73 { 74 printf("read buf = %s\n",buf); 75 write(curfd, buf, strlen(buf) + 1); 76 } 77 } 78 } 79 } 80 close(lfd); 81 close(epfd); 82 return 0; 83 }
epoll的两种工作模式:
LT模式(水平触发):
假设委托内核检测读事件 -> 检测fd的读取缓冲区:
读缓冲区有数据 -> epoll检测到了会给用户通知
a. 用户不读数据,数据一直在缓冲区,epoll会一直通知
b. 用户只读了一部分数据,epoll会通知
c. 缓冲区的数据读完了,不通知
LT(Level - triggered)是缺省(默认)的工作方式,并且同时支持 block 和 no-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你的。
ET模式(边沿触发):
假设委托内核检测读事件 -> 检测fd的读取缓冲区:
读缓冲区有数据 -> epoll检测到了会给用户通知
a. 用户不读数据,数据一直在缓冲区,epoll下次检测的时候就不通知了
b. 用户只读了一部分数据,epoll不通知
c. 缓冲区的数据读完了,不通知
ET(edge - triggered)是高速工作方式,只支持 no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过 epoll 告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞的套接口,以避免由于一个文件句柄(文件描述符)的 阻塞读/阻塞写 操作把处理多个文件描述符的任务饿死。
struct epoll_event { uint32_t events; //Epoll events epoll_data_t dat; //User data variable }; 常见的Epoll检测事件: - EPOLLIN - EPOLLOUT - EPOLLERR - EPOLLET
Epoll_et:
1 #include <stdio.h> 2 #include <arpa/inet.h> 3 #include <unistd.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <fcntl.h> 7 #include <sys/epoll.h> 8 #include <errno.h> 9 10 int main() 11 { 12 //创建 socket 13 int lfd = socket(PF_INET, SOCK_STREAM, 0); 14 struct sockaddr_in saddr; 15 saddr.sin_port = htons(9999); 16 saddr.sin_family = AF_INET; 17 saddr.sin_addr.s_addr = INADDR_ANY; 18 //绑定 19 bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr)); 20 //监听 21 listen(lfd, 8); 22 //调用epoll_create()创建一个epoll实例 23 int epfd = epoll_create(100); 24 //将监听的文件描述符相关的检测信息添加到 epoll实例中 25 struct epoll_event epev; 26 epev.events = EPOLLIN; 27 epev.data.fd = lfd; 28 epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &epev); 29 struct epoll_event epevs[1024]; 30 while(1) 31 { 32 int ret = epoll_wait(epfd, epevs, 1024, -1);//检测 33 if(ret == -1) 34 { 35 perror("epoll_wait"); 36 exit(-1); 37 } 38 printf("ret = %d\n", ret); 39 //将客户端sleep改为小的睡眠值,否则返回为1 -> 同一时刻连接一个客户端 40 for(int i = 0; i < ret; i++) 41 { 42 int curfd = epevs[i].data.fd; 43 if(curfd == lfd) 44 { 45 //监听的文件描述符有数据到达,有客户端连接 46 struct sockaddr_in cliaddr; 47 int len = sizeof(cliaddr); 48 int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len); 49 50 //设置cfd属性非阻塞 51 int flag = fcntl(cfd, F_GETFL); 52 //flag = flag | O_NONBLOCK; 53 flag | O_NONBLOCK; 54 fcntl(cfd, F_SETFD, flag); 55 56 epev.events = EPOLLIN | EPOLLET;//ET模式 设置边沿触发 57 epev.data.fd = cfd; 58 epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &epev); 59 } 60 else 61 { 62 if(epevs[i].events & EPOLLOUT)///不同情况 需要不同处理 63 { 64 continue;/// 65 } 66 //有数据到达,循环读取所有数据 67 int len = 0; 68 char buf[5]; 69 while((len = read(curfd, buf, sizeof(buf))) > 0) 70 { 71 //打印数据 72 //printf("rev data = %s\n", buf); 73 write(STDOUT_FILENO, buf, len); 74 write(curfd, buf, len); 75 } 76 if(len == 0) 77 { 78 printf("client closed...\n"); 79 } 80 else if(len == -1) 81 { 82 if(errno == EAGAIN) 83 { 84 printf("data over..."); 85 } 86 else 87 { 88 perror("read"); 89 exit(-1); 90 } 91 } 92 } 93 } 94 } 95 close(lfd); 96 close(epfd); 97 return 0; 98 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)