2023年epoll学习笔记.doc





《2023年epoll学习笔记.doc》由会员分享,可在线阅读,更多相关《2023年epoll学习笔记.doc(47页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、epoll学习笔记 epoll有两种模式,Edge Triggered(简称ET) 和 Level Triggered(简称LT).在采用这两种模式时要注意的是,假如采用ET模式,那么仅当状态发生变化时才会告知,而采用LT模式类似于本来的select/poll操作,只要尚有没有解决的事件就会一直告知.以代码来说明问题:一方面给出server的代码,需要说明的是每次accept的连接,加入可读集的时候采用的都是ET模式,并且接受缓冲区是5字节的,也就是每次只接受5字节的数据:#include#include#include#include#include#include#include#incl
2、ude#includeusingnamespacestd;#defineMAXLINE5#defineOPEN_MAX100#defineLISTENQ20#defineSERV_PORT5000#defineINFTIM1000voidsetnonblocking(intsock)intopts;opts=fcntl(sock,F_GETFL);if(opts0)perror(fcntl(sock,GETFL);exit(1);opts=opts|O_NONBLOCK;if(fcntl(sock,F_SETFL,opts)0)perror(fcntl(sock,SETFL,opts);exi
3、t(1);intmain()inti,maxi,listenfd,connfd,sockfd,epfd,nfds;ssize_tn;charlineMAXLINE;socklen_tclilen;/声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要解决的事件structepoll_eventev,events20;/生成用于解决accept的epoll专用的文献描述符epfd=epoll_create(256);structsockaddr_inclientaddr;structsockaddr_inserveraddr;listenfd=socket(AF_INET,S
4、OCK_STREAM,0);/把socket设立为非阻塞方式/setnonblocking(listenfd);/设立与要解决的事件相关的文献描述符ev.data.fd=listenfd;/设立要解决的事件类型ev.events=EPOLLIN|EPOLLET;/ev.events=EPOLLIN;/注册epoll事件epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);bzero(&serveraddr,sizeof(serveraddr);serveraddr.sin_family=AF_INET;char*local_addr=127.0.0.1;inet
5、_aton(local_addr,&(serveraddr.sin_addr);/htons(SERV_PORT);serveraddr.sin_port=htons(SERV_PORT);bind(listenfd,(sockaddr*)&serveraddr,sizeof(serveraddr);listen(listenfd,LISTENQ);maxi=0;for(;)/等待epoll事件的发生nfds=epoll_wait(epfd,events,20,500);/解决所发生的所有事件for(i=0;infds;+i)if(eventsi.data.fd=listenfd)connfd
6、=accept(listenfd,(sockaddr*)&clientaddr,&clilen);if(connfd0)perror(connfd0);exit(1);/setnonblocking(connfd);char*str=inet_ntoa(clientaddr.sin_addr);coutaccaptaconnectionfromstrendl;/设立用于读操作的文献描述符ev.data.fd=connfd;/设立用于注测的读操作事件ev.events=EPOLLIN|EPOLLET;/ev.events=EPOLLIN;/注册evepoll_ctl(epfd,EPOLL_CTL
7、_ADD,connfd,&ev);elseif(eventsi.events&EPOLLIN)coutEPOLLINendl;if(sockfd=eventsi.data.fd)0)continue;if(n=read(sockfd,line,MAXLINE)0)if(errno=ECONNRESET)close(sockfd);eventsi.data.fd=-1;elsestd:coutreadlineerrorstd:endl;elseif(n=0)close(sockfd);eventsi.data.fd=-1;linen=0;coutreadlinenew($host:$port)o
8、rdiecreatesocketerror$;my$msg_out=;print$socket$msg_out;printnowsendover,gotosleepn;while(1)sleep(1);运营server和client发现,server仅仅读取了5字节的数据,而client其实发送了10字节的数据,也就是说,server仅当第一次监听到了EPOLLIN事件,由于没有读取完数据,并且采用的是ET模式,状态在此之后不发生变化,因此server再也接受不到EPOLLIN事件了.(友情提醒:上面的这个测试客户端,当你关闭它的时候会再次出发IO可读事件给server,此时server就会去
9、读取剩下的5字节数据了,但是这一事件与前面描述的ET性质并不矛盾.)假如我们把client改为这样:#!/usr/bin/perluseIO:Socket;my$host=127.0.0.1;my$port=5000;my$socket=IO:Socket:INET-new($host:$port)ordiecreatesocketerror$;my$msg_out=;print$socket$msg_out;printnowsendover,gotosleepn;sleep(5);print5secondgonesendanotherlinen;print$socket$msg_out;wh
10、ile(1)sleep(1);可以发现,在server接受完5字节的数据之后一直监听不到client的事件,而当client休眠5秒之后重新发送数据,server再次监听到了变化,只但是由于只是读取了5个字节,仍然有10个字节的数据(client第二次发送的数据)没有接受完.假如上面的实验中,对accept的socket都采用的是LT模式,那么只要尚有数据留在buffer中,server就会继续得到告知,读者可以自行改动代码进行实验.基于这两个实验,可以得出这样的结论:ET模式仅当状态发生变化的时候才获得告知,这里所谓的状态的变化并不涉及缓冲区中尚有未解决的数据,也就是说,假如要采用ET模式,
11、需要一直read/write直到犯错为止,很多人反映为什么采用ET模式只接受了一部分数据就再也得不到告知了,大多由于这样;而LT模式是只要有数据没有解决就会一直告知下去的.补充说明一下这里一直强调的状态变化是什么:1)对于监听可读事件时,假如是socket是监听socket,那么当有新的积极连接到来为状态发生变化;对一般的socket而言,协议栈中相应的缓冲区有新的数据为状态发生变化.但是,假如在一个时间同时接受了N个连接(N1),但是监听socket只accept了一个连接,那么其它未 accept的连接将不会在ET模式下给监听socket发出告知,此时状态不发生变化;对于一般的socket
12、,就如例子中而言,假如相应的缓冲区自身已有了N字节的数据,而只取出了小于N字节的数据,那么残存的数据不会导致状态发生变化.2)对于监听可写事件时,同理可推,不再详述.而不管是监听可读还是可写,对方关闭socket连接都将导致状态发生变化,比如在例子中,假如强行中断client脚本,也就是积极中断了socket连接,那么都将导致server端发生状态的变化,从而server得到告知,将已经在本方缓冲区中的数据读出.把前面的描述可以总结如下:仅当对方的动作(发出数据,关闭连接等)导致的事件才干导致状态发生变化,而本方协议栈中已经解决的事件(涉及接受了对方的数据,接受了对方的积极连接请求)并不是导致
13、状态发生变化的必要条件,状态变化一定是对方导致的.所以在ET模式下的,必须一直解决到犯错或者完全解决完毕,才干进行下一个动作,否则也许会发生错误.此外,从这个例子中,也可以阐述一些基本的网络编程概念.一方面,连接的两端中,一端发送成功并不代表着对方上层应用程序接受成功, 就拿上面的client测试程序来说,10字节的数据已经发送成功,但是上层的server并没有调用read读取数据,因此发送成功仅仅说明了数据被对方的协议栈接受存放在了相应的buffer中,而上层的应用程序是否接受了这部分数据不得而知;同样的,读取数据时也只代表着本方协议栈的相应buffer中有数据可读,而此时时候在对端是否在发
14、送数据也不得而知.epoll精髓在linux的网络编程中,很长的时间都在使用select来做事件触发。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而减少效率。由于在内核中的select实现中,它是采用轮询来解决的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文献有这样的声明:#define _FD_SETSIZE 1024表达select最多同时监听1024个fd,当然,可以通过修改头文献再重编译内核来扩大这个数目,但这似乎并不治本。epoll的接口非常简朴,一共就三
15、个函数:1. int epoll_create(int size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下假如查看/proc/进程id/fd/,是可以看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则也许导致fd被耗尽。2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);epoll的事件注册函数,它不
16、同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表达动作,用三个宏来表达:EPOLL_CTL_ADD:注册新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:struct epoll_event _uint32_t events; /* Epoll events */ epoll_data_t d
17、ata; /* User data variable */;events可以是以下几个宏的集合:EPOLLIN :表达相应的文献描述符可以读(涉及对端SOCKET正常关闭);EPOLLOUT:表达相应的文献描述符可以写;EPOLLPRI:表达相应的文献描述符有紧急的数据可读(这里应当表达有带外数据到来);EPOLLERR:表达相应的文献描述符发生错误;EPOLLHUP:表达相应的文献描述符被挂断;EPOLLET: 将EPOLL设为边沿触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。EPOLLONESHOT:只监听一次事件,当监听完这次事件之
18、后,假如还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不拟定,也有说法说是永久阻塞)。该函数返回需要解决的事件数目,如返
19、回0表达已超时。-从man手册中,得到ET和LT的具体描述如下EPOLL事件有两种模型:Edge Triggered (ET)Level Triggered (LT)假如有这样一个例子:1. 我们已经把一个用来从管道中读取数据的文献句柄(RFD)添加到epoll描述符2. 这个时候从管道的另一端被写入了2KB的数据3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作4. 然后我们读取了1KB的数据5. 调用epoll_wait(2).Edge Triggered 工作模式:假如我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 2023 epoll 学习 笔记

限制150内