应用办公生活信息教育商业
投稿投诉
商业财经
汽车智能
教育国际
房产环球
信息数码
热点科技
生活手机
晨报新闻
办公软件
科学动态
应用生物
体育时事

纯技术干货一文读懂selectpollepoll的用法

  select,poll,epoll都是IO多路复用的机制。IO多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步IO,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步IO则无需自己负责进行读写,异步IO的实现会负责把数据从内核拷贝到用户空间。一、select实现1。1。基本概念
  IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。
  IO多路复用适用如下场合:当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用IO复用。当一个客户同时处理多个接口时,而这种情况是可能的,但很少出现。如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到IO复用。如果一个服务器即要处理TCP,又要处理UDP,一般要使用IO复用。如果一个服务器要处理多个服务或多个协议,一般要使用IO复用。
  与多进程和多线程技术相比,IO多路复用技术的最大优势是系统开销小,系统不必创建进程线程,也不必维护这些进程线程,从而大大减小了系统的开销。1。2。select函数
  该函数准许进程指示内核等待多个事件中的任何一个发送,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下:includesysselect。hincludesystime。hintselect(intmaxfdp1,fdsetreadset,fdsetwriteset,fdsetexceptset,conststructtimevaltimeout)返回值:就绪描述符的数目,超时返回0,出错返回1
  函数参数介绍如下:
  (1)第一个参数maxfdp1指定待测试的描述字个数,它的值是待测试的最大描述字加1(因此把该参数命名为maxfdp1),描述字0、1、2。。。maxfdp11均将被测试。
  因为文件描述符是从0开始的。
  (2)中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个条件不感兴趣,就可以把它设为空指针。structfdset可以理解为一个集合,这个集合中存放的是文件描述符,可通过以下四个宏进行设置:voidFDZERO(fdsetfdset);清空集合voidFDSET(intfd,fdsetfdset);将一个给定的文件描述符加入集合之中voidFDCLR(intfd,fdsetfdset);将一个给定的文件描述符从集合中删除intFDISSET(intfd,fdsetfdset);检查集合中指定的文件描述符是否可以读写
  (3)timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。structtimeval{longtvsec;secondslongtvusec;microseconds};
  更多LInux内核视频教程文档资料免费领取后台私信【内核】自行获取。
  这个参数有三种可能:
  (1)永远等待下去:仅在有一个描述字准备好IO时才返回。为此,把该参数设置为空指针NULL。
  (2)等待一段固定时间:在有一个描述字准备好IO时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
  (3)根本不等待:检查描述字后立即返回,这称为轮询。为此,该参数必须指向一个timeval结构,而且其中的定时器值必须为0。1。3。测试程序
  写一个TCP回射程序,程序的功能是:客户端向服务器发送信息,服务器接收并原样发送给客户端,客户端显示出接收到的信息。
  服务端程序如下:1includestdio。h2includestdlib。h3includestring。h4includeerrno。h5includenetinetin。h6includesyssocket。h7includesysselect。h8includesystypes。h9includenetinetin。h10include11includeunistd。h12include1314defineIPADDR127。0。0。115definePORT878716defineMAXLINE102417defineLISTENQ518defineSIZE101920typedefstructservercontextst21{22intclicnt;客户端个数23intclifds〔SIZE〕;客户端的个数24fdsetallfds;句柄集合25intmaxfd;句柄最大值26}servercontextst;27staticservercontextstssrvctxNULL;282930staticintcreateserverproc(constcharip,intport)31{32intfd;33structsockaddrinservaddr;34fdsocket(AFINET,SOCKSTREAM,0);35if(fd1){36fprintf(stderr,createsocketfail,erron:d,reason:s,37errno,strerror(errno));38return1;39}4041一个端口释放后会等待两分钟之后才能再被使用,SOREUSEADDR是让端口释放后立即就可以被再次使用。42intreuse1;43if(setsockopt(fd,SOLSOCKET,SOREUSEADDR,reuse,sizeof(reuse))1){44return1;45}4647bzero(servaddr,sizeof(servaddr));48servaddr。sinfamilyAFINET;49inetpton(AFINET,ip,servaddr。sinaddr);50servaddr。sinporthtons(port);5152if(bind(fd,(structsockaddr)servaddr,sizeof(servaddr))1){53perror(binderror:);54return1;55}5657listen(fd,LISTENQ);5859returnfd;60}6162staticintacceptclientproc(intsrvfd)63{64structsockaddrincliaddr;65socklentcliaddrlen;66cliaddrlensizeof(cliaddr);67intclifd1;6869printf(accpetclintprociscalled。);7071ACCEPT:72clifdaccept(srvfd,(structsockaddr)cliaddr,cliaddrlen);7374if(clifd1){75if(errnoEINTR){76gotoACCEPT;77}else{78fprintf(stderr,acceptfail,error:s,strerror(errno));79return1;80}81}8283fprintf(stdout,acceptanewclient:s:d,84inetntoa(cliaddr。sinaddr),cliaddr。sinport);8586将新的连接描述符添加到数组中87inti0;88for(i0;iSIZE;i){89if(ssrvctxclifds〔i〕0){90ssrvctxclifds〔i〕clifd;91ssrvctxclicnt;92break;93}94}9596if(iSIZE){97fprintf(stderr,toomanyclients。);98return1;99}100101}102103staticinthandleclientmsg(intfd,charbuf)104{105assert(buf);106printf(recvbufis:s,buf);107write(fd,buf,strlen(buf)1);108return0;109}110111staticvoidrecvclientmsg(fdsetreadfds)112{113inti0,n0;114intclifd;115charbuf〔MAXLINE〕{0};116for(i0;issrvctxclicnt;i){117clifdssrvctxclifds〔i〕;118if(clifd0){119continue;120}121判断客户端套接字是否有数据122if(FDISSET(clifd,readfds)){123接收客户端发送的信息124nread(clifd,buf,MAXLINE);125if(n0){126n0表示读取完成,客户都关闭套接字127FDCLR(clifd,ssrvctxallfds);128close(clifd);129ssrvctxclifds〔i〕1;130continue;131}132handleclientmsg(clifd,buf);133}134}135}136staticvoidhandleclientproc(intsrvfd)137{138intclifd1;139intretval0;140fdsetreadfdsssrvctxallfds;141structtimevaltv;142inti0;143144while(1){145每次调用select前都要重新设置文件描述符和时间,因为事件发生后,文件描述符和时间都被内核修改啦146FDZERO(readfds);147添加监听套接字148FDSET(srvfd,readfds);149ssrvctxmaxfdsrvfd;150151tv。tvsec30;152tv。tvusec0;153添加客户端套接字154for(i0;issrvctxclicnt;i){155clifdssrvctxclifds〔i〕;156去除无效的客户端句柄157if(clifd!1){158FDSET(clifd,readfds);159}160ssrvctxmaxfd(clifdssrvctxmaxfd?clifd:ssrvctxmaxfd);161}162163开始轮询接收处理服务端和客户端套接字164retvalselect(ssrvctxmaxfd1,readfds,NULL,NULL,tv);165if(retval1){166fprintf(stderr,selecterror:s。,strerror(errno));167return;168}169if(retval0){170fprintf(stdout,selectistimeout。);171continue;172}173if(FDISSET(srvfd,readfds)){174监听客户端请求175acceptclientproc(srvfd);176}else{177接受处理客户端消息178recvclientmsg(readfds);179}180}181}182183staticvoidserveruninit()184{185if(ssrvctx){186free(ssrvctx);187ssrvctxNULL;188}189}190191staticintserverinit()192{193ssrvctx(servercontextst)malloc(sizeof(servercontextst));194if(ssrvctxNULL){195return1;196}197198memset(ssrvctx,0,sizeof(servercontextst));199200inti0;201for(;iSIZE;i){202ssrvctxclifds〔i〕1;203}204205return0;206}207208intmain(intargc,charargv〔〕)209{210intsrvfd;211初始化服务端context212if(serverinit()0){213return1;214}215创建服务,开始监听客户端请求216srvfdcreateserverproc(IPADDR,PORT);217if(srvfd0){218fprintf(stderr,socketcreateorbindfail。);219gotoerr;220}221开始接收并处理客户端请求222handleclientproc(srvfd);223serveruninit();224return0;225err:226serveruninit();227return1;228}
  客户端程序如下:1includenetinetin。h2includesyssocket。h3includestdio。h4includestring。h5includestdlib。h6includesysselect。h7includetime。h8includeunistd。h9includesystypes。h10includeerrno。h1112defineMAXLINE102413defineIPADDRESS127。0。0。114defineSERVPORT87871516definemax(a,b)(ab)?a:b1718staticvoidhandlerecvmsg(intsockfd,charbuf)19{20printf(clientrecvmsgis:s,buf);21sleep(5);22write(sockfd,buf,strlen(buf)1);23}2425staticvoidhandleconnection(intsockfd)26{27charsendline〔MAXLINE〕,recvline〔MAXLINE〕;28intmaxfdp,stdineof;29fdsetreadfds;30intn;31structtimevaltv;32intretval0;3334while(1){3536FDZERO(readfds);37FDSET(sockfd,readfds);38maxfdpsockfd;3940tv。tvsec5;41tv。tvusec0;4243retvalselect(maxfdp1,readfds,NULL,NULL,tv);4445if(retval1){46return;47}4849if(retval0){50printf(clienttimeout。);51continue;52}5354if(FDISSET(sockfd,readfds)){55nread(sockfd,recvline,MAXLINE);56if(n0){57fprintf(stderr,client:serverisclosed。);58close(sockfd);59FDCLR(sockfd,readfds);60return;61}6263handlerecvmsg(sockfd,recvline);64}65}66}6768intmain(intargc,charargv〔〕)69{70intsockfd;71structsockaddrinservaddr;7273sockfdsocket(AFINET,SOCKSTREAM,0);7475bzero(servaddr,sizeof(servaddr));76servaddr。sinfamilyAFINET;77servaddr。sinporthtons(SERVPORT);78inetpton(AFINET,IPADDRESS,servaddr。sinaddr);7980intretval0;81retvalconnect(sockfd,(structsockaddr)servaddr,sizeof(servaddr));82if(retval0){83fprintf(stderr,connectfail,error:s,strerror(errno));84return1;85}8687printf(clientsendtoserver。);88write(sockfd,helloserver,32);8990handleconnection(sockfd);9192return0;93}
  4、程序结果
  启动服务程序,执行三个客户程序进行测试,结果如下图所示:
  select的调用过程如下所示:(1)使用copyfromuser从用户空间拷贝fdset到内核空间(2)注册回调函数pollwait(3)遍历所有fd,调用其对应的poll方法(对于socket,这个poll方法是sockpoll,sockpoll根据情况会调用到tcppoll,udppoll或者datagrampoll)(4)以tcppoll为例,其核心实现就是pollwait,也就是上面注册的回调函数。(5)pollwait的主要工作就是把current(当前进程)挂到设备的等待队列中,不同的设备有不同的等待队列,对于tcppoll来说,其等待队列是sksksleep(注意把进程挂到等待队列中并不代表进程已经睡眠了)。在设备收到一条消息(网络设备)或填写完文件数据(磁盘设备)后,会唤醒设备等待队列上睡眠的进程,这时current便被唤醒了。(6)poll方法返回时会返回一个描述读写操作是否就绪的mask掩码,根据这个mask掩码给fdset赋值。(7)如果遍历完所有的fd,还没有返回一个可读写的mask掩码,则会调用scheduletimeout是调用select的进程(也就是current)进入睡眠。当设备驱动发生自身资源可读写后,会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间(scheduletimeout指定),还是没人唤醒,则调用select的进程会重新被唤醒获得CPU,进而重新遍历fd,判断有没有就绪的fd。(8)把fdset从内核空间拷贝到用户空间。
  总结:
  select的几大缺点:每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大select支持的文件描述符数量太小了,默认是1024二,poll实现
  poll的实现和select非常相似,只是描述fd集合的方式不同,poll使用pollfd结构而不是select的fdset结构,其他的都差不多。
  1、基本知识
  poll的机制与select类似,与select在本质上没有多大差别,管理多个描述符也是进行轮询,根据描述符的状态进行处理,但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
  2。2。poll函数
  函数格式如下所示:includepoll。hintpoll(structpollfdfds,unsignedintnfds,inttimeout);
  pollfd结构体定义如下:structpollfd{intfd;文件描述符shortevents;等待的事件shortrevents;实际发生了的事件};
  每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码,内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:POLLIN有数据可读。POLLRDNORM有普通数据可读。POLLRDBAND有优先数据可读。POLLPRI有紧迫数据可读。POLLOUT写数据不会导致阻塞。POLLWRNORM写普通数据不会导致阻塞。POLLWRBAND写优先数据不会导致阻塞。POLLMSGSIGPOLL消息可用。此外,revents域中还可能返回下列事件:
  POLLER指定的文件描述符发生错误。POLLHUP指定的文件描述符挂起事件。POLLNVAL指定的文件描述符非法。
  这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。
  使用poll()和select()不一样,你不需要显式地请求异常情况报告。
  POLLINPOLLPRI等价于select()的读事件,POLLOUTPOLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORMPOLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置events为POLLINPOLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
  timeout参数指定等待的毫秒数,无论IO是否准备好,poll都会返回。timeout指定为负数值表示无限超时,使poll()一直挂起直到一个指定事件发生;timeout为0指示poll调用立即返回并列出准备好IO的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。
  返回值和错误代码
  成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回1,并设置errno为下列值之一:EBADF一个或多个结构体中指定的文件描述符无效。EFAULTfds指针指向的地址超出进程的地址空间。EINTR请求的事件之前产生一个信号,调用可以重新发起。EINVALnfds参数超出PLIMITNOFILE值。ENOMEM可用内存不足,无法完成请求。
  2。3测出程序
  编写一个echoserver程序,功能是客户端向服务器发送信息,服务器接收输出并原样发送回给客户端,客户端接收到输出到终端。
  服务器端程序如下:1includestdio。h2includestdlib。h3includestring。h4includeerrno。h56includenetinetin。h7includesyssocket。h8includepoll。h9includeunistd。h10includesystypes。h1112defineIPADDRESS127。0。0。113definePORT878714defineMAXLINE102415defineLISTENQ516defineOPENMAX100017defineINFTIM11819函数声明20创建套接字并进行绑定21staticintsocketbind(constcharip,intport);22IO多路复用poll23staticvoiddopoll(intlistenfd);24处理多个连接25staticvoidhandleconnection(structpollfdconnfds,intnum);2627intmain(intargc,charargv〔〕)28{29intlistenfd,connfd,sockfd;30structsockaddrincliaddr;31socklentcliaddrlen;32listenfdsocketbind(IPADDRESS,PORT);33listen(listenfd,LISTENQ);34dopoll(listenfd);35return0;36}3738staticintsocketbind(constcharip,intport)39{40intlistenfd;41structsockaddrinservaddr;42listenfdsocket(AFINET,SOCKSTREAM,0);43if(listenfd1)44{45perror(socketerror:);46exit(1);47}48bzero(servaddr,sizeof(servaddr));49servaddr。sinfamilyAFINET;50inetpton(AFINET,ip,servaddr。sinaddr);51servaddr。sinporthtons(port);52if(bind(listenfd,(structsockaddr)servaddr,sizeof(servaddr))1)53{54perror(binderror:);55exit(1);56}57returnlistenfd;58}5960staticvoiddopoll(intlistenfd)61{62intconnfd,sockfd;63structsockaddrincliaddr;64socklentcliaddrlen;65structpollfdclientfds〔OPENMAX〕;66intmaxi;67inti;68intnready;69添加监听描述符70clientfds〔0〕。fdlistenfd;71clientfds〔0〕。eventsPOLLIN;72初始化客户连接描述符73for(i1;iOPENMAX;i)74clientfds〔i〕。fd1;75maxi0;76循环处理77for(;;)78{79获取可用描述符的个数80nreadypoll(clientfds,maxi1,INFTIM);81if(nready1)82{83perror(pollerror:);84exit(1);85}86测试监听描述符是否准备好87if(clientfds〔0〕。reventsPOLLIN)88{89cliaddrlensizeof(cliaddr);90接受新的连接91if((connfdaccept(listenfd,(structsockaddr)cliaddr,cliaddrlen))1)92{93if(errnoEINTR)94continue;95else96{97perror(accepterror:);98exit(1);99}100}101fprintf(stdout,acceptanewclient:s:d,inetntoa(cliaddr。sinaddr),cliaddr。sinport);102将新的连接描述符添加到数组中103for(i1;iOPENMAX;i)104{105if(clientfds〔i〕。fd0)106{107clientfds〔i〕。fdconnfd;108break;109}110}111if(iOPENMAX)112{113fprintf(stderr,toomanyclients。);114exit(1);115}116将新的描述符添加到读描述符集合中117clientfds〔i〕。eventsPOLLIN;118记录客户连接套接字的个数119maxi(imaxi?i:maxi);120if(nready0)121continue;122}123处理客户连接124handleconnection(clientfds,maxi);125}126}127128staticvoidhandleconnection(structpollfdconnfds,intnum)129{130inti,n;131charbuf〔MAXLINE〕;132memset(buf,0,MAXLINE);133for(i1;inum;i)134{135if(connfds〔i〕。fd0)136continue;137测试客户描述符是否准备好138if(connfds〔i〕。reventsPOLLIN)139{140接收客户端发送的信息141nread(connfds〔i〕。fd,buf,MAXLINE);142if(n0)143{144close(connfds〔i〕。fd);145connfds〔i〕。fd1;146continue;147}148printf(readmsgis:);149write(STDOUTFILENO,buf,n);150向客户端发送buf151write(connfds〔i〕。fd,buf,n);152}153}154}
  客户端代码如下所示:1includenetinetin。h2includesyssocket。h3includestdio。h4includestring。h5includestdlib。h6includepoll。h7includetime。h8includeunistd。h9includesystypes。h1011defineMAXLINE102412defineIPADDRESS127。0。0。113defineSERVPORT87871415definemax(a,b)(ab)?a:b1617staticvoidhandleconnection(intsockfd);1819intmain(intargc,charargv〔〕)20{21intsockfd;22structsockaddrinservaddr;23sockfdsocket(AFINET,SOCKSTREAM,0);24bzero(servaddr,sizeof(servaddr));25servaddr。sinfamilyAFINET;26servaddr。sinporthtons(SERVPORT);27inetpton(AFINET,IPADDRESS,servaddr。sinaddr);28connect(sockfd,(structsockaddr)servaddr,sizeof(servaddr));29处理连接描述符30handleconnection(sockfd);31return0;32}3334staticvoidhandleconnection(intsockfd)35{36charsendline〔MAXLINE〕,recvline〔MAXLINE〕;37intmaxfdp,stdineof;38structpollfdpfds〔2〕;39intn;40添加连接描述符41pfds〔0〕。fdsockfd;42pfds〔0〕。eventsPOLLIN;43添加标准输入描述符44pfds〔1〕。fdSTDINFILENO;45pfds〔1〕。eventsPOLLIN;46for(;;)47{48poll(pfds,2,1);49if(pfds〔0〕。reventsPOLLIN)50{51nread(sockfd,recvline,MAXLINE);52if(n0)53{54fprintf(stderr,client:serverisclosed。);55close(sockfd);56}57write(STDOUTFILENO,recvline,n);58}59测试标准输入是否准备好60if(pfds〔1〕。reventsPOLLIN)61{62nread(STDINFILENO,sendline,MAXLINE);63if(n0)64{65shutdown(sockfd,SHUTWR);66continue;67}68write(sockfd,sendline,n);69}70}71}三、epoll3。1基本知识
  epoll是在2。6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。3。2epoll接口
  epoll操作过程需要三个接口,分别如下:includesysepoll。hintepollcreate(intsize);intepollctl(intepfd,intop,intfd,structepolleventevent);intepollwait(intepfd,structepolleventevents,intmaxevents,inttimeout);
  (1)intepollcreate(intsize);
  创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看proc进程idfd,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
  (2)intepollctl(intepfd,intop,intfd,structepolleventevent);
  epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epollcreate()的返回值,第二个参数表示动作,用三个宏来表示:
  EPOLLCTLADD:注册新的fd到epfd中;
  EPOLLCTLMOD:修改已经注册的fd的监听事件;
  EPOLLCTLDEL:从epfd中删除一个fd;
  第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,structepollevent结构如下:structepollevent{uint32tevents;Epolleventsepolldatatdata;Userdatavariable};
  events可以是以下几个宏的集合:
  EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
  EPOLLOUT:表示对应的文件描述符可以写;
  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
  EPOLLERR:表示对应的文件描述符发生错误;
  EPOLLHUP:表示对应的文件描述符被挂断;
  EPOLLET:将EPOLL设为边缘触发(EdgeTriggered)模式,这是相对于水平触发(LevelTriggered)来说的。
  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
  (3)intepollwait(intepfd,structepolleventevents,intmaxevents,inttimeout);
  等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epollcreate()时的size,参数timeout是超时时间(毫秒,0会立即返回,1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。3。3。工作模式
  epoll对文件描述符的操作有两种模式:LT(leveltrigger)和ET(edgetrigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
  LT模式:当epollwait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epollwait时,会再次响应应用程序并通知此事件。
  ET模式:当epollwait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epollwait时,不会再次响应应用程序并通知此事件。
  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读阻塞写操作把处理多个文件描述符的任务饿死。3。4。测试程序
  编写一个服务器回射程序echo,练习epoll过程。
  服务器代码如下所示:1includestdio。h2includestdlib。h3includestring。h4includeerrno。h56includenetinetin。h7includesyssocket。h8include9includesysepoll。h10includeunistd。h11includesystypes。h1213defineIPADDRESS127。0。0。114definePORT878715defineMAXSIZE102416defineLISTENQ517defineFDSIZE100018defineEPOLLEVENTS1001920函数声明21创建套接字并进行绑定22staticintsocketbind(constcharip,intport);23IO多路复用epoll24staticvoiddoepoll(intlistenfd);25事件处理函数26staticvoid27handleevents(intepollfd,structepolleventevents,intnum,intlistenfd,charbuf);28处理接收到的连接29staticvoidhandleaccpet(intepollfd,intlistenfd);30读处理31staticvoiddoread(intepollfd,intfd,charbuf);32写处理33staticvoiddowrite(intepollfd,intfd,charbuf);34添加事件35staticvoidaddevent(intepollfd,intfd,intstate);36修改事件37staticvoidmodifyevent(intepollfd,intfd,intstate);38删除事件39staticvoiddeleteevent(intepollfd,intfd,intstate);4041intmain(intargc,charargv〔〕)42{43intlistenfd;44listenfdsocketbind(IPADDRESS,PORT);45listen(listenfd,LISTENQ);46doepoll(listenfd);47return0;48}4950staticintsocketbind(constcharip,intport)51{52intlistenfd;53structsockaddrinservaddr;54listenfdsocket(AFINET,SOCKSTREAM,0);55if(listenfd1)56{57perror(socketerror:);58exit(1);59}60bzero(servaddr,sizeof(servaddr));61servaddr。sinfamilyAFINET;62inetpton(AFINET,ip,servaddr。sinaddr);63servaddr。sinporthtons(port);64if(bind(listenfd,(structsockaddr)servaddr,sizeof(servaddr))1)65{66perror(binderror:);67exit(1);68}69returnlistenfd;70}7172staticvoiddoepoll(intlistenfd)73{74intepollfd;75structepolleventevents〔EPOLLEVENTS〕;76intret;77charbuf〔MAXSIZE〕;78memset(buf,0,MAXSIZE);79创建一个描述符80epollfdepollcreate(FDSIZE);81添加监听描述符事件82addevent(epollfd,listenfd,EPOLLIN);83for(;;)84{85获取已经准备好的描述符事件86retepollwait(epollfd,events,EPOLLEVENTS,1);87handleevents(epollfd,events,ret,listenfd,buf);88}89close(epollfd);90}9192staticvoid93handleevents(intepollfd,structepolleventevents,intnum,intlistenfd,charbuf)94{95inti;96intfd;97进行选好遍历98for(i0;inum;i)99{100fdevents〔i〕。data。fd;101根据描述符的类型和事件类型进行处理102if((fdlistenfd)(events〔i〕。eventsEPOLLIN))103handleaccpet(epollfd,listenfd);104elseif(events〔i〕。eventsEPOLLIN)105doread(epollfd,fd,buf);106elseif(events〔i〕。eventsEPOLLOUT)107dowrite(epollfd,fd,buf);108}109}110staticvoidhandleaccpet(intepollfd,intlistenfd)111{112intclifd;113structsockaddrincliaddr;114socklentcliaddrlen;115clifdaccept(listenfd,(structsockaddr)cliaddr,cliaddrlen);116if(clifd1)117perror(accpeterror:);118else119{120printf(acceptanewclient:s:d,inetntoa(cliaddr。sinaddr),cliaddr。sinport);121添加一个客户描述符和事件122addevent(epollfd,clifd,EPOLLIN);123}124}125126staticvoiddoread(intepollfd,intfd,charbuf)127{128intnread;129nreadread(fd,buf,MAXSIZE);130if(nread1)131{132perror(readerror:);133close(fd);134deleteevent(epollfd,fd,EPOLLIN);135}136elseif(nread0)137{138fprintf(stderr,clientclose。);139close(fd);140deleteevent(epollfd,fd,EPOLLIN);141}142else143{144printf(readmessageis:s,buf);145修改描述符对应的事件,由读改为写146modifyevent(epollfd,fd,EPOLLOUT);147}148}149150staticvoiddowrite(intepollfd,intfd,charbuf)151{152intnwrite;153nwritewrite(fd,buf,strlen(buf));154if(nwrite1)155{156perror(writeerror:);157close(fd);158deleteevent(epollfd,fd,EPOLLOUT);159}160else161modifyevent(epollfd,fd,EPOLLIN);162memset(buf,0,MAXSIZE);163}164165staticvoidaddevent(intepollfd,intfd,intstate)166{167structepolleventev;168ev。eventsstate;169ev。data。fdfd;170epollctl(epollfd,EPOLLCTLADD,fd,ev);171}172173staticvoiddeleteevent(intepollfd,intfd,intstate)174{175structepolleventev;176ev。eventsstate;177ev。data。fdfd;178epollctl(epollfd,EPOLLCTLDEL,fd,ev);179}180181staticvoidmodifyevent(intepollfd,intfd,intstate)182{183structepolleventev;184ev。eventsstate;185ev。data。fdfd;186epollctl(epollfd,EPOLLCTLMOD,fd,ev);187}
  客户端也用epoll实现,控制STDINFILENO、STDOUTFILENO、和sockfd三个描述符,程序如下所示:1includenetinetin。h2includesyssocket。h3includestdio。h4includestring。h5includestdlib。h6includesysepoll。h7includetime。h8includeunistd。h9includesystypes。h10include1112defineMAXSIZE102413defineIPADDRESS127。0。0。114defineSERVPORT878715defineFDSIZE102416defineEPOLLEVENTS201718staticvoidhandleconnection(intsockfd);19staticvoid20handleevents(intepollfd,structepolleventevents,intnum,intsockfd,charbuf);21staticvoiddoread(intepollfd,intfd,intsockfd,charbuf);22staticvoiddoread(intepollfd,intfd,intsockfd,charbuf);23staticvoiddowrite(intepollfd,intfd,intsockfd,charbuf);24staticvoidaddevent(intepollfd,intfd,intstate);25staticvoiddeleteevent(intepollfd,intfd,intstate);26staticvoidmodifyevent(intepollfd,intfd,intstate);2728intmain(intargc,charargv〔〕)29{30intsockfd;31structsockaddrinservaddr;32sockfdsocket(AFINET,SOCKSTREAM,0);33bzero(servaddr,sizeof(servaddr));34servaddr。sinfamilyAFINET;35servaddr。sinporthtons(SERVPORT);36inetpton(AFINET,IPADDRESS,servaddr。sinaddr);37connect(sockfd,(structsockaddr)servaddr,sizeof(servaddr));38处理连接39handleconnection(sockfd);40close(sockfd);41return0;42}434445staticvoidhandleconnection(intsockfd)46{47intepollfd;48structepolleventevents〔EPOLLEVENTS〕;49charbuf〔MAXSIZE〕;50intret;51epollfdepollcreate(FDSIZE);52addevent(epollfd,STDINFILENO,EPOLLIN);53for(;;)54{55retepollwait(epollfd,events,EPOLLEVENTS,1);56handleevents(epollfd,events,ret,sockfd,buf);57}58close(epollfd);59}6061staticvoid62handleevents(intepollfd,structepolleventevents,intnum,intsockfd,charbuf)63{64intfd;65inti;66for(i0;inum;i)67{68fdevents〔i〕。data。fd;69if(events〔i〕。eventsEPOLLIN)70doread(epollfd,fd,sockfd,buf);71elseif(events〔i〕。eventsEPOLLOUT)72dowrite(epollfd,fd,sockfd,buf);73}74}7576staticvoiddoread(intepollfd,intfd,intsockfd,charbuf)77{78intnread;79nreadread(fd,buf,MAXSIZE);80if(nread1)81{82perror(readerror:);83close(fd);84}85elseif(nread0)86{87fprintf(stderr,serverclose。);88close(fd);89}90else91{92if(fdSTDINFILENO)93addevent(epollfd,sockfd,EPOLLOUT);94else95{96deleteevent(epollfd,sockfd,EPOLLIN);97addevent(epollfd,STDOUTFILENO,EPOLLOUT);98}99}100}101102staticvoiddowrite(intepollfd,intfd,intsockfd,charbuf)103{104intnwrite;105nwritewrite(fd,buf,strlen(buf));106if(nwrite1)107{108perror(writeerror:);109close(fd);110}111else112{113if(fdSTDOUTFILENO)114deleteevent(epollfd,fd,EPOLLOUT);115else116modifyevent(epollfd,fd,EPOLLIN);117}118memset(buf,0,MAXSIZE);119}120121staticvoidaddevent(intepollfd,intfd,intstate)122{123structepolleventev;124ev。eventsstate;125ev。data。fdfd;126epollctl(epollfd,EPOLLCTLADD,fd,ev);127}128129staticvoiddeleteevent(intepollfd,intfd,intstate)130{131structepolleventev;132ev。eventsstate;133ev。data。fdfd;134epollctl(epollfd,EPOLLCTLDEL,fd,ev);135}136137staticvoidmodifyevent(intepollfd,intfd,intstate)138{139structepolleventev;140ev。eventsstate;141ev。data。fdfd;142epollctl(epollfd,EPOLLCTLMOD,fd,ev);143}
  epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数select或者poll函数。而epoll提供了三个函数,epollcreate,epollctl和epollwait,epollcreate是创建一个epoll句柄;epollctl是注册要监听的事件类型;epollwait则是等待事件的产生。对于第一个缺点,epoll的解决方案在epollctl函数中。每次注册新的事件到epoll句柄中时(在epollctl中指定EPOLLCTLADD),会把所有的fd拷贝进内核,而不是在epollwait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epollctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epollwait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用scheduletimeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以catprocsysfsfilemax查看,一般来说这个数目和系统内存关系很大。
  总结:
  (1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epollwait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epollwait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在醒着的时候要遍历整个fd集合,而epoll在醒着的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
  (2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epollwait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

40岁来临前,给你11条改变人生的建议有人说,40岁是一扇门,分出了人生的两个阶段。此前的生活里,奔波和忙碌是永远的主旋律。到了下半场,取舍和进退成了要紧的话题。所谓四十不惑,不是什么都知道,而是……人生不免走弯路弯路不免会走,但经历切肤之痛后我们就学精了(首发于www。qiqixie。com)这个周末,我太太,曼达去海边儿跟朋友度假了。头头则去参加他一个同学的婚礼。这家里就剩我一……洗缩水的毛衫,真能恢复如初吗?软糯的羊绒衫,暖身的毛衣,绝对是冬日衣橱里的主角,然而,一个不小心洗错了,M变成XS的惨剧就这样发生了。不过近来市面上开始出现一些还原神器专门给洗缩水了的毛衫等羊毛羊绒制……英特尔追赶先进制程脚步,Intel18A提前至2024下半年处理器龙头英特尔在IEDM2022(2022IEEE国际电子零件会议)展示最新技术蓝图时,表示保持快速步伐,不仅走在正轨,未来还要加速交货。随着不断缩小的硅与物理量子效应冲击,……朝阳浪马轮胎抢抓机遇创新实干以优良业绩回报家乡人民非常感谢市委、市政府!最艰难时期已经过去,企业生产经营正有序恢复,浪马人决不辜负市委、市政府的关心、关怀和期待,定以优良业绩回报家乡人民!日前,朝阳浪马轮胎有限责任公司董事长李……澳华内镜研究报告AQ300上市,对比奥巴和澳华(报告出品方作者:华创证券,郑辰,李婵娟,万梦蝶)一、解密奥林巴斯内镜王者之路(一)奥林巴斯从多元化发展路径到以医疗为核心的瘦身转型奥林巴斯1919年成立,以显微镜……150亿公里外!我国公布航天四大目标,追赶美国中国航天深空探测大动作来了!目标定在150亿公里外,我国航天太强了!探月自从嫦娥五号探测器将月球样本带回来后,我国的探月工程就暂时修整了。不过,第四期探月工程即将展……暴雪出轨是一种习惯暴雪和网易,终于还是分手了。时隔13年,熟悉的剧情再度重演:同样的更换代理,同样的国区停服。到明年1月24日,包括《魔兽世界》《炉石传说》《守望先锋》等暴雪旗下的一……老了脑子越来越不灵活,这些加速大脑衰老的习惯你中招了吗?正如你所知道的那样,随着年龄增长,脑力或多或少地会有所下降,智力下降、记忆力减退是正常的现象。所以理所应当的认为人老了大脑也就是老了,实际上大脑的衰老比想象的要来得早。有……杨迪没收到跨年晚会邀请,谢娜离开湖南卫视,也没在其他平台露面每年到了年末,很多人忙着休假玩乐,娱乐圈艺人却是最忙的时候,因为各种跨年晚会、盛典和演出扎堆,有人气的自然资源好,商机不断,赶场赚钱忙。随着跨年临近,几大卫视以及一些网络……需求恢复强度橡胶行情紧俏展望2023年,整个产业链的修复要取决于全球经济的恢复。若全球经济持续低迷,尽管终端市场有一定的修复,但是效果也将大打折扣,难有实质性的改观,对于上游天然橡胶的支撑也显得薄弱。……Reno9系列玩机技巧速览卡片,让桌面即好看又好用!OPPOReno9系列出厂即搭载了非常好用的ColorOS13系统,上面有各种既人性化又实用的功能,之前也和大家详细介绍了快速转移数据以及一些系统上的设置技巧,接下来就正式和大……
公司总裁赌输60亿,最终倾家荡产,值吗?不知大家还记得金立这个手机品牌,它成立于2002年,总部位于深圳。这家手机公司的创始人正是刘立荣。刘立荣,1972年出生于湖南省益阳市桃江县的一个普通家庭,曾经在校园中的刘立荣……小作精孟子义的瓜耍大牌,作精一直以来都是娱乐圈最为人诟病的事,被大家认为是没有艺德的表现,更是圈内、圈外都无法忍受的行为。老一辈艺人们都是勤勤恳恳,谦逊有礼,备受观众的喜爱和尊重。可是不……推荐国内的十大世界级美景,个个都是超乎寻常的美景,此生必去在一场场的旅途中,遇见了很多我们生活之外的美好,也看遍了无数的美景风光。最好的美景永远都在远方,其实不必向往国外的景点风光,作为拥有上下五千年文明的中国,地大物博,有山川……隔夜水致癌?隔夜肉隔夜鸡蛋和隔夜菜,哪个不能吃?导语:最近在网络上看到一个问题,为什么隔夜的水不能喝?从早上放到晚上的水却可以喝了?关于这个问题的讨论也是很激烈,有人说隔夜的水也是可以喝的,有人说隔夜这一词指的并不是放置时间……小米12X超强劲敌,曲面屏骁龙870IMX766,到手价仅2雷军在上个月28号发布的小米12系列中,除了搭载骁龙8Gen1的小米12和12Pro之外,还有一款定位准旗舰的小米12X,不过这款小米12X很多网友都不看好,尽管该机不少配置和……为缓解芯焦虑,韩国今年欲为半导体行业投资减负从2021年开始蔓延全球的芯片荒,到2022年末寒冬初显,半导体行业在这两年中见证了冰火两重天的发展。不过,行业的下行周期依旧阻挡不了各国在这一领域竞相争揽外国投资。20……有容奶大!MantizSaturnProII雷电3显卡扩展坞提示:本文为笔者2020年11月作品,因为是非常少有的评测资料,应部分网友要求重新发布。笔者做了20多年的评测,是纯属个人爱好,而不是靠做评测恰饭的。所以请各位读者注意文章发布……春季前后为何更多老人去世?需要注意这5点,尤其是最后一个大家有没有发现一到冬天尤其是春节前后,经常会有一些老年人,突然去世。平时还好好的,白天甚至还在外面遛弯,晚上不知怎么的,就突然去世了。这其实是有原因的,冬季天冷,老……父母必听孩子的生长潜能得到充分发挥了吗?【父母必听】身高由遗传和环境两大因素所决定,判断孩子的身高生长潜能是否充分发挥,可以通过孩子的身高评分和遗传身高评分进行对比评价。之前我们说过,身高是由遗传和环境两大因素……陈大胖成都市陈世美?出名后抛弃糟糠之妻,一细节暴露真实嘴脸陈大胖成都市陈世美?出名后抛弃糟糠之妻,一细节暴露真实嘴脸百万网红陈大胖蹭胖猴仔热度走红后与前妻离婚遭到网友的嘲讽陈大胖走红吃播路线曾与吃播胖猴仔属于同……糖尿病足,伤不起的狠脚色!糖尿病足是糖尿病患者常见的严重并发症之一,也是导致患者截肢的一个很重要的原因。在病变早期,由于症状轻微或不典型,患者往往难以察觉或引不起重视,但这恰恰又是糖尿病足防治的最佳时期……魅族太猛了骁龙870OLED直屏Flyme9,跌至1449元魅族的成立时间比小米早,但是却被后来居上,有网友归咎于魅族一直扶持联发科导致体验受到质疑,直到魅族Pro7之后,就彻底爆发,被网友嫌弃了,发布价2880元的魅族Pro7,最终以……
友情链接:快好找快生活快百科快传网中准网文好找聚热点快软网