CppExplore

一切像雾像雨又像风

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  29 随笔 :: 0 文章 :: 280 评论 :: 0 Trackbacks

作者:CppExplore 网址:http://www.cppblog.com/CppExplore/
多路复用的方式是真正实用的服务器程序,非多路复用的网络程序只能作为学习或着陪测的角色。本文说下个人接触过的多路复用函数:select/poll/epoll/port。kqueue的*nix系统没接触过,估计熟悉了上面四种,kqueue也只是需要熟悉一下而已。
一、select模型
select原型:

int select(int n, fd_set *readfds, 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_set最大可以对应8个fd。
(1)执行fd_set set; FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,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,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。本人对调整fd_set的大小不太感兴趣,参考http://www.cppblog.com/CppExplore/archive/2008/03/21/45061.html中的模型2(1)可以有效突破select可监控的文件描述符上限。
(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模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。

下面给一个伪码说明基本select模型的服务器模型:

array[slect_len];
nSock
=0;
array[nSock
++]=listen_fd;(之前listen port已绑定并listen)
maxfd
=listen_fd;
while{
   FD_ZERO(
&set);
   foreach (fd in array) 
   
{
       fd大于maxfd,则maxfd
=fd
       FD_SET(fd,
&set)
   }

   res=select(maxfd
+1,&set,0,0,0);
   
if(FD_ISSET(listen_fd,&set))
   
{
       newfd
=accept(listen_fd);
       array[nsock
++]=newfd;
            if(--res<=0) continue
   }

   foreach 下标1开始 (fd in array) 
   
{
       
if(FD_ISSET(fd,&set))
          执行读等相关操作
          如果错误或者关闭,则要删除该fd,将array中相应位置和最后一个元素互换就好,nsock减一
             if(--res<=0) continue

   }

}

二、poll模型
poll原型:

int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
struct pollfd 
{
                       
int fd;           /* file descriptor */
                       
short events;     /* requested events */
                       
short revents;    /* returned events */
               }
;

和select相比,两大改进:
(1)不再有fd个数的上限限制,可以将参数ufds想象成栈低指针,nfds是栈中元素个数,该栈可以无限制增长
(2)引入pollfd结构,将fd信息、需要监控的事件、返回的事件分开保存,则poll返回后不会丢失fd信息和需要监控的事件信息,也就省略了select模型中前面的循环操作,返回后的循环仍然不可避免。另每次poll阻塞操作都会自动把上次的revents清空。
poll的服务器模型伪码:

struct pollfd fds[POLL_LEN];
unsigned 
int nfds=0;
fds[
0].fd=server_sockfd;
fds[
0].events=POLLIN|POLLPRI;
nfds
++;
while{
  res=poll(fds,nfds,
-1);
  
if(fds[0].revents&(POLLIN|POLLPRI)){执行accept并加入fds中,if(--res<=0)continue}
  循环之后的fds,
if(fds[i].revents&(POLLIN|POLLERR )){操作略if(--res<=0)continue}
}
注意select和poll中res的检测,可有效减少循环的次数,这也是大量死连接存在时,select和poll性能下降厉害的原因。

三、epoll模型

epoll阻塞操作的原型:

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
epoll引入了新的结构epoll_event。
typedef union epoll_data 
{
                 
void *ptr;
                 
int fd;
                 __uint32_t u32;
                 __uint64_t u64;
            }
 epoll_data_t;

            struct epoll_event 
{
                 __uint32_t events;  
/* Epoll events */
                 epoll_data_t data;  
/* User data variable */
            }
;

与以上模型的优点:
(1)它保留了poll的两个相对与select的优点
(2)epoll_wait的参数events作为出参,直接返回了有事件发生的fd,epoll_wait的返回值既是发生事件的个数,省略了poll中返回之后的循环操作。
(3)不再象select、poll一样将标识符局限于fd,epoll中可以将标识符扩大为指针,大大增加了epoll模型下的灵活性。
epoll的服务器模型伪码:

epollfd=epoll_create(EPOLL_LEN);
epoll_ctl(epollfd,EPOLL_CTL_ADD,server_sockfd,
&ev)
struct epoll_event events[EPOLL_MAX_EVENT];
while
{
nfds
=epoll_wait(epollfd,events,EPOLL_MAX_EVENT,-1);
循环nfds,是server_sockfd则accept,否则执行响应操作
}

epoll使用中的问题:
(1)epoll_ctl的EPOLL_CTL_DEL操作中,最后一个参数是无意义的,但是在小版本号过低的2.6内核下要求最后一个参数一定非NULL,否则返回失败,并且返回的errno在man epoll_ctl中不存在,因此安全期间,保证epoll_ctl的最后一个参数总非NULLL。
(2)如果一个fd(比如管道)的事件导致了另一个fd2的删除,则必须扫描返回结果集中是否有fd2,有则在结果集中删除,避免冲突。
(3)有文章说epoll在G网环境下性能会低于poll/select,看有些测试,给出的拐点在2w/s并发之后,我本人的工作范围不可能达到这么高的并发,个人在测试性能的时候最大也是取的1w/s的并发,一个是因为系统单进程允许打开的文件描述符最大值,4w的数字太高了,另一个就是我这边服务器的性能达不到那么高的性能,极限1.7w/s的响应,那测试的数据竟然在2w并发的时候还有2w的响应,不知道是什么硬件配置。或许等有了G网的环境,会关注epoll高并发下的性能下降


(4)epoll的LT和ET性能的差异,我测试的数据表明两者性能相当,“使用epoll就是为了高性能,就是要使用ET模式”这个说法是站不住脚的。个人倾向于使用LT模式,编程简单、安全。

四、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信息,到可以绑定指针结构信息

五、抽象接口
综合以上多路复用函数的特点,可以进行统一的封装,这里给出我封装的接口,也算是给一个思路:

 virtual int init()=0;
 virtual 
int wait()=0;
 virtual 
void * next_result()=0;
 virtual 
void delete_from_results(void * data)=0;
 virtual 
void * get_data(void * event)=0;
 virtual 
int get_event(void * event)=0;
 virtual 
int add_data(int fd,XPollData * data)=0;
 virtual 
int delete_data(int fd,XPollData *data)=0;
 virtual 
int change_data(int fd,XPollData *data)=0;
 virtual 
int reset_data(int fd,XPollData *data)=0;

使用的时候就是先init,再wait,再循环执行next_result直到空,每个result,使用get_data和get_event挨个处理,如果某个fd引起另一个fd关闭,调delete_from_results(除epoll,其它都直接return),处理完reset_data(select和port用,poll/epoll直接return)。

posted on 2008-04-30 17:23 cppexplore 阅读(7748) 评论(8)  编辑 收藏 引用

评论

# re: 【原创】系统设计之 网络模型(三)多路复用模型[未登录] 2008-04-30 18:13 true
关注epoll和线程池的结合。  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(三)多路复用模型[未登录] 2008-04-30 18:47 CppExplore
@true
可以在上一篇《网络模型二》http://www.cppblog.com/CppExplore/archive/2008/03/21/45061.html中跟贴讨论新模型,是文中已有的,还是没有的新模型,本文旨在讨论函数本身的差异。
epoll和线程池的结合,不是很理解,是epoll单线程后接业务线程,还是epoll本身线程池?epoll本身的话,我想象不出来,结构在可读状态的话,新线程epoll_wait还是可读。并且线程池要求无内蕴状态,行为的变化来自于外界的输入参数,莫非是《网络模型二》中的模型2(1)?
就epoll而言,《网络模型二》的2模型中,业务线程先不谈,单线程的epoll性能就很强,其他模型的测试结果反而有下降。poll/select则不同。
这个话题最好还是在《网络模型二》中探讨,本文不多扩展。  回复  更多评论
  

# re: 【原创】系统设计之 网络模型(三)多路复用模型[未登录] 2008-04-30 20:07 true
在《网络模型二》中回复了,请讨论  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(三)多路复用模型 2008-09-03 12:23 bluesky
(4)epoll的LT和ET性能的差异,我测试的数据表明两者性能相当,“使用epoll就是为了高性能,就是要使用ET模式”这个说法是站不住脚的。个人倾向于使用LT模式,编程简单、安全。

epoll LT与ET的性能差别还是蛮大的,LT单台能撑到10wcpu已经是极限,但ET撑到15w没啥问题。不过如果应用不要求撑到这么高的在线,那确实LT就可以满足了。  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(三)多路复用模型 2008-11-07 11:21 ddd
转载  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(三)多路复用模型 2009-02-24 11:11 ff
很高兴看到博主的好文,谢谢~~~希望继续写下去  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(三)多路复用模型 2009-06-05 18:09 上海苏元电气科技有限公司
上海苏元电气科技有限公司,以国内电气领域服务专家为奋斗目标,自成立以来致力于通过技术创新和优质服务,深化产品内涵,与广大用户共赢,与合作伙伴共赢。通过实行营销专业化,品种规格市场化,坚持自主研发的道路,形成了具有自主知识产权 的“SUWIN”品牌和先进完整成熟的产品设计、制造、销售技术体系。具备了独立开发具有国际先进水平的新产品,新技术能力。

也是一种方式,公司刚弄起这些.  回复  更多评论
  

# re: 【原创】技术系列之 网络模型(三)多路复用模型 2015-03-27 17:18 ckw
文中一、select模型 使用select时也无法突破最大fd=1024的限制啊!  回复  更多评论
  


只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理