C语言多路复用技术

上传人:xuey****n398 文档编号:165461806 上传时间:2022-10-28 格式:DOCX 页数:12 大小:25.16KB
收藏 版权申诉 举报 下载
C语言多路复用技术_第1页
第1页 / 共12页
C语言多路复用技术_第2页
第2页 / 共12页
C语言多路复用技术_第3页
第3页 / 共12页
资源描述:

《C语言多路复用技术》由会员分享,可在线阅读,更多相关《C语言多路复用技术(12页珍藏版)》请在装配图网上搜索。

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 *readfds, fd_set *writefds, fd_

2、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_set最大可以对应8个fd。(1)执行fd_set

3、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(fd_set)的值。我这边服务器上sizeof(fd_set)512,每

4、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最大值maxfd,用于select的第一个 参数。(3)可见select模型必

5、须在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)res=select(maxfd+1,&set,0,0,0);if(FD_ISSET(lis

6、ten_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解决方案。它解决了select()的几个不足,尽管select()仍然经常使用(多数还是出于习惯,或者打着可移植的名义):poll的原型#incl

7、ude int poll (struct pollfd *fds, unsigned int nfds, int timeout);和select()不一样,poll()没有使用低效的三个基于位的文件描述符set,而是采用了一个单独的结构体pollfd数组,由fds指针指向这个组。pollfd结构体定义如下:#include struct pollfd int fd;short events;short revents;每一个pollfd结构体指定了一个被监视的文件描述符,可以传递多个结构体,指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码,由用户来设

8、置这个域。revents域是文件描述符的操作结果事件掩码。内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下:POLLIN有数据可读。POLLRDNORM有普通数据可读。POLLRDBAND有优先数据可读。POLLPRI有紧迫数据可读。POLLOUT写数据不会导致阻塞。POLLWRNORM写普通数据不会导致阻塞。POLLWRBAND写优先数据不会导致阻塞。POLLMSGSIGPOLL消息可用。此外,revents域中还可能返回下列事件:POLLER指定的文件描述符发生错误。POLLHUP指定的文件描述符挂起事件。POLLNVAL指定的文件描

9、述符非法。这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。使用poll()和select()不一样,你不需要显式地请求异常情况报告。POLLIN | POLLPRI等价于select()的读事件,POLLOUT | POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM | POLLRDBAND,而POLLOUT则等价于POLLWRNORM。例如,要同时监视一个文件描述符是否可读和可写,我们可以设置events为POLLIN | POLLOUT。在poll返回时,我们可以检查revents中的标志,对应于文件描述符请求的e

10、vents结构体。如果POLLIN事件被设置,则文件描述符可以被读取而不阻塞。如果POLLOUT被设置,则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的:它们可能被同时设置,表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。timeout参数指定等待的毫秒数,无论I/O是否准备好,poll都会返回。timeout指定为负数值表示无限超时;timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符,但并不等待其它的事件。这种情况下,poll()就像它的名字那样,一旦选举出来,立即返回。返回值和错误代码成功时,poll()返回结构体中revents域不为0的文件描述符个数

11、;如果在超时前没有任何事件发生,poll()返回0;失败时,poll()返回-1,并设置errno为下列值之一:EBADF一个或多个结构体中指定的文件描述符无效。EFAULTfds指针指向的地址超出进程的地址空间。EINTR请求的事件之前产生一个信号,调用可以重新发起。EINVALnfds参数超出PLIMIT_NOFILE值。ENOMEM可用内存不足,无法完成请求。poll的服务器模型伪码:struct pollfd fdsPOLL_LEN;unsigned int nfds=0;fds0.fd=server_sockfd;fds0.events=POLLIN|POLLPRI;nfds+;wh

12、ile res=poll(fds,nfds,-1);if(fds0.revents&(POLLIN|POLLPRI) 执行accept并加入fds中,if(-res=0)continue循环之后的fds,if(fdsi.revents&(POLLIN|POLLERR ) 操作略if(-respoll() struct list_head rdllist; / 已经完成的操作事件的队列。 struct rb_root rbr; / 保存epoll监视的文件描述符 ; 这个结构体保存了epoll文件描述符的扩展信息,它被保存在file结构体的private_data中。它与epoll文件节点一一对

13、应。通常一个epoll文件节点对应多个被监视的文件描述符。所以一个eventpoll结构体会对应多个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 w

14、ait_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_entry stru

15、ct 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分配一个dentry

16、。最后把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节点关联起来。这里要介绍一下eventpoll结构体,它保存

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

18、对应的红黑树。此外,这个函数还要作一个操作,就是把当前进程放入对应文件操作的等待队列。这一步是由下面的代码完成的。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:一方面把它挂到文件操作的等待队列中,另一方面把它挂到epitem的队列中。此外,它还注册了

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

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

21、测试。(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_epfd, EPOLL_CTL_ADD, rc, &ev) 0) fpri

22、ntf(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 sock_new) fprintf(stderr, 接收客户端连接失败n);continue;epoll的优点:1.支持一个进程打开大数目的socket描述符(FD)sele

23、ct 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体

24、数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。2.IO效率不随FD数目增加而线性下降传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是活跃的,但是select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对活跃的socket进行操作-这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有活跃的socket才会主动的去调用 callback函数,其他idle状态socket则

25、不会,在这点上,epoll实现了一个伪AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的-比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle connections模拟WAN环境,epoll的效率就远在select/poll之上了。3.使用mmap加速内核与用户空间的消息传递。这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重要,在这点上,

26、epoll是通过内核于用户空间mmap同一块内存实现的。而如果你想我一样从2.5内核就关注epoll的话,一定不会忘记手工 mmap这一步的。port模型port则和epoll非常接近,不需要前后的两次扫描,直接返回有事件的结果,可以象epoll一样绑定指针,不同点是(1)epoll可以返回多个事件,而port一次只返回一个(port_getn可以返回多个,但是在不到指定的n值时,等待直到达到n个)(2)port返回的结果会自动port_dissociate,如果要再次监控,需要重新port_associate这个就不多说了。可以看出select-poll-epoll/port的演化路线:(1)从readset、writeset等分离到 将读写事件集中到统一的结构(2)从阻塞操作前后的两次循环 到 之后的一次循环 到精确返回有事件发生的fd(3)从只能绑定fd信息,到可以绑定指针结构信息在以前我没有接触过多路复用这些东西,只是有这个概念,也没有具体的理解和学习过,上面的一些东西也是看一些书和网上找的。通过这次的学习,对复用和复用在socket中的应用有了更新的认识和理解。也为以后进一步的应用与开发打开了一定的思路,也提供了又一种途径。还有很多其他的复用技术,我就没有具体去了解了。在这里就介绍如上的几种,以供我们今后应用

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

copyright@ 2023-2025  zhuangpeitu.com 装配图网版权所有   联系电话:18123376007

备案号:ICP2024067431-1 川公网安备51140202000466号


本站为文档C2C交易模式,即用户上传的文档直接被用户下载,本站只是中间服务平台,本站所有文档下载所得的收益归上传人(含作者)所有。装配图网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对上载内容本身不做任何修改或编辑。若文档所含内容侵犯了您的版权或隐私,请立即通知装配图网,我们立即给予删除!