下载本文档
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
1、epoll学习笔记ep oil 学习笔记epoll 有两种模式,Edge Triggered( 简称 ET)和 Level Triggered( 简称 LT). 在采用这两种模式时要注意的是,如果采用ET模式,那么仅当状态发生变化时才 会通知,而采用LT模式类似于原来的select/poll 操作,只要还有没有处理的事 件就会一直通知.以代码来说明问题:首先给出server的代码,需要说明的是每次accept的连接,加入可读集的时候 采用的都是ET模式,而且接收缓冲区是5字节的,也就是每次只接收5字节的数 据:#include <iostream>#includevsys/sock
2、et.h>#include<sys/e poll.h>#include<netinet/in.h>#defineMAXLINE 5#defineOPEN MAX100#defineLISTENQ 20#includevarp a/inet.h>#include<fcntl.h>#includevunistd.h>#includevstdio.h>#includeverrno.h>usingnames pace std;#define SERV PORT 5000 #define INFTIM 1000 void setnonbl
3、ocking( int sock)int op ts;op ts=fcntl(sock,F_GETFL);if (opts<0)perror("fcntl(sock,GETFL)");exit (1);op ts|O_NONBLOCK;op tsFfcntl(sock,F SETFL,op ts)<0)perror("fcntl(sock,SETFL,o pts)");intmain()int i, maxi, listenfd,connfd,sockfd,e pfd, nfds;ssize_tn;char lineMAXLINE;sockl
4、en_tclilen;/声明epolLevent 结构体的变量,ev用于注册事件,数组用于回传要处理的事件structepol l_eventev,events20;/生成用于处理accept的epoll专用的文件描述符 ep fd=e pol l create(256);struct sockaddr_in clientaddr;listenfdstruct sockaddr_in serveraddr;socket(AF_INET, SOCK_STREAM,0);/把socket设置为非阻塞方式 /setnonblocking(listenfd);/设置与要处理的事件相关的文件描述符 ev
5、.data.fd=listenfd;/设置要处理的事件类型 ev.events=E POLLIN|E POLLET;/ev.events=E POLLIN;II注册epoll事件ep oll_ctl(e pfd,E POLI_ CTL_ADD,listenfd, &ev);bzero (&serveraddr,sizeof(serveraddr);serveraddr.sin_familyAF_INET;char *local_addr="127.0.0.1"inet_aton(local_addr,&(serveraddr.sin_addr);/h
6、tons(SERV_ PORT);serveraddr.sin_ port=htons(SERV_ PORT);bind(listenfd,(sockaddr*)&serveraddr,sizeof(serveraddr);LISTENQ);listen(listenfd, maxi = 0;for (;)/等待epoll事件的发生 nfds=e pol l_wait(e pfd,events,20,500);/处理所发生的所有事件 for (i=0;i<nfds;+i)if (eventsi.data.fd=listenfd)en);connfd = acce pt(liste
7、nfd,(sockaddr*)&clientaddr.& clilif (connfd<0)P error("c onn fd<0");exit ;/setnonblocking(connfd);Char *str =inet ntoa(ciientaddr.sin,addr);cout << "acca pt a connectionfrom "<< str << endl;/设置用于读操作的文件描述符ev.data.fd=connfd;/设置用于注测的读操作事件-ev.events=E
8、POLLIN|E POLLET;/ev.events=EP OLLIN; else/汪册evep oll_ctl(e pfd,EPO_ CTL_ADD,connfd, &ev);if (eventsi.events&EPOLLIN)cout << "EP OLLIN" << endl;if ( (sockfd = eventsi.data.fd)< 0)continue;if ( (n = read(sockfd,line,MAXLINE) < 0)if (errno = ECONNRESET)close(sockfd);
9、 eventsi.data.fd elsestd:cout<<"readline else if (n = 0)close(sockfd);eventsi.data.fd= -1;=-1;error"vvstd:endl;linen= '0'cout << "read "<< line <</设置用于写操作的文件描述符ev.data.fd=sockfd;/设置用于注测的写操作事件endl;elseev.events=E POLLOUT|E POLLET;/修改sockfd上要处理的事件为 E
10、POLLOUT/ep oll_ctl(e pfd,E POLI_CTI_M OD,sockfd, &ev);if (eventsi.events&EPOLLOUT)sockfd = eventsi.data.fd; write(sockfd,line,n);/设置用于读操作的文件描述符ev.data.fd=sockfd;/设置用于注测的读操作事件ev.events=E POLLIN|E POLLET;/修改sockfd上要处理的事件为 EPOLIN ep oll_ctl(e pfd,E POLI_CTI_M OD,sockfd, &ev);return 0;下面给出测试
11、所用的Perl写的client端,在client中发送10字节的数据,同 时让client在发送完数据之后进入死循环,也就是在发送完之后连接的状态不 发生改变-既不再发送数据,也不关闭连接,这样才能观察出server的状态: #!/usr/bin/perluse IO:Socket;my $hostmy $port"127.0.0.1"5000;my$socket =IO:Socket:INET->new"$host:$ port")my$msg_out ="1234567890"print$socket$msg_out;pri
12、nt"now send over, go toslee p因t;while> (1)or die "create socket error $"use IO:Socket;slee p(1);运行server和client发现,server仅仅读取了 5字节的数据,而client其实发 送了 10字节的数据,也就是说,server仅当第一次监听到了 EPOLLIN事件,由于 没有读取完数据,而且采用的是ET模式,状态在此之后不发生变化,因此server 再也接收不到EPOLLIN事件了 .(友情提示:上面的这个测试客户端,当你关闭它的时候会再次出发IO可读事
13、件 给server,此时server就会去读取剩下的5字节数据了,但是这一事件与前面描 述的ET性质并不矛盾.) 如果我们把client改为这样:#!/usr/bin/perlmy $host= "127.0.0.1"my $port= 5000;my $socket = IO:Socket:INET->new"$host:$port")or die "create socket error $"my $msg out="1234567890"p rint$socket $msg_out;On"P
14、rint"now send over, go toslee pslee p( 5);printprint"5 second gone$socket $msg_ou凶tTsendanother linen"It;_Lwhile(1)Lslee p(1);L可以发现,在server接收完5字节的数据之后一直监听不到 client的事件,而 当client休眠5秒之后重新发送数据server再次监听到了变化,只不过因为只 是读取了 5个字节,仍然有10个字节的数据(client第二次发送的数据)没有接 收完.如果上面的实验中,对accept的socket都采用的是LT模
15、式,那么只要还有数据 留在buffer中server就会继续得到通知,读者可以自行改动代码进行实验.基于这两个实验,可以得出这样的结论:ET模式仅当状态发生变化的时候才获得 通知,这里所谓的状态的变化并不包括缓冲区中还有未处理的数据,也就是说,如果要采用ET模式,需要一直read/write 直到出错为止,很多人反映为什么采 用ET模式只接收了一部分数据就再也得不到通知了,大多因为这样;而LT模式是只要有数据没有处理就会一直通知下去的.补充说明一下这里一直强调的-状态变化-是什么:1)对于监听可读事件时,如果是socket是监听socket,那么当有新的主动连接到 来为状态发生变化;对一般的s
16、ocket而言,协议栈中相应的缓冲区有新的数据为 状态发生变化.但是,如果在一个时间同时接收了 N个连接(N>1),但是监听 socket只accept 了一个连接,那么其它未accept的连接将不会在ET模式下给 监听socket发出通知,此时状态不发生变化;对于一般的socket,就如例子中而 言,如果对应的缓冲区本身已经有了N字节的数据,而只取出了小于N字节的数据,那么残存的数据不会造成状态发生变化.2)对于监听可写事件时,同理可推,不再详述. 而不论是监听可读还是可写,对方关闭socket连接都将造成状态发生变化,比如 在例子中,如果强行中断client脚本,也就是主动中断了 s
17、ocket连接,那么都将 造成server端发生状态的变化,从而server得到通知,将已经在本方缓冲区中 的数据读出.把前面的描述可以总结如下:仅当对方的动作(发出数据,关闭连接等)造成的事 件才能导致状态发生变化,而本方协议栈中已经处理的事件(包括接收了对方的 数据,接收了对方的主动连接请求)并不是造成状态发生变化的必要条件,状态 变化一定是对方造成的.所以在ET模式下的,必须一直处理到出错或者完全处理 完毕,才能进行下一个动作,否则可能会发生错误.另外,从这个例子中,也可以阐述一些基本的网络编程概念.首先,连接的两端中 一端发送成功并不代表着对方上层应用程序接收成功,就拿上面的clien
18、t测试 程序来说,10字节的数据已经发送成功,但是上层的server并没有调用read读 取数据,因此发送成功仅仅说明了数据被对方的协议栈接收存放在了相应的 buffer中,而上层的应用程序是否接收了这部分数据不得而知;同样的,读取数据时也只代表着本方协议栈的对应buffer中有数据可读,而此时时候在对端是否在发送数据也不得而知.ep oil精髓在linux的网络编程中,很长的时间都在使用select来做事件触发。在linuxep oil。新的内核中,有了一种替换它的机制,就是 相比于select ,epoll最大的好处在于它不会随着监听fd数目的增长而降低效 率。因为在内核中的select实
19、现中,它是采用轮询来处理的,轮询的 fd数目 越多,自然耗时越多。并且,在lin ux/posix_ty pes.h头文件有这样的声明:#defi ne _FD_SETSIZE1024表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内 核来扩大这个数目,但这似乎并不治本。ep oil的接口非常简单,一共就三个函数:1. int ep oll_create(i nt size);创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个 参数不同于selectO中的第一个参数,给出最大监听的fd+1的值。需要注意的 是,当创建好epoll句柄后,它就
20、是会占用一个fd值,在linux下如果查看/proc/进程id/fd/ ,是能够看到这个fd的,所以在使用完ep oil后,必须调用close() 关闭,否则可能导致fd被耗尽。2. int ep oll_ctl(i nt epfd, int op, int fd, struct ep oll_eve nt *eve nt);ep oll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是 ep oll_create()的返回值,第二个参数表示动作,用三个宏来表示:EPOLL_CTL_AD注册新的 fd 至U epf
21、d 中;EPOLL_CTL_MO修改已经注册的fd的监听事件;中删除一个fd ;fd,第四个参数是告诉内核需要监听什么事,structEPOLL_CTL_DEL从 epfd 第三个参数是需要监听的 epol l_eve nt 结构如下: struct ep oll_eve nt /* Epoll eve nts */* User data variable */_uin t32_t eve nts;epol l_data_t data;; events可以是以下几个宏的集合:EP OLLIN :表示对应的文件描述符可以读(包括对端SOCKE正常关闭);EP OLLOUT表示对应的文件描述符可以
22、写;EPOLLPRI表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数 据到来);EP OLLERR表示对应的文件描述符发生错误;EP OLLHU P表示对应的文件描述符被挂断;EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触 发(Level Triggered)来说的。EP OLLONESHO只监听一次事件,当监听完这次事件之后,如果还需要继续监 听这个socket的话,需要再次把这个socket加入到EPOLL队列里3. int epoll_wait(intepfd, structepoll_event * events, int ma
23、xevents,int timeout);等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集 合,maxevents告之内核这个 events有多大,这个 maxevents的值不能大于创 建epoll_create() 时的size,参数timeout是超时时间(毫秒,0会立即返回, -1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如 返回0表示已超时。从man手册中,得到ET和LT的具体描述如下EP OLL事件有两种模型:假如有这样一个例子:1.符2.3.4.5.Edge Triggered (ET) Level Triggered (L
24、T)我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到ep oil描述这个时候从管道的另一端被写入了2KB的数据调用epoll_wait(2),并且它会返回RFD说明它已经准备好读取操作 然后我们读取了 1KB的数据调用 epoll_wait(2)Edge Triggered 工作模式:如果我们在第1步将RFD添加到epoll描述符的时候使用了 EPOLLET标志,那 么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于 文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈 信息。只有在监视的文件句柄上发生了某个事件的时候ET工作模式才会
25、汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲 区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2) 完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必 须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。i 基于非阻塞文件句柄ii 只有当read(2)或者write(2)
26、返回EAGAIN寸才需要挂起,等待。 但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此 次事件处理完成,当read()返回的读到的数据长度小于请求的数据长度时,就 可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。Level Triggered工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的 poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即 使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定 EPOLLONESH标志,在epoll_wait(2)
27、 收到事件后epoll会与 事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOa定后, 使用带有EPOLL_CTMG标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。然后详细解释ET, LT:LT(level triggered)是缺省的工作方式,并且同时支持 block和no-blocksocket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对 这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的, 所以,这种模式编程出错误可能性要小一点。传统的select/poll 都是这种模型的代表.ET(edge-trigg
28、ered) 是高速工作方式,只支持no-block socket。在这种模式下, 当描述符从未就绪变为就绪时,内核通过ep oll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直 到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接OCK收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBL错误)。但是请注意,如果一直不对这个 fd作IO操作(从而导致它再次变成未 就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加 速效用仍需要更多的be nchmark确认(这句话不理解)。在
29、许多测试中我们会看到如果没有大量的idle -connection 或者 dead-connection , epoll的效率并不会比select/poll 高很多,但是当我们遇 到大量的idle- connection( 例如WAF环境中存在大量的慢速连接),就会发现 epoll的效率大大高于select/poll 。(未测试)另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需 再次读取:while(rs)bufle n =
30、recv(activeeve ntsi.data.fd, buf, sizeof(buf), 0); if(buflen < 0)/由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已 无数据可读/在这里就当作是该次事件已处理处.if(errno = EAGAIN)break;elsereturn;else if(bufle n = 0)/ 这里表示对端的socket已正常关闭.if(bufle n = sizeof(buf)rs = 1;/需要再次读取elsers = 0;还有,假如发送端流量大于接收端的流量(意思是ep oil所在的程序读比转发的 socket要快)
31、,由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区 的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAI错误(参考 man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返 回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno 为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间 的阻塞在socket_send()内部,但暂没有更好的办法.ssize_t socket_se nd(i nt
32、sockfd, const char* buffer, size_t bufle n) "" "ssize_t tmp;size_t total = bufle n;const char *p = buffer;while(1)tmp = sen d(sockfd, p, total, 0);if(tmp < 0)/当send收到信号时,可以继续写,但这里返回-1.if(errno = EINTR)return -1;/当socket是非阻塞时,如返回此错误,表示写缓冲队列已满, / 在这里做延时后再重试.if(errno = EAGAIN)uslee p(
33、1000); con ti nue; return -1;if(size_t)t mp = total) return bufle n;total -= tmp;P += tmp; return tmp;select来做事件触发。在linux ep oil。在linux的网络编程中,很长的时间都在使用 新的内核中,有了一种替换它的机制,就是 相比于select ,epoll最大的好处在于它不会随着监听fd数目的增长而降低效 率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的 fd数目 越多,自然耗时越多。并且,在lin ux/posix_ty pes.h头文件有这样的声明:#d
34、efi ne _FD_SETSIZE1024表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内 核来扩大这个数目,但这似乎并不治本。ep oil的接口非常简单,一共就三个函数:1. int ep oll_create(i nt size);创建一个ep oil的句柄,size用来告诉内核这个监听的数目一共有多大。这个 参数不同于selectO中的第一个参数,给出最大监听的fd+1的值。需要注意的 是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/ 进程id/fd/ ,是能够看到这个fd的,所以在使用完epoll后,必须调用clo
35、se() 关闭,否则可能导致fd被耗尽。2. int ep oll_ctl(i nt epfd, int op, int fd, struct ep oll_eve nt *eve nt);epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什 么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是 ep oll_create()的返回值,第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD注册新的 fd 至U epfd 中;EPOLL_CTL_MO修改已经注册的fd的监听事件;中删除一个fd ;fd,第四个参数是告诉内核需要监听什么事,structE
36、POLL_CTL_DEL从 epfd 第三个参数是需要监听的 epol l_eve nt 结构如下: struct ep oll_eve nt /* Epoll eve nts */* User data variable */uint32 t events;epol l_data_t data;events可以是以下几个宏的集合:EP OLLIN :表示对应的文件描述符可以读(包括对端SOCKE正常关闭);EP OLLOUT表示对应的文件描述符可以写;EPOLLPRI表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数 据到来);EP OLLERR表示对应的文件描述符发生错误;EP O
37、LLHU P表示对应的文件描述符被挂断;EPOLLET将EPOLL设为边缘触发(Edge Triggered) 模式,这是相对于水平触 发(Level Triggered) 来说的。EP OLLONESHO只监听一次事件,当监听完这次事件之后,如果还需要继续监 听这个socket的话,需要再次把这个socket加入到EPOLL队列里3. intepoll_wait(intepfd, struct epoll_event * events, intmaxevents,int timeout);等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集 合,maxevent
38、s告之内核这个 events有多大,这个 maxevents的值不能大于创 建epoll_create() 时的size,参数timeout是超时时间(毫秒,0会立即返回, -1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如 返回0表示已超时。从man手册中,得到ET和LT的具体描述如下EP OLL事件有两种模型:假如有这样一个例子:1.符2.3.4.5.Edge Triggered (ET) Level Triggered (LT)我们已经把一个用来从管道中读取数据的文件句柄 (RFD)添加到ep oil描述这个时候从管道的另一端被写入了2KB的数据调用epoll_wai
39、t(2),并且它会返回RFD说明它已经准备好读取操作 然后我们读取了 1KB的数据调用 epoll_wait(2)Edge Triggered 工作模式:如果我们在第1步将RFD添加到epoll描述符的时候使用了 EPOLLE标志,那 么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于 文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈 信息。只有在监视的文件句柄上发生了某个事件的时候ET工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲 区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第
40、2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用epol l_wait(2) 完成后,是否挂起是不确定的。ep oil工作在ET模式的时候,必 须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读 /阻塞写操作把处理多 个文件描述符的任务饿死。最好以下面的方式调用 ET模式的ep oil接口,在后 面会介绍避免可能的缺陷。i 基于非阻塞文件句柄ii 只有当read(2)或者write(2)返回EAGAIN寸才需要挂起,等待。 但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此 次事件处理完成
41、,当read()返回的读到的数据长度小于请求的数据长度时,就 可以确定此时缓冲中已没有数据了,也就可以认为此事读事件已处理完成。Level Triggered工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的 poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即 使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定 EPOLLONESH标志,在epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOa定后, 使用带有EPOLL_CTM
42、G标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。然后详细解释ET, LT:LT(level triggered)是缺省的工作方式,并且同时支持 block和no-blocksocket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对 这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的, 所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.ET(edge-triggered) 是高速工作方式,只支持no-block socket。在这种模式下, 当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后
43、它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直 到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接CK收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLO错误)。但是请注意,如果一直不对这个 fd作IO操作(从而导致它再次变成未 就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加 速效用仍需要更多的be nchmark确认(这句话不理解)。在许多测试中我们会看到如果没有大量的idle -conn ecti on或者dead-connection ,epoll的效率并不会比selec
44、t/poll 高很多,但是当我们遇 到大量的idle- connection( 例如WAh环境中存在大量的慢速连接),就会发现 epoll的效率大大高于select/poll 。(未测试)另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很 有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需 要再次读取:while(rs)bufle n = recv(activeeve ntsi.data.fd, buf, sizeof(buf), 0);if(bufle n < 0)/由
45、于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已 无数据可读/在这里就当作是该次事件已处理处.if(errno = EAGAIN)break;elsereturn;else if(bufle n = 0)/ 这里表示对端的socket已正常关闭.if(bufle n = sizeof(buf)rs = 1;/ 需要再次读取elsers = 0;还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的 socket要快),由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区 的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EA
46、GAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装 socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返 回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno 为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间 的阻塞在socket_send()内部,但暂没有更好的办法.ssize_t socket_se nd(i nt sockfd, const char* buffer, size_t bufle n) ssize_t tmp;size_t total
47、= bufle n;const char *p = buffer;while(1)tmp = sen d(sockfd, p, total, 0);if(tmp < 0)/当send收到信号时,可以继续写,但这里返回-1. if(errno = EINTR)return -1;/当socket是非阻塞时,如返回此错误,表示写缓冲队列已满, /在这里做延时后再重试.if(errno = EAGAIN)uslee p(1000);con ti nue; return -1;if(size_t)t mp = total) return bufle n;total -= tmp; p += tm
48、p;return tmp; epoll 有两种模式,Edge Triggered( 简称 ET)和 Level Triggered( 简称 LT). 在采用这两种模式时要注意的是,如果采用ET模式,那么仅当状态发生变化时才 会通知,而采用LT模式类似于原来的select/poll 操作,只要还有没有处理的事 件就会一直通知.以代码来说明问题:首先给出server的代码,需要说明的是每次accept的连接,加入可读集的时候 采用的都是ET模式,而且接收缓冲区是5字节的,也就是每次只接收5字节的数 据:#include <iostream>#include <sys/socket
49、.h>#include <sys/e poll.h>#include <netinet/in .h>#include <ar pa/inet.h> #include <fcntl.h> #include <unistd.h> #include <stdio.h> #include <errno.h>usingnames pace std;#defineMAXLINE 5#defineOPEN_MAX 100#defineLISTENQ 20#defineSERV_ PORT 5000#defineINFT
50、IM 1000struct sockaddr_in serveraddr;void setnonblocking(int sock)intop ts;op ts=fcntl(sock,F_GETFL);if (opts<0)p error("fcntl(sock,GETFL)");exit(1);opts = op ts|O_NONBLOCK;if (fcntl(sock,F_SETFL,opts)<0)p error("fcntl(sock,SETFL,o pts)");exit(1);intmain()int i, maxi, liste
51、nfd, connfd, sockfd,e pfd, nfds;ssize_t n;char lineMAXLINE;socklen_t clilen;/声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件struct epol l_event ev,events20;/生成用于处理accept的epoll专用的文件描述符ep fd=e pol l_create(256);struct sockaddr_in clientaddr;listenfd = socket(AF_INET, SOCK_STREAM, 0);/把socket设置为非阻塞方式/setnonb
52、locking(listenfd);/设置与要处理的事件相关的文件描述符ev.data.fd=listenfd;/设置要处理的事件类型ev.events=E P0 LLIN|E POLLET;/ev.events=E POLLIN;/注册epoll事件ep oll_ctl(e pfd,E PO LL_CTL_ADD,listenfd, &ev);bzer o(&serveraddr,sizeof (serveraddr);serveraddr.sin_family = AF_INET;char *local_addr="127.0.0.1"inet_aton
53、(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=e poll_wait(e pfd,events,20,500);/处理所发生的所有事件for (i=0;i<nfds;+i)if (eventsi
54、.data.fd=listenfd)connfd = acce pt(listenfd,(sockaddr *)&clientaddr, &clilen);if (connfd<0)p error("connfd<0");exit(1);/setnonblocking(connfd);char *str = inet_ntoa(clientaddr.sin_addr);cout <<"acca pt a connection from "<< str << endl;/设置用于读操作的文件描
55、述符ev.data.fd=connfd;/设置用于注测的读操作事件ev.events=E POLLIN|E PO LLET;/ev.events=E POLLIN;/注册evep oll_ctl(e pfd,E POLL_CTL_ADD,connfd,&ev);else if (eventsi.events&EPOLLIN)cout <<"EP OLLIN"<< endl;if(sockfd = eventsi.data.fd) < 0)ifcontinue(n = read(sockfd, line, MAXLINE) <
56、; 0) if (errno = ECONNRESET) close(sockfd);eventsi.data.fd = -1;elsestd:cout<<"readline error"<<std:endl;else if (n = 0) close(sockfd);'0'eventsi.data.fd = -1;linen=cout <<"read "<< line << endl;/设置用于写操作的文件描述符ev.data.fd=sockfd;/设置用于注测的写操作事件ev.events=E POLLOUT|E POLLET;/修改sockfd 上要处理的事件为EPOLLOUT/e poll_ctl(e pfd,E POLL_CTL_MOD,sockfd,&ev);else if (eventsi.events&EPOLLOUT)sockfd = eventsi.data.fd;write(sockfd, line, n);/设置用于读操作的文件描述符ev.data.fd=sockfd;/设置用于注测的读
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2023年油田工程技术服务项目融资计划书
- 2024秋新沪科版物理八年级上册教学课件 第五章 质量 第三节 密度
- 机械原理考试题
- 养老院老人生活娱乐活动组织人员职业道德制度
- 养老院老人健康管理制度
- 《就业中国演讲》课件
- 《金地格林世界提案》课件
- 提前预支工资合同
- 2024事业单位保密协议范本与保密工作考核3篇
- 2024年度离婚协议书详述财产分配与子女抚养细节及责任2篇
- 做自己的心理压力调节师智慧树知到期末考试答案2024年
- ASME材料-设计许用应力
- 财务报表公式模板
- 员工信息安全入职培训
- 项目现场协调配合措施
- (2024年)课堂教学与信息技术融合ppt课件pptx
- 制作纸杯蛋糕(课件)全国通用六年级下册综合实践活动
- 事业单位会计讲解
- 巨量直播电商运营认证考试64题
- 2021年10月自考00058市场营销学试题及答案含解析
- 精准医学演讲课件
评论
0/150
提交评论