IO多路复用技术

📅 2026/7/1 2:51:21
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 lfdsocket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_familyAF_INET; saddr.sin_addr.s_addrINADDR_ANY; saddr.sin_porthtons(9999); int retbind(lfd,(struct sockaddr*)saddr,sizeof(saddr)); if(ret-1) { perror(BIND); exit(-1); } //listen retlisten(lfd,128); if(ret-1) { perror(listen); exit(-1); } //IO多路复用 fd_set rdset,tmp; FD_ZERO(rdset); //全部设置为0 FD_SET(lfd,rdset); //把监听到的字符设置为1 int maxfdlfd; while(1) { tmprdset; //调用select让内核检测哪些fd有数据 retselect(maxfd1,tmp,NULL,NULL,NULL); if(ret-1) { perror(select); exit(-1); } else if(ret0) { continue; } else if(ret0) { //说明数据发生了改变 if(FD_ISSET(lfd,tmp)) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int lensizeof(clientaddr); int cfdaccept(lfd,(struct sockaddr*)clientaddr,len); //将新的fd加入即合理 FD_SET(cfd,rdset); //更新最大的文件描述符 maxfdmaxfdcfd?maxfd:cfd; } for(int ilfd1;imaxfd;i) { if(FD_ISSET(i,tmp)) { //说明其对应的客户端发来了数据 char buf[1024]{0}; int lenread(i,buf,sizeof(buf)); if(len-1) { perror(read); exit(-1); } else if(len0) { printf(client closed...\n); close(i); FD_CLR(i,rdset); //清空 } else if(len0) { 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 lfdsocket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_familyAF_INET; saddr.sin_addr.s_addrINADDR_ANY; saddr.sin_porthtons(9999); int retbind(lfd,(struct sockaddr*)saddr,sizeof(saddr)); //listen retlisten(lfd,128); //初始化结构体2 struct pollfd fds[1024]; for(int i0;i1024;i) { fds[i].fd-1; fds[i].eventsPOLLIN; } fds[0].fdlfd; int nfds0; while(1) { //调用poll让内核检测哪些fd有数据 retpoll(fds,nfds1,-1); if(ret-1) { perror(select); exit(-1); } else if(ret0) { continue; } else if(ret0) { //说明数据发生了改变 if(fds[0].reventsPOLLIN) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int lensizeof(clientaddr); int cfdaccept(lfd,(struct sockaddr*)clientaddr,len); //将新的fd加入即合理,从头遍历一遍看哪个位置的文件描述符不可用 for(int i1;i1024;i) { if(fds[i].fd-1) { fds[i].fdcfd; fds[i].eventsPOLLIN; break; } } //更新最大的文件描述符索引 nfdsnfdscfd?nfds:cfd; } for(int i 1;infds;i) { if(fds[i].reventsPOLLIN) { //说明其对应的客户端发来了数据 char buf[1024]{0}; int lenread(fds[i].fd,buf,sizeof(buf)); if(len-1) { perror(read); exit(-1); } else if(len0) { printf(client closed...\n); close(fds[i].fd); fds[i].fd-1; //清空 } else if(len0) { 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 lfdsocket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_familyAF_INET; saddr.sin_addr.s_addrINADDR_ANY; saddr.sin_porthtons(9999); int retbind(lfd,(struct sockaddr*)saddr,sizeof(saddr)); if(ret-1) { perror(BIND); exit(-1); } //listen retlisten(lfd,128); if(ret-1) { perror(listen); exit(-1); } //创建一个epoll实例 int epfdepoll_create(100); //将监听的加入进去 struct epoll_event epev; epev.eventsEPOLLIN; epev.data.fdlfd; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,epev); struct epoll_event epevs[1024];//保存内核检测后返回的数据 while(1) { //调用epoll_wait让内核检测哪些fd有数据 retepoll_wait(epfd,epevs,1024,-1); if(ret-1) { perror(epoll_wait); exit(-1); } printf(ret6 id: %d\n,ret); for(int i0;iret;i) { if(epevs[i].data.fdlfd) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int lensizeof(clientaddr); int cfdaccept(lfd,(struct sockaddr*)clientaddr,len); epev.eventsEPOLLIN; epev.data.fdcfd; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,epev); } else { if(epevs[i].eventsEPOLLOUT) { continue; } //说明其对应的客户端发来了数据 char buf[1024]{0}; int lenread(epevs[i].data.fd,buf,sizeof(buf)); if(len-1) { perror(read); exit(-1); } else if(len0) { printf(client closed...\n); epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL); close(epevs[i].data.fd); } else if(len0) { 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 lfdsocket(AF_INET,SOCK_STREAM,0); //BIND struct sockaddr_in saddr; saddr.sin_familyAF_INET; saddr.sin_addr.s_addrINADDR_ANY; saddr.sin_porthtons(9999); int retbind(lfd,(struct sockaddr*)saddr,sizeof(saddr)); if(ret-1) { perror(BIND); exit(-1); } //listen retlisten(lfd,128); if(ret-1) { perror(listen); exit(-1); } //创建一个epoll实例 int epfdepoll_create(100); //将监听的加入进去 struct epoll_event epev; epev.eventsEPOLLIN | EPOLLET; epev.data.fdlfd; epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,epev); struct epoll_event epevs[1024];//保存内核检测后返回的数据 while(1) { //调用epoll_wait让内核检测哪些fd有数据 retepoll_wait(epfd,epevs,1024,-1); if(ret-1) { perror(epoll_wait); exit(-1); } printf(ret6 id: %d\n,ret); for(int i0;iret;i) { if(epevs[i].data.fdlfd) { //表示有新的客户端链接进来 struct sockaddr_in clientaddr; int lensizeof(clientaddr); int cfdaccept(lfd,(struct sockaddr*)clientaddr,len); //设置为非阻塞 int flagfcntl(cfd,F_GETFL); flag|O_NONBLOCK; fcntl(cfd,F_SETFL,flag); epev.eventsEPOLLIN; epev.data.fdcfd; epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,epev); } else { if(epevs[i].eventsEPOLLOUT) { continue; } //循环读取全部数据 char buf[5]; int len0; while((lenread(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(len0) { 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; }