`
jiagou
  • 浏览: 2530098 次
文章分类
社区版块
存档分类
最新评论

高并发服务器设计之多路复用模型

 
阅读更多

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

view plaincopy to clipboardprint?
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout

其中参数n表示监控的所有fd中最大值+1。
和select模型紧密结合的四个宏,含义不解释了:

view plaincopy to clipboardprint?
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);
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模型的服务器模型:

view plaincopy to clipboardprint?
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

}
}
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原型:

view plaincopy to clipboardprint?
int poll(struct pollfd *ufds, unsigned int nfds, int timeout);
struct pollfd {
int fd; /**//* file descriptor */
short events; /**//* requested events */
short revents; /**//* returned events */
};
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的服务器模型伪码:

view plaincopy to clipboardprint?
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}
}
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阻塞操作的原型:

view plaincopy to clipboardprint?
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 */
};
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的服务器模型伪码:

view plaincopy to clipboardprint?
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,否则执行响应操作
}
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信息,到可以绑定指针结构信息

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

view plaincopy to clipboardprint?
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;
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)。


本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/hanzengyi/archive/2010/03/04/5341288.aspx

分享到:
评论

相关推荐

    (牛客网C++课程)Linux 高并发Web服务器项目实战(带定时检测代码)

    2. 用 epoll 事件检测技术实现 IO 多路复用,提高运行效率; 3. 采用模拟 Proacto r的事件处理模式,利用线程池实现多线程机制,实现高并发通信,减少频繁创建和销毁线程带来的开销;(信号和互斥锁) 4. 主进程负责...

    C++从0实现百万并发Reactor服务器完结13章下载

    reactor中的 IO 使用的是select poll epoll 多路复用IO, 以便提高 IO 事件的处理能力,提高IO事件处理效率,支持更高的并发 。 二、Reactor 模型有三个重要的组件: 多路复用器:由操作系统提供,在 linux 上一般是...

    本项目包括利用多线程、select、poll以及epoll实现的并发处理连接请求

    高并发编程会有一些服务器模型,例如reactor或proactor。这两类都要使用到IO多路复用,O多路复用是指单个进程/线程就可以同时处理多个IO请求。有三个方式select、poll、epoll。 select:将文件描述符放入一个集合中...

    epump:ePump是一个基于IO事件通知、非阻塞通信、多路复用、多线程等机制开发的事件驱动模型的 C 语言应用开发框架,利用该框架可以很容易地开发出高性能、大并发连接的服务器程序

    performance and numerous concurrent connections.ePump是一个基于I/O事件通知、非阻塞通信、多路复用、多线程等机制开发的事件驱动模型的 C 语言应用开发框架,利用该框架可以很容易地开发出高性能、大并发连接的...

    基于Linux的web服务器

    1.利用IO多路复用技术Epoll与线程池实现Reactor高并发模型。 2.利用主从状态机解析HTTP请求报文,实现对资源的请求。 3.使用升序双向链表实现定时器,关闭超时的非活动连接。 4.单例模式的日志系统,实现异步写入...

    C语言实现高并发http文件上传下载服务器【源码下载】VS2010工程

    C 语言实现的http文件上传下载服务 系统平台:windows 开发工具:vs2010 开发语言:C 程序为单线程,使用I/O多路复用实现并发 抽取libevent的最最最基础框架,自己封装event 使用BSD tree.h的红黑树

    Linux高性能服务器编程

    高性能服务器程序框架 8.1 服务器模型 8.1.1 CS模型 8.1.2 P2P模型 8.2 服务器编程框架 8.3 IO模型 8.4 两种高效的事件处理模式 8.4.1 Reactor模式 8.4.2 Proactor模式 8.4.3 模拟Proactor模式 8.5 两种...

    socket网络编程-epoll-水平触发和边缘触发源码

    (1)I/O多路复用技术用于监控多个TCP连接上的数据收发,而epoll就是一种在Linux上使用的I/O多路复用并支持高并发的典型技术。传统的select、poll也是I/O多路复用技术,但这2种技术受内部实现的限制,不支持高并发,...

    cpp-Lotos一个微型但高性能的HTTPWeb服务器

    一个微型但高性能的HTTP Web服务器,遵循Reactor模型,使用非阻塞IO和IO多路复用(epoll ET)来处理并发。 Lotos是用纯C编写的,经过了很好的测试。 支持多个HTTP头(连接,内容长度等),将来还会增加更多。

    网络编程教程,很好的一本写linux网络编程书,这是我上传的源码

     6.1.1 多路复用模型的概念与select函数  6.1.2 应用示例  6.1.3 pselect函数对select的增强  6.2 信号驱动的输入/输出模型  6.3 系统I/O模型的总结  6.4 带外数据  6.4.1 带外数据的发送...

    基于java NIO的简单聊天软件示例

    它是一种同步非阻塞的I/O模型,也是I/O多路复用的基础,已经被越来越多地应用到大型应用服务器,成为解决高并发与大量连接、I/O处理问题的有效方式。 NIO是一种基于通道和缓冲区的I/O方式,它可以使用Native函数库...

    libevent深入浅出

    本教程要求有一定的服务并发编程基础,了解select和epoll等多路I/O复用机制。教程目的主要是快速建立libevent的认知,了解libevent的常用数据结构和编程方法。达到可以使用libevent写出自己的高并发服务器处理模型。

    libevent深入淺出

    本教程要求有一定的服务并发编程基础,了解select和epoll等多路I/O复用机制。 教程目的主要是快速建立libevent的认知,了解libevent的常用数据结构和编程方法...达到可以使用libevent写出自己的高并发服务器处理模型。

    linux programming instances网络编程教程 附源代码

    全书由13章组成,内容涉及到Lindx系统编程基础、TCP/UDP协议、套接字编程概念及I/O模型、高级编程中需要用到的进程问通信同步、多路复用、多线程编程和一些高级套接字控制方法、IPv6介绍以及网络安全等。...

    网站架构技术

    使用应用服务器集群改善网站的并发处理能力 问题: 负载均衡情况下session状态的保持? 解决方案: 基于DNS的负载均衡 反向代理 ngix JK2 数据库的读写分离 问题: 读库与写库的数据同步...

    go web编程

    2.4.1 多路复用 器 27 2.4.2 服务静态文件 29 2.4.3 创建处理器函数 29 2.4.4 使用cookie进行访问控制 30 2.5 使用模板生成HTML响应 33 2.6 安装PostgreSQL 38 2.6.1 在Linux或FreeBSD...

    UNIX 高级教程系统技术内幕

    1.1.2 创始之初 1.1.3 繁衍 1.1.4 BSD 1.1.5 System V 1.1.6 商业化 1.1.7 Mach 1.1.8 标准 1.1.9 OSF 和UI 1.1.10 SVR4 及其之后 1.2 演变的动力 1.2.1 功能 1.2.2 网络 1.2.3 性能 1.2.4 硬件变化 1.2.5 改进质量 ...

    asp.net知识库

    ASP.Net应用程序的多进程模型 NET委托:一个C#睡前故事 [推荐] - [原创] Microsoft .NET策略及框架概述 卸载Class? Web Form 窗体 如何实现web页面的提示保存功能 在ASP.Net中两种利用CSS实现多界面的方法 如何在...

    JAVA_API1.6文档(中文)

    定义了用于多路复用的、非阻塞 I/O 操作的选择器。 java.nio.channels.spi 用于 java.nio.channels 包的服务提供者类。 java.nio.charset 定义用来在字节和 Unicode 字符之间转换的 charset、解码器和编码器。 ...

    linux网路编程 中文 23M 版

    1.7 Linux软件开发的可借鉴之处........................:.................. 12 1-8 .................................................................13 第2 章Linux编程环境...................................

Global site tag (gtag.js) - Google Analytics