C语言多路复用技术_第1页
C语言多路复用技术_第2页
C语言多路复用技术_第3页
C语言多路复用技术_第4页
C语言多路复用技术_第5页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、文档供参考,可复制、编制,期待您的好评与关注! linux下的常见多路复用技术C语言post by 陈俊生 / 2012-1-4 9:25 Wednesday要实现I/O多路复用有很多的方式,其中可以用进程或者是线程等来实现,也可以用select/poll /epoll/port等来实现。在相比两者之间,利用select/poll /epoll/port等来实现复用效率更快,实现更容易,在底层的需要的资源更少,效率更快。下面就介绍一下select/poll /epoll/port等的具体用法。Select的用法。select原型:int select(int n, fd_set *readfd

2、s, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);其中参数n表示监控的所有fd中最大值1。和select模型紧密结合的四个宏,含义不解释了:FD_CLR(int fd, fd_set *set);FD_ISSET(int fd, fd_set *set);FD_SET(int fd, fd_set *set);FD_ZERO(fd_set *set);理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则 1字节长的fd_se

3、t最大可以对应8个fd。(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。(2)若fd5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)(3)若再加入fd2,fd=1,则set变为0001,0011(4)执行select(6,&set,0,0,0)阻塞等待(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。基于上面的讨论,可以轻松得出select模型的特点:(1)可监控的文件描述符个数取决与sizeof(

4、fd_set)的值。我这边服务器上sizeof(fd_set)512,每bit表示一个文件描述 符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回 后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值

5、maxfd,用于select的第一个 参数。(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否 有时间发生)。下面给一个伪码说明基本select模型的服务器模型:arrayslect_len;nSock=0;arraynSock+=listen_fd;(之前listen port已绑定并listen)maxfd=listen_fd;while FD_ZERO(&set);foreach (fd in array) fd大于maxfd,则maxfd=fdFD_SET(fd,&set)re

6、s=select(maxfd+1,&set,0,0,0);if(FD_ISSET(listen_fd,&set)newfd=accept(listen_fd);arraynsock+=newfd;if(-res<=0) continueforeach 下标1开始 (fd in array) if(FD_ISSET(fd,&set)执行读等相关操作如果错误或者关闭,则要删 除该fd,将array中相应位置和最后一个元素互换就好,nsock减一if(-res<=0) continuepoll模型poll()系统调用是System V的多元I/O解决方案。它解决了

7、select()的几个不足,尽管select()仍然经常使用(多数还是出于习惯,或者打着可移植的名义):poll的原型#include <sys/poll.h>int poll (struct pollfd *fds, unsigned int nfds, int timeout);和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构体定义如下:#include <sys/poll.h>struct pollfd int fd;short events;sh

8、ort revents;每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设置这个域。revents域是文件描述符的操作结果事件掩码。内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:POLLIN有数据可读。POLLRDNORM有普通数据可读。POLLRDBAND有优先数据可读。POLLPRI有紧迫数据可读。POLLOUT写数据不会导致阻塞。POLLWRNORM写普通数据不会导致阻塞。POLLWRBAND写优先数据不

9、会导致阻塞。POLLMSGSIGPOLL消息可用。此外,revents域中还可能返回下列事件:POLLER指定的文件描述符发生错误。POLLHUP指定的文件描述符挂起事件。POLLNVAL指定的文件描述符非法。这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。使用poll()和select()不一样,你不需要显式地请求异常情况报告。POLLIN | POLLPRI等价于select()的读事件,POLLOUT | POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM | POLLRDBAND,而POLLOUT则等价于POL

10、LWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置events为POLLIN | POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的events结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时;timeout为0指示poll调用立即返回并列出

11、准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。返回值和错误代码成功时,poll()返回结构体中revents域不为0的文件描述符个数;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:EBADF一个或多个结构体中指定的文件描述符无效。EFAULTfds指针指向的地址超出进程的地址空间。EINTR请求的事件之前产生一个信号,调用可以重新发起。EINVALnfds参数超出PLIMIT_NOFILE值。ENOMEM可用内存不足,无法完成请求。poll的服务器模型伪码:str

12、uct pollfd fdsPOLL_LEN;unsigned int nfds=0;fds0.fd=server_sockfd;fds0.events=POLLIN|POLLPRI;nfds+;while res=poll(fds,nfds,-1);if(fds0.revents&(POLLIN|POLLPRI) 执行accept并加入fds中,if(-res<=0)continue循环之后的fds,if(fdsi.revents&(POLLIN|POLLERR ) 操作略if(-res<=0)continue注意select和poll中res的检测,可有效减少循

13、环的次数,这也是大量死连接存在时,select和poll性能下降厉害的原因。epoll模型epoll是什么?2.6内核中提高I/O性能的新方法。按照man手册的说法:是为处理大批量句柄而作了改进的poll。要使用epoll只需要这三个系统调用:epoll_create(2), epoll_ctl(2), epoll_wait(2)。唯一有点麻烦是epoll有2种工作方式:LT和ET。LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任

14、何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不

15、会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。(1)功能介绍epoll与select/poll不同的一点是,它是由一组系统调用组成。int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); epoll相关系统调用是在Linux 2.5

16、.44开始引入的。该系统调用针对传统的select/poll系统调用的不足,设计上作了很大的改动。select/poll的缺点在于:1.每次调用时要重复地从用户态读入参数。2.每次调用时要重复地扫描文件描述符。3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll显得不够高效。epoll的设计思路,是把select/poll单个的操作拆分为1个epoll_create+多个epoll_ctl+一个epoll_wa

17、it。此外,内核针对epoll操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的文件描述符都有一个对应的eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中。而被监视的文件的重要信息则保存在epitem结构体中。所以他们是一对多的关系。由于在执行epoll_create和epoll_ctrl时,已经把用户态的信息保存到内核态了,所以之后即使反复地调用epoll_wait,也不会重复地拷贝参数,扫描文件描述符,反复地把当前进程放入/放出等待队列。这样就避免了以上的三个缺点。接下去看看它们的实现:struct ep_pqueue poll_

18、table pt; struct epitem *epi; ; (2) 关键结构体这个结构体类似于select/poll中的struct poll_wqueues。由于epoll需要在内核态保存大量信息,所以光光一个回调函数指针已经不能满足要求,所以在这里引入了一个新的结构体struct epitem。struct epitem struct rb_node rbn; / 红黑树,用来保存eventpoll struct list_head rdllink; / 双向链表,用来保存已经完成的eventpoll struct epoll_filefd ffd; / 这个结构体对应的被监听的文件描

19、述符信息 int nwait; / poll操作中事件的个数 / 双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table struct list_head pwqlist; struct eventpoll *ep; / 指向eventpoll,多个epitem对应一个eventpoll struct epoll_event event; / 记录发生的事件和对应的fd atomic_t usecnt; / 引用计数 / 双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link, / 用来保存所有监视这个文件的

20、epoll节点 struct list_head fllink; struct list_head txlink; / 双向链表,用来保存传输队列 unsigned int revents; / 文件描述符的状态,在收集和传输时用来锁住空的事件集合 ; 该结构体用来保存与epoll节点关联的多个文件描述符,保存的方式是使用红黑树实现的hash表。至于为什么要保存,下文有详细解释。它与被监听的文件描述符一一对应。view plainstruct eventpoll rwlock_t lock; / 读写锁 struct rw_semaphore sem; / 读写信号量 wait_queue_h

21、ead_t wq; / Wait queue used by sys_epoll_wait() wait_queue_head_t poll_wait; / Wait queue used by file->poll() struct list_head rdllist; / 已经完成的操作事件的队列。 struct rb_root rbr; / 保存epoll监视的文件描述符 ; 这个结构体保存了epoll文件描述符的扩展信息,它被保存在file结构体的private_data中。它与epoll文件节点一一对应。通常一个epoll文件节点对应多个被监视的文件描述符。所以一个eventp

22、oll结构体会对应多个epitem结构体。那么,epoll中的等待事件放在哪里呢?见下面/ Wait structure used by the poll hooks struct eppoll_entry struct list_head llink; / List header used to link this structure to the "struct epitem" void *base; / The "base" pointer is set to the container "struct epitem" wait

23、_queue_t wait; / Wait queue item that will be linked to the target file wait queue head. wait_queue_head_t *whead; / The wait queue head that linked the "wait" wait queue item ; 与select/poll的struct poll_table_entry相比,epoll的表示等待队列节点的结构体只是稍有不同,与struct poll_table_entry比较一下。struct poll_table_e

24、ntry struct file * filp; wait_queue_t wait; wait_queue_head_t * wait_address; ; 由于epitem对应一个被监视的文件,所以通过base可以方便地得到被监视的文件信息。又因为一个文件可能有多个事件发生,所以用llink链接这些事件。(3)epoll_create的实现epoll_create()的功能是创建一个eventpollfs文件系统的inode节点。具体由ep_getfd()完成。ep_getfd()先调用ep_eventpoll_inode()创建一个inode节点,然后调用d_alloc()为inode分

25、配一个dentry。最后把file,dentry,inode三者关联起来。在执行了ep_getfd()之后,它又调用了ep_file_init(),分配了eventpoll结构体,并把eventpoll的指针赋给file结构体,这样eventpoll就与file结构体关联起来了。需要注意的是epoll_create()的参数size实际上只是起参考作用,只要它不小于等于0,就并不限制这个epoll inode关联的文件描述符数量。(4)epoll_ctl的实现epoll_ctl的功能是实现一系列操作,如把文件与eventpollfs文件系统的inode节点关联起来。这里要介绍一下eventpo

26、ll结构体,它保存在file->f_private中,记录了eventpollfs文件系统的inode节点的重要信息,其中成员rbr保存了该epoll文件节点监视的所有文件描述符。组织的方式是一棵红黑树,这种结构体在查找节点时非常高效。首先它调用ep_find()从eventpoll中的红黑树获得epitem结构体。然后根据op参数的不同而选择不同的操作。如果op为EPOLL_CTL_ADD,那么正常情况下epitem是不可能在eventpoll的红黑树中找到的,所以调用ep_insert创建一个epitem结构体并插入到对应的红黑树中。 ep_insert()首先分配一个epitem对

27、象,对它初始化后,把它放入对应的红黑树。此外,这个函数还要作一个操作,就是把当前进程放入对应文件操作的等待队列。这一步是由下面的代码完成的。init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);。revents = tfile->f_op->poll(tfile, &epq.pt);函数先调用init_poll_funcptr注册了一个回调函数 ep_ptable_queue_proc,这个函数会在调用f_op->poll时被执行。该函数分配一个epoll等待队列结点eppoll_entry:一方面把它挂到文件操作的

28、等待队列中,另一方面把它挂到epitem的队列中。此外,它还注册了一个等待队列的回调函数ep_poll_callback。当文件操作完成,唤醒当前进程之前,会调用ep_poll_callback(),把eventpoll放到epitem的完成队列中,并唤醒等待进程。 如果在执行f_op->poll以后,发现被监视的文件操作已经完成了,那么把它放在完成队列中了,并立即把等待操作的那些进程唤醒。(5)epoll_wait的实现epoll_wait的工作是等待文件操作完成并返回。它的主体是ep_poll(),该函数在for循环中检查epitem中有没有已经完成的事件,有的话就把结果返回。没有的

29、话调用schedule_timeout()进入休眠,直到进程被再度唤醒或者超时。(6)性能分析epoll机制是针对select/poll的缺陷设计的。通过新引入的eventpollfs文件系统,epoll把参数拷贝到内核态,在每次轮询时不会重复拷贝。通过把操作拆分为epoll_create,epoll_ctl,epoll_wait,避免了重复地遍历要监视的文件描述符。此外,由于调用epoll的进程被唤醒后,只要直接从epitem的完成队列中找出完成的事件,找出完成事件的复杂度由O(N)降到了O(1)。但是epoll的性能提高是有前提的,那就是监视的文件描述符非常多,而且每次完成操作的文件非常少

30、。所以,epoll能否显著提高效率,取决于实际的应用场景。这方面需要进一步测试。(7)epoll的例子以下代码由上BBS用户safedead(static int s_epfd;/epoll描述字/初始化epollstruct epoll_event ev;/设置epolls_epfd = epoll_create(65535);/这个过程可以循环以便加入多个LISTEN套接字进入epoll事件集合/服务器监听创建rc = listen();/listen参数这里省略/加入epoll事件集合ev.events = EPOLLIN;ev.data.fd = rc;if (epoll_ctl(s_

31、epfd, EPOLL_CTL_ADD, rc, &ev) < 0) fprintf(stderr, "epoll set insertion error: fd=%d", rc);return(-1);/epoll事件处理int i, nfds, sock_new;struct epoll_event events16384;for( ; ; ) /等待epoll事件nfds = epoll_wait(s_epfd, events, 16384, -1);/处理epoll事件for(i = 0; i < nfds; i+) /eventsi.data.

32、fd是epoll事件中弹出的套接字/接收连接sock_new = accept(eventsi.data.fd);/accept其它参数这里省略了if(0 > sock_new) fprintf(stderr, "接收客户端连接失败n");continue;epoll的优点:1.支持一个进程打开大数目的socket描述符(FD)select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来

33、网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。2.IO效率不随FD数目增加而线性下降传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的so

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论