Linux下I/O多路转接-epoll函数 原创 Linux平台 2022年4月8日 11:57 夏至未至 717 当前内容 6079 字,在路上,马上到,马上到 ### 目录 [TOC] ### 何为epoll 简单点说,是为处理大批量句柄而作了改进的poll,被公认为Linux2.6下性能最好的多路I/O就绪通知方法. ### epoll原理 epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll之会把哪个流发生了怎样的I/O事件通知我们。此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1)) ### epoll使用 #### 操作函数 ##### epoll_create #include int epoll_create(int size); 用于创建一个 epoll 的句柄(本质是文件描述符,自从linux2.6.8之后,size参数是被忽略的,用完之后,必须调用close()关闭 ##### epoll_ctl #include int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里(向红黑树)先注册要监听的事件类型,第一个参数epfd是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示。 EPOLL_CTL_ADD //注册新的fd到新的epfd中 EPOLL_CTL_MOD //修改已经注册的fd的监听事件 EPOLL_CTL_DEL //从epfd中删除一个fd struct epoll_event 的结构 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 | 表示对应的文件描述符可写 | | EPOLLPRI | 表示对应的文件描述符有紧急的数据可读 | | EPOLLERR | 表示对应的文件描述符发生错误 | | EPOLLHUP | 表示对应的文件描述符被挂起 | | EPOLLET | 将EPOLL设置为边缘触发模式 | | EPOLLONESHOT | 只监听一次事件,当监听完这次事件之后如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列中 | 比如: epoll_ctl(epollfd, EPOLL_CTL_ADD, socket, EPOLLIN);//注册缓冲区非空事件,即有数据流入 epoll_ctl(epollfd, EPOLL_CTL_DEL, socket, EPOLLOUT);//注册缓冲区非满事件,即流可以被写入 ##### epoll_wait 时间复杂度O(1) #include int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout); 收集在epoll监控的事件中已经就绪的事件 - 参数events是分配好的epoll_event结构体数组(也就是下面要说的就绪队列) - epoll将就绪事件添加到events数组中,(events不可以是空指针,内核只负责把数据复制到这个events数组中,并不会帮助我们在用户态分内存) - maxevents告诉内核这个events有多大,maxevents的值不能大于创建epoll_create()时的size - timeout是超时时间(0,表示立即返回;1,表示永久阻塞)如果函数调用成功,返回对应IO上已经准备好的文件描述符数目,如果返回0,表示已经超时,返回小于0的值表示函数失败 一个epoll模式的代码大概的样子是: while true { active_stream[] = epoll_wait(epollfd) for i in active_stream[] { read or write till } } true { active_stream[] = epoll_wait(epollfd) for i in active_stream[] { read or write till } } ### 源码实现 场景:服务器代码 #include #include #include #include #include #include #include #include #include #include #include #include void usage(const char* argv) { printf("%s:[ip][port]\n",argv); } void set_nonblock(int fd) { int fl = fcntl(fd,F_GETFL); fcntl(fd,F_SETFL,fl | O_NONBLOCK); } int startup(char* _ip,int _port) //创建一个套接字,绑定,检测服务器 { //sock //1.创建套接字 int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { perror("sock"); exit(2); } int opt = 1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); //2.填充本地 sockaddr_in 结构体(设置本地的IP地址和端口) struct sockaddr_in local; local.sin_port=htons(_port); local.sin_family=AF_INET; local.sin_addr.s_addr=inet_addr(_ip); //3.bind()绑定 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) { perror("bind"); exit(3); } //4.listen()监听 检测服务器 if(listen(sock,5)<0) { perror("listen"); exit(4); } return sock; //这样的套接字返回 } int main(int argc,char *argv[]) { if(argc!=3) //检测参数个数是否正确 { usage(argv[0]); exit(1); } int listen_sock=startup(argv[1],atoi(argv[2])); //创建一个绑定了本地 ip 和端口号的套接字描述符 //1.创建epoll int epfd = epoll_create(256); //可处理的最大句柄数256个 if(epfd < 0) { perror("epoll_create"); exit(5); } struct epoll_event _ev; //epoll结构填充 _ev.events = EPOLLIN; //初始关心事件为读 _ev.data.fd = listen_sock; //2.托管 epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&_ev); //将listen sock添加到epfd中,关心读事件 struct epoll_event revs[64]; int timeout = -1; int num = 0; int done = 0; while(!done) { //epoll_wait()相当于在检测事件 switch((num = epoll_wait(epfd,revs,64,timeout))) //返回需要处理的事件数目 64表示 事件有多大 { case 0: //返回0 ,表示监听超时 printf("timeout\n"); break; case -1: //出错 perror("epoll_wait"); break; default: //大于零 即就是返回了需要处理事件的数目 { struct sockaddr_in peer; socklen_t len = sizeof(peer); int i; for(i=0;i < num;i++) { int rsock = revs[i].data.fd; //准确获取哪个事件的描述符 if(rsock == listen_sock && (revs[i].events) && EPOLLIN) //如果是初始的 就接受,建立链接 { int new_fd = accept(listen_sock,(struct sockaddr*)&peer,&len); if(new_fd > 0) { printf("get a new client:%s:%d\n",inet_ntoa(peer.sin_addr),ntohs(peer.sin_port)); set_nonblock(new_fd); _ev.events = EPOLLIN | EPOLLET; _ev.data.fd = new_fd; epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&_ev); //二次托管 } } else // 接下来对num - 1 个事件处理 { if(revs[i].events & EPOLLIN) { char buf[1024]; ssize_t _s = read(rsock,buf,sizeof(buf)-1); if(_s > 0) { buf[_s] = '\0'; printf("client:%s\n",buf); _ev.events = EPOLLOUT | EPOLLET; _ev.data.fd = rsock; epoll_ctl(epfd,EPOLL_CTL_DEL,rsock,&_ev); //二次托管 } else if(_s == 0) //client:close { printf("client:%d close\n",rsock); epoll_ctl(epfd,EPOLL_CTL_DEL,rsock,NULL); close(rsock); } else { perror("read"); } } else if(revs[i].events & EPOLLOUT) { const char *msg = "HTTP/1.0.200 OK\r\n\r\n李宁爱张宁!\r\n"; write(rsock,msg,strlen(msg)); epoll_ctl(epfd,EPOLL_CTL_DEL,rsock,NULL); close(rsock); } else {} } } } break; } } return 0; } 本文标题: Linux下I/O多路转接-epoll函数 本文作者: 夏至未至 发布时间: 2022年4月8日 11:57 最近更新: 2022年4月8日 14:00 原文链接: 许可协议: 署名-非商业性-禁止演绎 4.0 国际(CC BY-NC-ND 4.0) 请按协议转载并保留原文链接及作者 epoll(1) 上一个 Windows帮助文档不可用(已解决:Win7和Win10) 下一个 Linux下I/O多路转接-select函数 当前文章评论暂未开放,请移步至留言处留言。