监测膀胱内压力表三通旋塞时后三通接什么位置

jazka 的BLOG
用户名:jazka
文章数:142
评论数:195
访问量:868535
注册日期:
阅读量:5863
阅读量:12276
阅读量:422085
阅读量:1110474
51CTO推荐博文
&&& &距离上一篇博客都已经半个多月了,这么多天一直在学习研究关于Windows的完成端口移植到Linux下epoll方面的内容。这两方面以前都没有太多的接触,所以花费了较长的时间。在连续加班两天后,用一个周末的代价换来了一个调试成功。下面就把最近的成果与各位网友分享一下。如有不正确之处,望指正。
&先来说说Windows下的完成端口。完成端口号称是Windows下面最复杂的异步IO操作。但是如果你想开发出具有高性能的、支持大量连接的网络服务程序的话,就必须将它拿下。这里假设你已经对完成端口有一定的了解了。
&&& &下面引用一下幽默讲解Windows支持的五种Socket I/O模型的例子来通俗的说一下完成端口究竟是怎么回事。
&& &老陈有一个在外地工作的女儿,不能经常回来,老陈和她通过信件联系。他们的信会被邮递员投递到他们的微软信箱里。
&&& &我们平时使用的select模型,老陈每隔几分钟便到楼下看看是否有信。这样的方式会浪费老陈很多时间。同理,程序会阻塞在这里等待数据的到来,使得该进程(线程)无法进行其他的操作,导致性能的降低。
&&& &WSAAsyncSelect模型、WSAEventSelect模型同为事件触发模型。此时,只要有信到,微软就会主动通知老陈。此时,老陈只需要等待通知即可,在等待过程中可以做其他的事情。
&而Overlapped I/O 事件通知模型基本和上面两种类似。只是,老陈不需要上下楼取信了,他只需告诉微软自己在几楼几号,微软就会把信送到老陈家里。
&&& &后来微软推出了Overlapped I/O 完成例程模型,老陈将自己拆信―阅读―回复的过程告诉微软,微软就会按照上述步骤去处理信件。
&&& &但是,由于微软要处理的信件实在太多了,信箱经常崩溃。于是采用了新技术Completion Port来处理这些信件。
&&& &通过Win32的重叠I/O机制,应用程序可以提请一项I/O操作,重叠的操作请求在后台完成,而同一时间提请操作的线程去做其他的事情。等重叠操作完成后线程收到有关的通知。而一个完成端口其实就是一个通知队列,由操作系统把已经完成的重叠I/O请求的通知放入其中。当某项I/O操作一旦完成,某个可以对该操作结果进行处理的工作者线程就会收到一则通知。
&&& &完成端口的使用主要分两步。
&&& &首先,创建完成端口
HANDLE&&&&&&&&hI hIocp = CreateIoCompletionPort( &&&&&&&&INVALID_HANDLE_VALUE, &&&&&&&&NULL, &&&&&&&&(ULONG_PTR)0, &&&&&&&&0); if (hIocp == NULL) { &&&&&&&&// Error }
&&&完成端口创建后,要把将使用该完成端口的套接字与之关联起来。方法是再次调用CreateIoCompletionPort ()函数,第一个参数FileHandle设为套接字的句柄,第二个参数ExistingCompletionPort 设为刚刚创建的那个完成端口的句柄。以下代码创建了一个套接字,并把它和前面创建的完成端口关联起来:
SOCKET&&&&&&&&s; s = socket(AF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { &&&&&&&&// Error
if (CreateIoCompletionPort((HANDLE)s,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& hIocp,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&(ULONG_PTR)0,&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& &0) == NULL) { // Error } ... 这时就完成了套接字与完成端口的关联操作。在这个套接字上进行的任何重叠操作都将通过完成端口发出完成通知。
&&& 其次,使用API函数GetQueuedCompletionStatus来不断的监听查询某个完成端口的I/O操作的结果。通常来讲,在主线程中都只创建一个完成端口,将所有的套接字都与此完成端口关联。而进行监听查询的线程数一般取CPU数量的两倍。
BOOL GetQueuedCompletionStatus( &&&&&&&&HANDLE CompletionPort,&&&&&&&&&&&&&// handle to completion port &&&&&&&&LPDWORD lpNumberOfBytes,&&&&&&&&// bytes transferred &&&&&&&&PULONG_PTR lpCompletionKey,&&&& // file completion key &&&&&&&&LPOVERLAPPED *lpOverlapped,&&&& // buffer &&&&&&&&DWORD dwMilliseconds&&&&&&&&&&&&  // optional timeout value );
第一个参数指出了线程要监视哪一个完成端口。GetQueuedCompletionStatus使调用线程挂起,直到指定的端口的I/O完成队列中出现了一项或直到超时。同I/O完成端口相关联的第3个数据结构是使线程得到完成I/O项中的信息:传输的字节数,完成键和OVERLAPPED结构的地址。该信息是通过传递给GetQueuedCompletionSatatus的lpdwNumberOfBytesTransferred,lpdwCompletionKey和lpOverlapped参数返回给线程的。
&&& &注意lpOverlapped,这是很重要的一个数据结构,从这里你将获得你想要的数据,并进行判断处理。这里你可能会问,这个lpOverlapped数据结构是哪里来的,是什么类型的呢?接下来你就明白了。
&&& 上面讨论了完成端口的使用,这其实是后期的处理,要想真正了解整个过程,还需要学习下面关于之前如何将发送和接收数据的I/O操作提交。
&&& 一个是API函数
  int WSAAPI WSARecv ( SOCKET s, LPWSABUF lpBuffers,   DWORD dwBufferCount, LPDWORD lpNumberOfBytesRecvd,   LPINT lpFlags, LPWSAOVERLAPPED lpOverlapped,   LPWSAOVERLAPPED_COMPLETION_ROUTINE l&&&& pCompletionRoutine );&
 lpOverlapped:一个指向WSAOVERLAPPED结构的指针,在这个参数中就可以设置你要接收的数据结构。
另一个是API函数WASSend在一个已连接的套接口上发送数据。
  int WSAAPI WSASend (&&&&  SOCKET s,&&&&  LPWSABUF lpBuffers,   DWORD dwBufferCount,&&&&  LPDWORD lpNumberOfBytesSent,   int iFlags,&&&&  LPWSAOVERLAPPED lpOverlapped,   LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine   );&
同理,lpOverlapped用来设置发送是数据结构。
对完成端口来说,将一个套结字邦定到完成端口后,WSARecv和WSASend会立即返回,提高了系统的效率。可以调用 GetQueuedCompletionStatus来判断WSARecv和WSASend是否完成。主线程接受到一个连接后,调用WSARecv等待该连接发送的数据(不阻塞,由完成端口实现数据的接受完毕判断)。在线程函数中接受完毕,然后用WSASend函数发送给客户数据(同样是不阻塞,直接返回,由完成端口判断数据是否发送完毕)。这样在线程函数中需要程序员自己设置状态来区分是发送完毕还是接受完毕。
注意WSARecv 只是向系统提交一个异步接收请求,这个请求会在有数据到达之后返回,并且放入完成队列通知工作线程,这个异步接收请求到此完成,继续提交请求是为了接收下一个数据包,也就是说,每次请求返回之后必须再次提交。WSASend也只是向系统提交一个异步发送请求,当发送成功后,需要提交WSARecv接收请求,因为发送是主动的,发送完毕后必然要等待接收对方的回复。如果不提交WSARecv接收请求,则对方发过来的数据后,完成端口不会监听。
先写这些,下一篇在写关于Linux下面epoll的相关内容。本文出自 “” 博客,请务必保留此出处
了这篇文章
类别:┆阅读(0)┆评论(0)博客访问: 435142
博文数量: 124
博客积分: 3236
博客等级: 中校
技术积分: 916
注册时间:
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: C/C++
内核为处理大批量句柄而作了改进的,是下多路复用接口的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统利用率。
一、epoll的优点
数目增加而线性下降。
内核微调。
二、epoll的使用
种工作方式:和。&  
,水平触发和在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的进行操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的都是这种模型的代表。&  
,边缘触发。在这种模式下,当描述符从未就绪变为就绪时,内核通过告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个错误)。但是请注意,如果一直不对这个作操作(从而导致它再次变成未就绪),内核不会发送更多的通知()。
3个和。在头文件
参数:要少个。
返回值:成功时,返回一个非负整数的文件描述符,作为创建好的句柄。调用失败时,返回,错误信息可以通过获得。&&& 说明:句柄,用来告诉内核这个监听的数目一共有多大。这个参数不同于中的第一个参数,给出最大监听的的值。需要注意的是,当创建好句柄后,它就是会占用一个值,所以在使用完后,必须调用关闭,否则可能导致被耗尽。
&&& 参数:函数返回的句柄。
参数:操作选项。
参数:要进行操作的目标文件描述符。
参数:结构指针,将和要进行的操作关联起来。
返回值:成功时,返回,作为创建好的句柄。调用失败时,返回,错误信息可以通过获得。
说明:是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
参数op的可选值有以下个:到中;EPOLL_CTL_MOD:修改已经注册的的监听事件;EPOLL_CTL_DEL:从中删除一个;
&union&epoll_data&{&&&&&void&*&&&&&int&&&&&&__uint32_t&u32;&&&&&__uint64_t&u64;&&}&epoll_data_t;&&&&struct&epoll_event&{&&&&&__uint32_t&&&&&&&epoll_data_t&&&&};&&typedef union epoll_data {
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_ /* Epoll events */
epoll_data_ /* User data variable */
&&&正常关闭);设为边缘触发模式,这是相对于水平触发来说的。的话,需要再次把这个加入到队列里&&& 参数:函数返回的句柄。
参数:结构指针,。
参数&:诉有多大
参数&等待时的,以毫秒为单位。
返回值:成功时,返回。调用失败时,返回,表示等待超时。
说明:。三、示例
&&&&&&#include&&&#include&&&#include&&&#include&&&#include&&&#include&&&#include&&&#include&&&#include&&&&&using&namespace&&&&&#define&MAXLINE&100&&#define&OPEN_MAX&100&&#define&LISTENQ&20&&#define&SERV_PORT&5000&&#define&INFTIM&1000&&&&void&setnonblocking(int&sock)&&{&&&&&&int&&&&&&&&opts=fcntl(sock,F_GETFL);&&&&&&if(opts<0)&&&&&&{&&&&&&&&&&perror("fcntl(sock,GETFL)");&&&&&&&&&&exit(1);&&&&&&}&&&&&&&opts&=&opts|O_NONBLOCK;&&&&&&if(fcntl(sock,F_SETFL,opts)<0)&&&&&&{&&&&&&&&&&perror("fcntl(sock,SETFL,opts)");&&&&&&&&&&exit(1);&&&&&&}&&}&&&&int&main(int&argc,&char*&argv[])&&{&&&&&&int&i,&maxi,&listenfd,&connfd,&sockfd,epfd,nfds,&&&&&&&ssize_t&n;&&&&&&char&line[MAXLINE];&&&&&&socklen_t&&&&&&&string&szTemp("");&&&&&&&&if&(&2&==&argc&)&&&&&&{&&&&&&&&&&if(&(portnumber&=&atoi(argv[1]))&<&0&)&&&&&&&&&&{&&&&&&&&&&&&&&fprintf(stderr,"Usage:%s&portnumber\a\n",argv[0]);&&&&&&&&&&&&&&return&1;&&&&&&&&&&}&&&&&&}&&&&&&else&&&&&&{&&&&&&&&&&fprintf(stderr,"Usage:%s&portnumber\a\n",argv[0]);&&&&&&&&&&return&1;&&&&&&}&&&&&&&&&&&&&&&&&&struct&epoll_event&ev,&events[20];&&&&&&&&&&&&&&&&&&epfd&=&epoll_create(256);&&&&&&&&&&&&&struct&sockaddr_in&&&&&&&struct&sockaddr_in&&&&&&&listenfd&=&socket(AF_INET,&SOCK_STREAM,&0);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev.data.fd=&&&&&&&&&&&&&&&&&&ev.events=EPOLLIN|EPOLLET;&&&&&&&&&&&&&&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_aton(local_addr,&(serveraddr.sin_addr));&&&&&&serveraddr.sin_port=htons(portnumber);&&&&&&bind(listenfd,(sockaddr&*)&serveraddr,&sizeof(serveraddr));&&&&&&&&&&&&listen(listenfd,&LISTENQ);&&&&&&&&&&&&maxi&=&0;&&&&&&&&&&&&for&(&;&;&)&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&nfds=epoll_wait(epfd,events,20,500);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&for(i=0;&i&<&nfds;&++i)&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&if(events[i].data.fd&==&listenfd)&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&connfd&=&accept(listenfd,(sockaddr&*)&clientaddr,&&clilen);&&&&&&&&&&&&&&&&&&if(connfd&<&0)&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&perror("connfd&<&0");&&&&&&&&&&&&&&&&&&&&&&exit(1);&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&char&*str&=&inet_ntoa(clientaddr.sin_addr);&&&&&&&&&&&&&&&&&&cout&<<&"accapt&a&connection&from&"&<<&str&<<&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev.data.fd=&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev.events=EPOLLIN|EPOLLET;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&&&&&&else&if(events[i].events&EPOLLIN)&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&cout&<<&"EPOLLIN"&<<&&&&&&&&&&&&&&&&&&&if&(&(sockfd&=&events[i].data.fd)&<&0)&&&&&&&&&&&&&&&&&&&&&&continue;&&&&&&&&&&&&&&&&&&if&(&(n&=&recv(sockfd,&line,&sizeof(line),&0))&<&0)&&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&if&(errno&==&ECONNRESET)&&&&&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&close(sockfd);&&&&&&&&&&&&&&&&&&&&&&&&&&events[i].data.fd&=&-1;&&&&&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&else&&&&&&&&&&&&&&&&&&&&&&&&&&std::cout<<"readline&error"<<std::&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&else&if&(n&==&0)&&&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&close(sockfd);&&&&&&&&&&&&&&&&&&&&&&events[i].data.fd&=&-1;&&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&szTemp&=&"";&&&&&&&&&&&&&&&&&&szTemp&+=&&&&&&&&&&&&&&&&&&&szTemp&=&szTemp.substr(0,szTemp.find('\r'));&&&&&&&&&&&&&&&&&&&memset(line,0,100);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&cout&<<&"Readin:&"&<<&szTemp&<<&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev.data.fd=&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev.events=EPOLLOUT|EPOLLET;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);&&&&&&&&&&&&&&&&&}&&&&&&&&&&&&&&else&if(events[i].events&EPOLLOUT)&&&&&&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&sockfd&=&events[i].data.&&&&&&&&&&&&&&&&&&szTemp&=&"Server:"&+&szTemp&+&"\n";&&&&&&&&&&&&&&&&&&send(sockfd,&szTemp.c_str(),&szTemp.size(),&0);&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev.data.fd=&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&ev.events=EPOLLIN|EPOLLET;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);&&&&&&&&&&&&&&&}&&&&&&&&&&}&&&&&&&}&&&&&&&&&&&&&close(epfd);&&&&&&return&0;&&}&
阅读(12788) | 评论(3) | 转发(4) |
相关热门文章
给主人留下些什么吧!~~
这个代码有问题,只有一个客户端还行。
如果有N个,确使用一个szTemp来缓存消息,会乱续返回。
一般写操作不需要用epoll。
这个代码有问题,只有一个客户端还行。
如果有N个,确使用一个szTemp来缓存消息,会乱续返回。
一般写操作不需要用epoll。
这个代码有问题,只有一个客户端还行。
如果有N个,确使用一个szTemp来缓存消息,会乱续返回。
一般写操作不需要用epoll。
请登录后评论。epoll ET/LT 触发方式的性能差异
我的图书馆
epoll ET/LT 触发方式的性能差异
转自:http://blog.csdn.net/football111/article/details/剖析 epoll ET/LT 触发方式的性能差异误解(定性分析)平时大家使用 epoll 时都知道其事件触发模式有默认的 level-trigger 模式和通过 EPOLLET 启用的 edge-trigger 模式两种。从 epoll 发展历史来看,它刚诞生时只有 edge-trigger 模式,后来因容易产生 race-cond 且不易被开发者理解,又增加了 level-trigger 模式并作为默认处理方式。二者的差异在于 level-trigger 模式下只要某个 fd 处于 readable/writable 状态,无论什么时候进行 epoll_wait 都会返回该 fd;而 edge-trigger 模式下只有某个 fd 从 unreadable 变为 readable 或从 unwritable 变为 writable 时,epoll_wait 才会返回该 fd。通常的误区是:level-trigger 模式在 epoll 池中存在大量 fd 时效率要显著低于 edge-trigger 模式。但从 kernel 代码来看,edge-trigger/level-trigger 模式的处理逻辑几乎完全相同,差别仅在于 level-trigger 模式在 event 发生时不会将其从 ready list 中移除,略为增大了 event 处理过程中 kernel space 中记录数据的大小。然而,edge-trigger 模式一定要配合 user app 中的 ready list 结构,以便收集已出现 event 的 fd,再通过 round-robin 方式挨个处理,以此避免通信数据量很大时出现忙于处理热点 fd 而导致非热点 fd 饿死的现象。统观 kernel 和 user space,由于 user app 中 ready list 的实现千奇百怪,不一定都经过仔细的推敲优化,因此 edge-trigger 的总内存开销往往还大于 level-trigger 的开销。一般号称 edge-trigger 模式的优势在于能够减少 epoll 相关系统调用,这话不假,但 user app 里可不是只有 epoll 相关系统调用吧?为了绕过饿死问题,edge-trigger 模式的 user app 要自行进行 read/write 循环处理,这其中增加的系统调用和减少的 epoll 系统调用加起来,有谁能说一定就能明显地快起来呢?实际上,epoll_wait 的效率是 O(ready fd num) 级别的,因此 edge-trigger 模式的真正优势在于减少了每次 epoll_wait 可能需要返回的 fd 数量,在并发 event 数量极多的情况下能加快 epoll_wait 的处理速度,但别忘了这只是针对 epoll 体系自己而言的提升,与此同时 user app 需要增加复杂的逻辑、花费更多的 cpu/mem 与其配合工作,总体性能收益究竟如何?只有实际测量才知道,无法一概而论。不过,为了降低处理逻辑复杂度,常用的事件处理库大部分都选择了 level-trigger 模式(如 libevent、boost::asio等)结论:· epoll 的 edge-trigger 和 level-trigger 模式处理逻辑差异极小,性能测试结果表明常规应用场景 中二者性能差异可以忽略。· 使用 edge-trigger 的 user app 比使用 level-trigger 的逻辑复杂,出错概率更高。· edge-trigger 和 level-trigger 的性能差异主要在于 epoll_wait 系统调用的处理速度,是否是 user app 的性能瓶颈需要视应用场景而定,不可一概而论&分类:& 11:29&1708人阅读&(1)&&1. Epoll&是何方神圣?Epoll&可是当前在&Linux&下开发大规模并发网络程序的热门人选,&Epoll&在&Linux2.6&内核中正式引入,和&select&相似,其实都&I/O&多路复用技术而已&,并没有什么神秘的。其实在&Linux&下设计并发网络程序,向来不缺少方法,比如典型的&Apache&模型(&Process Per Connection&,简称&PPC&),&TPC&(&Thread Per Connection&)模型,以及&select&模型和&poll&模型,那为何还要再引入&Epoll&这个东东呢?那还是有得说说的&…2.&常用模型的缺点如果不摆出来其他模型的缺点,怎么能对比出&Epoll&的优点呢。2.1 PPC/TPC&模型这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我&。只是&PPC&是为它开了一个进程,而&TPC&开了一个线程。可是别烦我是有代价的,它要时间和空间啊,连接多了之后,那么多的进程&/&线程切换,这开销就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。2.2 select&模型1.&最大并发数限制,因为一个进程所打开的&FD&(文件描述符)是有限制的,由&FD_SETSIZE&设置,默认值是&&,因此&Select&模型的最大并发数就被相应限制了。自己改改这个&FD_SETSIZE&?想法虽好,可是先看看下面吧&…2.&效率问题,&select&每次调用都会线性扫描全部的&FD&集合,这样效率就会呈现线性下降,把&FD_SETSIZE&改大的后果就是,大家都慢慢来,什么?都超时了??!!3.&内核&/&用户空间&内存拷贝问题,如何让内核把&FD&消息通知给用户空间呢?在这个问题上&select&采取了内存拷贝方法。2.3 poll&模型基本上效率和&select&是相同的,&select&缺点的&2&和&3&它都没有改掉。3. Epoll&的提升把其他模型逐个批判了一下,再来看看&Epoll&的改进之处吧,其实把&select&的缺点反过来那就是&Epoll&的优点了。3.1. Epoll&没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于&2048,&一般来说这个数目和系统内存关系很大&,具体数目可以&cat /proc/sys/fs/file-max&察看。3.2.&效率提升,&Epoll&最大的优点就在于它只管你“活跃”的连接&,而跟连接总数无关,因此在实际的网络环境中,&Epoll&的效率就会远远高于&select和&poll&。3.3.&内存拷贝,&Epoll&在这点上使用了“共享内存&”,这个内存拷贝也省略了。&&4. Epoll&为什么高效Epoll&的高效和其数据结构的设计是密不可分的,这个下面就会提到。首先回忆一下&select&模型,当有&I/O&事件到来时,&select&通知应用程序有事件到了快去处理,而应用程序必须轮询所有的&FD&集合,测试每个&FD&是否有事件发生,并处理事件;代码像下面这样:int&res = select(maxfd+1, &readfds, NULL, NULL, 120);if&(res & 0){&&&&for&(int&i = 0; i & MAX_CONNECTION; i++)&&&&{&&&&&&&&if&(FD_ISSET(allConnection[i], &readfds))&&&&&&&&{&&&&&&&&&&&&handleEvent(allConnection[i]);&&&&&&&&}&&&&}}// if(res == 0) handle timeout, res & 0 handle error&Epoll&不仅会告诉应用程序有I/0&事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而不必遍历整个FD&集合。int&res = epoll_wait(epfd, events, 20, 120);for&(int&i = 0; i &i++){&&&&handleEvent(events[n]);}5. Epoll&关键数据结构前面提到&Epoll&速度快和其数据结构密不可分,其关键数据结构就是:struct&epoll_event {&&&&__uint32_&&&&&&// Epoll events&&&&epoll_data_&&&&&&// User data variable};typedef&union&epoll_data {&&&&void&*&&&&int&&&&&__uint32_t u32;&&&&__uint64_t u64;} epoll_data_t;可见&epoll_data&是一个&union&结构体&,&借助于它应用程序可以保存很多类型的信息&:fd&、指针等等。有了它,应用程序就可以直接定位目标了。6.&使用&Epoll既然&Epoll&相比&select&这么好,那么用起来如何呢?会不会很繁琐啊&…&先看看下面的三个函数吧,就知道&Epoll&的易用了。&int&epoll_create(int&size);生成一个&Epoll&专用的文件描述符,其实是申请一个内核空间,用来存放你想关注的&socket fd&上是否发生以及发生了什么事件。&size&就是你在这个&Epoll fd&上能关注的最大&socket fd&数,大小自定,只要内存足够。int&epoll_ctl(int&epfd,&int&op,&int&fd,&struct&epoll_event *event&);控制某个&Epoll&文件描述符上的事件:注册、修改、删除。其中参数&epfd&是&epoll_create()&创建&Epoll&专用的文件描述符。相对于&select&模型中的FD_SET&和&FD_CLR&宏。int&epoll_wait(int&epfd,struct&epoll_event * events,int&maxevents,int&timeout);等待&I/O&事件的发生;参数说明:epfd:&由&epoll_create()&生成的&Epoll&专用的文件描述符;epoll_event:&用于回传代处理事件的数组;maxevents:&每次能处理的事件数;timeout:&等待&I/O&事件发生的超时值;返回发生事件数。相对于&select&模型中的&select&函数。7.&epoll的两种工作模式LT/ETEPOLLLT完全靠kernel epoll驱动,应用程序只需要处理从epoll_wait返回的fds,应用程序可以根据需要,执行读取/写入操作一次或多次&此模式下,系统默认所有的fds都是空闲的,只有epoll_wait通知的fds是忙碌的,所以应用系统只需要处理这些fds就可以了&EPOLLET主要靠应用程序处理fds,应用程序从epoll_wait只能得到哪些fds是由空闲变为忙碌状态。此时应用程序需要自己维护一张fds的表格,把从 epoll_wait获得的状态变化信息登记到这张表格。然后应用程序可以选择遍历这张fds的表格,对处于忙碌状态的fds进行操作。&当读取/写入操作遇到EAGAIN的错误,就表示这个fd由忙碌状态变为空闲状态,在下一次epoll_wait调用之前如果有数据进来或者这个fd的写缓冲区又空闲了,那么epoll_wait会再次通知应用程序,这个fd从空闲状态变为忙碌状态。&此模式下,系统仅仅通知应用程序哪些fds变成了忙碌状态,一旦fd变成忙碌状态,epoll将不再关注这个fd的任何状态信息,直到应用程序通过读写操作触发EAGAIN状态,epoll认为这个fd又变为空闲状态,那么epoll又重新关注这个fd的状态变化。&因此EPOLLET比EPOLLLT对应用程序的要求更多,需要程序员设计的部分也更多,看上去EPOLLLT要简单的多。但是如果这里我们要求对fd有 超时控制,EPOLLLT需要有额外的fds遍历操作,而EPOLLET本来就需要不断遍历fds,如此看来使用EPOLLET是更好的选 择,EPOLLLT才是设计不够完善的小玩具。&而且由于epoll_wait每次返回的fds的数量有限,在大并发的模式下,EPOLLLT将非常的繁忙,所有的fds都要在它的队列中产生状态消息,而每次只有其中一部分fds被返回给应用程序。相对于EPOLLET,只要epoll_wait返回一次fds之后,这些fds就从epoll队列中消除,只有应用程序遇到EAGAIN之后fd才会重 新添加到epoll队列,如此看来随着epoll_wait的返回,队列中的fds是在减少的,所以在大并发的系统中,EPOLLET更有优势。但是对程 序员的要求也更高。&8.&例子程序下面是一个简单&Echo Server&的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。[c-sharp]&&&1.&//&&&&&&&2.&//&a&simple&echo&server&using&epoll&in&linux&&&&&&3.&//&&&&&&&4.&//&&&&&&&5.&//&by&sparkling&&&&&&6.&//&&&&&&&7.&#include&&sys/socket.h&&&&&&&8.&#include&&sys/epoll.h&&&&&&&9.&#include&&netinet/in.h&&&&&&10.&#include&&arpa/inet.h&&&&&&11.&#include&&fcntl.h&&&&&&12.&#include&&unistd.h&&&&&&13.&#include&&stdio.h&&&&&&14.&#include&&errno.h&&&&&&15.&#include&&iostream&&&&&&16.&using&namespace&&&&&&17.&#define&MAX_EVENTS&500&&&&&18.&struct&myevent_s&&&&&19.&{&&&&&20.&&&&&int&&&&&&21.&&&&&void&(*call_back)(int&fd,&int&events,&void&*arg);&&&&&22.&&&&&int&&&&&&23.&&&&&void&*&&&&&24.&&&&&int&&//&1:&in&epoll&wait&list,&0&not&in&&&&&25.&&&&&char&buff[128];&//&recv&data&buffer&&&&&26.&&&&&int&&&&&&27.&&&&&long&last_&//&last&active&time&&&&&28.&};&&&&&29.&//&set&event&&&&&30.&void&EventSet(myevent_s&*ev,&int&fd,&void&(*call_back)(int,&int,&void*),&void&*arg)&&&&&31.&{&&&&&32.&&&&&ev-&fd&=&&&&&&33.&&&&&ev-&call_back&=&call_&&&&&34.&&&&&ev-&events&=&0;&&&&&35.&&&&&ev-&arg&=&&&&&&36.&&&&&ev-&status&=&0;&&&&&37.&&&&&ev-&last_active&=&time(NULL);&&&&&38.&}&&&&&39.&//&add/mod&an&event&to&epoll&&&&&40.&void&EventAdd(int&epollFd,&int&events,&myevent_s&*ev)&&&&&41.&{&&&&&42.&&&&&struct&epoll_event&epv&=&{0,&{0}};&&&&&43.&&&&&int&&&&&&44.&&&&&epv.data.ptr&=&&&&&&45.&&&&&epv.events&=&ev-&events&=&&&&&&46.&&&&&if(ev-&status&==&1){&&&&&47.&&&&&&&&&op&=&EPOLL_CTL_MOD;&&&&&48.&&&&&}&&&&&49.&&&&&else{&&&&&50.&&&&&&&&&op&=&EPOLL_CTL_ADD;&&&&&51.&&&&&&&&&ev-&status&=&1;&&&&&52.&&&&&}&&&&&53.&&&&&if(epoll_ctl(epollFd,&op,&ev-&fd,&&epv)&&&0)&&&&&54.&&&&&&&&&printf("Event&Add&failed[fd=%d]/n",&ev-&fd);&&&&&55.&&&&&else&&&&&56.&&&&&&&&&printf("Event&Add&OK[fd=%d]/n",&ev-&fd);&&&&&57.&}&&&&&58.&//&delete&an&event&from&epoll&&&&&59.&void&EventDel(int&epollFd,&myevent_s&*ev)&&&&&60.&{&&&&&61.&&&&&struct&epoll_event&epv&=&{0,&{0}};&&&&&62.&&&&&if(ev-&status&!=&1)&return;&&&&&63.&&&&&epv.data.ptr&=&&&&&&64.&&&&&ev-&status&=&0;&&&&&65.&&&&&epoll_ctl(epollFd,&EPOLL_CTL_DEL,&ev-&fd,&&epv);&&&&&66.&}&&&&&67.&int&g_epollFd;&&&&&68.&myevent_s&g_Events[MAX_EVENTS+1];&//&g_Events[MAX_EVENTS]&is&used&by&listen&fd&&&&&69.&void&RecvData(int&fd,&int&events,&void&*arg);&&&&&70.&void&SendData(int&fd,&int&events,&void&*arg);&&&&&71.&//&accept&new&connections&from&clients&&&&&72.&void&AcceptConn(int&fd,&int&events,&void&*arg)&&&&&73.&{&&&&&74.&&&&&struct&sockaddr_in&&&&&&75.&&&&&socklen_t&len&=&sizeof(struct&sockaddr_in);&&&&&76.&&&&&int&nfd,&i;&&&&&77.&&&&&//&accept&&&&&78.&&&&&if((nfd&=&accept(fd,&(struct&sockaddr*)&sin,&&len))&==&-1)&&&&&79.&&&&&{&&&&&80.&&&&&&&&&if(errno&!=&EAGAIN&&&&errno&!=&EINTR)&&&&&81.&&&&&&&&&{&&&&&82.&&&&&&&&&&&&&printf("%s:&bad&accept",&__func__);&&&&&83.&&&&&&&&&}&&&&&84.&&&&&&&&&return;&&&&&85.&&&&&}&&&&&86.&&&&&do&&&&&87.&&&&&{&&&&&88.&&&&&&&&&for(i&=&0;&i&&&MAX_EVENTS;&i++)&&&&&89.&&&&&&&&&{&&&&&90.&&&&&&&&&&&&&if(g_Events[i].status&==&0)&&&&&91.&&&&&&&&&&&&&{&&&&&92.&&&&&&&&&&&&&&&&&break;&&&&&93.&&&&&&&&&&&&&}&&&&&94.&&&&&&&&&}&&&&&95.&&&&&&&&&if(i&==&MAX_EVENTS)&&&&&96.&&&&&&&&&{&&&&&97.&&&&&&&&&&&&&printf("%s:max&connection&limit[%d].",&__func__,&MAX_EVENTS);&&&&&98.&&&&&&&&&&&&&break;&&&&&99.&&&&&&&&&}&&&&100.&&&&&&&&&//&set&nonblocking&&&&101.&&&&&&&&&if(fcntl(nfd,&F_SETFL,&O_NONBLOCK)&&&0)&break;&&&&102.&&&&&&&&&//&add&a&read&event&for&receive&data&&&&103.&&&&&&&&&EventSet(&g_Events[i],&nfd,&RecvData,&&g_Events[i]);&&&&104.&&&&&&&&&EventAdd(g_epollFd,&EPOLLIN|EPOLLET,&&g_Events[i]);&&&&105.&&&&&&&&&printf("new&conn[%s:%d][time:%d]/n",&inet_ntoa(sin.sin_addr),&ntohs(sin.sin_port),&g_Events[i].last_active);&&&&106.&&&&&}while(0);&&&&107.&}&&&&108.&//&receive&data&&&&109.&void&RecvData(int&fd,&int&events,&void&*arg)&&&&110.&{&&&&111.&&&&&struct&myevent_s&*ev&=&(struct&myevent_s*)&&&&112.&&&&&int&&&&&113.&&&&&//&receive&data&&&&114.&&&&&len&=&recv(fd,&ev-&buff,&sizeof(ev-&buff)-1,&0);&&&&&&115.&&&&&EventDel(g_epollFd,&ev);&&&&116.&&&&&if(len&&&0)&&&&117.&&&&&{&&&&118.&&&&&&&&&ev-&len&=&&&&&119.&&&&&&&&&ev-&buff[len]&=&'/0';&&&&120.&&&&&&&&&printf("C[%d]:%s/n",&fd,&ev-&buff);&&&&121.&&&&&&&&&//&change&to&send&event&&&&122.&&&&&&&&&EventSet(ev,&fd,&SendData,&ev);&&&&123.&&&&&&&&&EventAdd(g_epollFd,&EPOLLOUT|EPOLLET,&ev);&&&&124.&&&&&}&&&&125.&&&&&else&if(len&==&0)&&&&126.&&&&&{&&&&127.&&&&&&&&&close(ev-&fd);&&&&128.&&&&&&&&&printf("[fd=%d]&closed&gracefully./n",&fd);&&&&129.&&&&&}&&&&130.&&&&&else&&&&131.&&&&&{&&&&132.&&&&&&&&&close(ev-&fd);&&&&133.&&&&&&&&&printf("recv[fd=%d]&error[%d]:%s/n",&fd,&errno,&strerror(errno));&&&&134.&&&&&}&&&&135.&}&&&&136.&//&send&data&&&&137.&void&SendData(int&fd,&int&events,&void&*arg)&&&&138.&{&&&&139.&&&&&struct&myevent_s&*ev&=&(struct&myevent_s*)&&&&140.&&&&&int&&&&&141.&&&&&//&send&data&&&&142.&&&&&len&=&send(fd,&ev-&buff,&ev-&len,&0);&&&&143.&&&&&ev-&len&=&0;&&&&144.&&&&&EventDel(g_epollFd,&ev);&&&&145.&&&&&if(len&&&0)&&&&146.&&&&&{&&&&147.&&&&&&&&&//&change&to&receive&event&&&&148.&&&&&&&&&EventSet(ev,&fd,&RecvData,&ev);&&&&149.&&&&&&&&&EventAdd(g_epollFd,&EPOLLIN|EPOLLET,&ev);&&&&150.&&&&&}&&&&151.&&&&&else&&&&152.&&&&&{&&&&153.&&&&&&&&&close(ev-&fd);&&&&154.&&&&&&&&&printf("recv[fd=%d]&error[%d]/n",&fd,&errno);&&&&155.&&&&&}&&&&156.&}&&&&157.&void&InitListenSocket(int&epollFd,&short&port)&&&&158.&{&&&&159.&&&&&int&listenFd&=&socket(AF_INET,&SOCK_STREAM,&0);&&&&160.&&&&&fcntl(listenFd,&F_SETFL,&O_NONBLOCK);&//&set&non-blocking&&&&161.&&&&&printf("server&listen&fd=%d/n",&listenFd);&&&&162.&&&&&EventSet(&g_Events[MAX_EVENTS],&listenFd,&AcceptConn,&&g_Events[MAX_EVENTS]);&&&&163.&&&&&//&add&listen&socket&&&&164.&&&&&EventAdd(epollFd,&EPOLLIN|EPOLLET,&&g_Events[MAX_EVENTS]);&&&&165.&&&&&//&bind&&&listen&&&&166.&&&&&sockaddr_in&&&&&167.&&&&&bzero(&sin,&sizeof(sin));&&&&168.&&&&&sin.sin_family&=&AF_INET;&&&&169.&&&&&sin.sin_addr.s_addr&=&INADDR_ANY;&&&&170.&&&&&sin.sin_port&=&htons(port);&&&&171.&&&&&bind(listenFd,&(const&sockaddr*)&sin,&sizeof(sin));&&&&172.&&&&&listen(listenFd,&5);&&&&173.&}&&&&174.&int&main(int&argc,&char&**argv)&&&&175.&{&&&&176.&&&&&short&port&=&12345;&//&default&port&&&&177.&&&&&if(argc&==&2){&&&&178.&&&&&&&&&port&=&atoi(argv[1]);&&&&179.&&&&&}&&&&180.&&&&&//&create&epoll&&&&181.&&&&&g_epollFd&=&epoll_create(MAX_EVENTS);&&&&182.&&&&&if(g_epollFd&&=&0)&printf("create&epoll&failed.%d/n",&g_epollFd);&&&&183.&&&&&//&create&&&bind&listen&socket,&and&add&to&epoll,&set&non-blocking&&&&184.&&&&&InitListenSocket(g_epollFd,&port);&&&&185.&&&&&//&event&loop&&&&186.&&&&&struct&epoll_event&events[MAX_EVENTS];&&&&187.&&&&&printf("server&running:port[%d]/n",&port);&&&&188.&&&&&int&checkPos&=&0;&&&&189.&&&&&while(1){&&&&190.&&&&&&&&&//&a&simple&timeout&check&here,&every&time&100,&better&to&use&a&mini-heap,&and&add&timer&event&&&&191.&&&&&&&&&long&now&=&time(NULL);&&&&192.&&&&&&&&&for(int&i&=&0;&i&&&100;&i++,&checkPos++)&//&doesn't&check&listen&fd&&&&193.&&&&&&&&&{&&&&194.&&&&&&&&&&&&&if(checkPos&==&MAX_EVENTS)&checkPos&=&0;&//&recycle&&&&195.&&&&&&&&&&&&&if(g_Events[checkPos].status&!=&1)&continue;&&&&196.&&&&&&&&&&&&&long&duration&=&now&-&g_Events[checkPos].last_&&&&197.&&&&&&&&&&&&&if(duration&&=&60)&//&60s&timeout&&&&198.&&&&&&&&&&&&&{&&&&199.&&&&&&&&&&&&&&&&&close(g_Events[checkPos].fd);&&&&200.&&&&&&&&&&&&&&&&&printf("[fd=%d]&timeout[%d--%d]./n",&g_Events[checkPos].fd,&g_Events[checkPos].last_active,&now);&&&&201.&&&&&&&&&&&&&&&&&EventDel(g_epollFd,&&g_Events[checkPos]);&&&&202.&&&&&&&&&&&&&}&&&&203.&&&&&&&&&}&&&&204.&&&&&&&&&//&wait&for&events&to&happen&&&&205.&&&&&&&&&int&fds&=&epoll_wait(g_epollFd,&events,&MAX_EVENTS,&1000);&&&&206.&&&&&&&&&if(fds&&&0){&&&&207.&&&&&&&&&&&&&printf("epoll_wait&error,&exit/n");&&&&208.&&&&&&&&&&&&&break;&&&&209.&&&&&&&&&}&&&&210.&&&&&&&&&for(int&i&=&0;&i&&&&i++){&&&&211.&&&&&&&&&&&&&myevent_s&*ev&=&(struct&myevent_s*)events[i].data.&&&&212.&&&&&&&&&&&&&if((events[i].events&EPOLLIN)&&(ev-&events&EPOLLIN))&//&read&event&&&&213.&&&&&&&&&&&&&{&&&&214.&&&&&&&&&&&&&&&&&ev-&call_back(ev-&fd,&events[i].events,&ev-&arg);&&&&215.&&&&&&&&&&&&&}&&&&216.&&&&&&&&&&&&&if((events[i].events&EPOLLOUT)&&(ev-&events&EPOLLOUT))&//&write&event&&&&217.&&&&&&&&&&&&&{&&&&218.&&&&&&&&&&&&&&&&&ev-&call_back(ev-&fd,&events[i].events,&ev-&arg);&&&&219.&&&&&&&&&&&&&}&&&&220.&&&&&&&&&}&&&&221.&&&&&}&&&&222.&&&&&//&free&resource&&&&223.&&&&&return&0;&&&&224.&}&&&&&&转载自:1、http://blog.csdn.net/sparkliang/archive//4770655.aspx2、.cn/s/blog_00bkrj.html近日又继续学习了一下EPOLL的工作模式,这会基本上搞清楚了,因而撰写了此篇文档进行描述。先来一段网上的介绍文档:EPOLL事件分发系统可以运转在两种模式下:Edge Triggered (ET)、Level Triggered (LT)。LT是缺省的工作方式,并且同时支持block和no-block socket;在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就 绪),内核不会发送更多的通知。后面才是我想说的内容,既然ET模式是高速模式,那我们进行服务器开发是一定要使用的了,可是查遍文档,也没有找到ET模式的设置方法,到底如何设置和使 用呢?通过反复测试,终于搞明白“EPOLLET”就是ET模式的设置了,也许是我太笨所以才迷惑这么久了,以下就是将TCP套接字hSocket和 epoll关联起来的代码:struct epoll_event struEstruEvent.events = EPOLLIN | EPOLLOUT | EPOLLET;struEvent.data.fd = hS&&& epoll_ctl(m_hEpoll, EPOLL_CTL_ADD, hSocket, &struEvent);如果将监听套接字m_hListenSocket和epoll关联起来,则代码如下:struct epoll_event struEstruEvent.events = EPOLLIN | EPOLLET;struEvent.data.fd = m_hListenS&&& epoll_ctl(m_hEpoll, EPOLL_CTL_ADD, m_hListenSocket, &struEvent);如果想使用LT模式,直接把事件的赋值修改为以下即可,也许这就是缺省的意义吧。struEvent.events = EPOLLIN | EPOLLOUT; //用户TCP套接字struEvent.events = EPOLLIN;&&&& //监听TCP套接字不过,通过我的测试确定,这两种模式的性能差距还是非常大的,最大可以达到10倍。100个连接的压力测试,其他环境都相同,LT模式CPU消耗99%、ET模式15%。在man epoll中的Notes说到:EPOLL事件分发系统可以运转在两种模式下:&& Edge Triggered (ET)&& Level Triggered (LT)接下来说明ET, LT这两种事件分发机制的不同。我们假定一个环境:1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符2. 这个时候从管道的另一端被写入了2KB的数据3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作4. 然后我们读取了1KB的数据5. 调用epoll_wait(2)......Edge Triggered 工作模式:如果我们在第1步将RFD添加到epoll描述符的时候使用了EPOLLET标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件。因此在第5步的时候,调用者可能会放弃等待仍在存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,事件将会在第3步被销毁。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。&& i&&& 基于非阻塞文件句柄&& ii&& 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待Level Triggered 工作模式相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。以上翻译自man epoll.然后详细解释ET, LT:LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.ET(edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。在许多测试中我们会看到如果没有大量的idle-connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle-connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。其他细节:1、为什么select是落后的?首先,在Linux内核中,select所用到的FD_SET是有限的,即内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数,在我用的2.6.15-25-386内核中,该值是1024,搜索内核源代码得到:include/linux/posix_types.h:#define __FD_SETSIZE && &&& 1024也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的。或者同时检测1025个句柄的可写状态也是不可能的。其次,内核中实现select是用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,显然,select函数执行时间与FD_SET中的句柄个数有一个比例关系,即select要检测的句柄数越多就会越费时。当然,在前文中我并没有提及poll方法,事实上用select的朋友一定也试过poll,我个人觉得select和poll大同小异,个人偏好于用select而已。、2.6内核中提高I/O性能的epollepoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44)(1)导言:首先,我强烈建议大家阅读Richard Stevens著作《TCP/IP Illustracted Volume 1,2,3》和《UNIX Network Programming Volume 1,2》。虽然他离开我们大家已经5年多了,但是他的书依然是进入网络编程的最直接的道路。其中的3卷的《TCP/IP Illustracted》卷1是必读-如果你不了解tcp协议各个选项的详细定义,你就失去了优化程序重要的一个手段。卷2,3可以选读一下。比如卷2 讲解的是4.4BSD内核TCP/IP协议栈实现----这个版本的协议栈几乎影响了现在所有的主流os,但是因为年代久远,内容不一定那么vogue. 在这里我多推荐一本《The Linux Networking Architecture--Design and Implementation of Network Protocols in the Linux Kernel》,以2.4内核讲解Linux TCP/IP实现,相当不错.作为一个现实世界中的实现,很多时候你必须作很多权衡,这时候参考一个久经考验的系统更有实际意义。举个例子,linux内核中sk_buff结构为了追求速度和安全,牺牲了部分内存,所以在发送TCP包的时候,无论应用层数据多大,sk_buff最小也有272的字节.其实对于socket应用层程序来说,《UNIX Network Programming Volume 1》意义更大一点.2003年的时候,这本书出了最新的第3版本,不过主要还是修订第2版本。其中第6章《I/O Multiplexing》是最重要的。Stevens给出了网络IO的基本模型。在这里最重要的莫过于select模型和Asynchronous I/O模型.从理论上说,AIO似乎是最高效的,你的IO操作可以立即返回,然后等待os告诉你IO操作完成。但是一直以来,如何实现就没有一个完美的方案。最著名的windows完成端口实现的AIO,实际上也是内部用线程池实现的罢了,最后的结果是IO有个线程池,你应用也需要一个线程池...... 很多文档其实已经指出了这带来的线程context-switch带来的代价。在linux 平台上,关于网络AIO一直是改动最多的地方,2.4的年代就有很多AIO内核patch,最著名的应该算是SGI那个。但是一直到2.6内核发布,网络模块的AIO一直没有进入稳定内核版本(大部分都是使用用户线程模拟方法,在使用了NPTL的linux上面其实和windows的完成端口基本上差不多了)。2.6内核所支持的AIO特指磁盘的AIO---支持io_submit(),io_getevents()以及对Direct IO的支持(就是绕过VFS系统buffer直接写硬盘,对于流服务器在内存平稳性上有相当帮助)。所以,剩下的select模型基本上就是我们在linux上面的唯一选择,其实,如果加上no-block socket的配置,可以完成一个"伪"AIO的实现,只不过推动力在于你而不是os而已。不过传统的select/poll函数有着一些无法忍受的缺点,所以改进一直是2.4-2.5开发版本内核的任务,包括/dev/poll,realtime signal等等。最终,Davide Libenzi开发的epoll进入2.6内核成为正式的解决方案(2)epoll的优点&1&支持一个进程打开大数目的socket描述符(FD)select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。&2&IO效率不随FD数目增加而线性下降传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。&3&使用mmap加速内核与用户空间的消息传递。这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。&4&内核微调这一点其实不算epoll的优点了,而是整个linux平台的优点。也许你可以怀疑linux平台,但是你无法回避linux平台赋予你微调内核的能力。比如,内核TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存pool(skb_head_pool)的大小--- 通过echo XXXX&/proc/sys/net/core/hot_list_length完成。再比如listen函数的第2个参数(TCP完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。更甚至在一个数据包面数目巨大但同时每个数据包本身大小却很小的特殊系统上尝试最新的NAPI网卡驱动架构。(3)epoll的使用令人高兴的是,2.6内核的epoll比其2.5开发版本的/dev/epoll简洁了许多,所以,大部分情况下,强大的东西往往是简单的。唯一有点麻烦是epoll有2种工作方式:LT和ET。LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。epoll是多路复用IO(I/O Multiplexing)中的一种方式,但是仅用于linux2.6以上内核,在开始讨论这个问题之前,先来解释一下为什么需要多路复用IO.以一个生活中的例子来解释.假设你在大学中读书,要等待一个朋友来访,而这个朋友只知道你在A号楼,但是不知道你具体住在哪里,于是你们约好了在A号楼门口见面.如果你使用的阻塞IO模型来处理这个问题,那么你就只能一直守候在A号楼门口等待朋友的到来,在这段时间里你不能做别的事情,不难知道,这种方式的效率是低下的.现在时代变化了,开始使用多路复用IO模型来处理这个问题.你告诉你的朋友来了A号楼找楼管大妈,让她告诉你该怎么走.这里的楼管大妈扮演的就是多路复用IO的角色.进一步解释select和epoll模型的差异.select版大妈做的是如下的事情:比如同学甲的朋友来了,select版大妈比较笨,她带着朋友挨个房间进行查询谁是同学甲,你等的朋友来了,于是在实际的代码中,select版大妈做的是以下的事情:int&n&=&select(&readset,NULL,NULL,100);for&(int&i&=&0;&n&&&0;&++i){&&&if&(FD_ISSET(fdarray[i],&&readset))&&&{&&&&&&do_something(fdarray[i]);&&&&& --n;&&&}}epoll版大妈就比较先进了,她记下了同学甲的信息,比如说他的房间号,那么等同学甲的朋友到来时,只需要告诉该朋友同学甲在哪个房间即可,不用自己亲自带着人满大楼的找人了.于是epoll版大妈做的事情可以用如下的代码表示:n=epoll_wait(epfd,events,20,500);&&&&for(i=0;i&n;++i){&&&&do_something(events[n]);}在epoll中,关键的数据结构epoll_event定义如下:typedef&union&epoll_data&{&&&&&&&&&&&&&&&&void&*&&&&&&&&&&&&&&&&int&&&&&&&&&&&&&&&&&__uint32_t&u32;&&&&&&&&&&&&&&&&__uint64_t&u64;&&&&&&&&}&epoll_data_t;&&&&&&&&struct&epoll_event&{&&&&&&&&&&&&&&&&__uint32_t&&&&&&&/*&Epoll&events&*/&&&&&&&&&&&&&&&&epoll_data_t&&&&&&&/*&User&data&variable&*/&&&&&&&&};&可以看到,epoll_data是一个union结构体,它就是epoll版大妈用于保存同学信息的结构体,它可以保存很多类型的信息:fd,指针,等等.有了这个结构体,epoll大妈可以不用吹灰之力就可以定位到同学甲.别小看了这些效率的提高,在一个大规模并发的服务器中,轮询IO是最耗时间的操作之一.再回到那个例子中,如果每到来一个朋友楼管大妈都要全楼的查询同学,那么处理的效率必然就低下了,过不久楼底就有不少的人了.对比最早给出的阻塞IO的处理模型, 可以看到采用了多路复用IO之后, 程序可以自由的进行自己除了IO操作之外的工作, 只有到IO状态发生变化的时候由多路复用IO进行通知, 然后再采取相应的操作, 而不用一直阻塞等待IO状态发生变化了.从上面的分析也可以看出,epoll比select的提高实际上是一个用空间换时间思想的具体应用.
TA的最新馆藏

我要回帖

更多关于 压力表三通针型阀 的文章

 

随机推荐