1 三种机制主要API
1.1 select (简略)
int select (int maxfdp1,fd_set *readset,fd_set *writeset, fd_set *exceptset,const struct timeval * timeout); //blocked some events occur, return -1 means error,0 means timeout, n means the num of fd with events occuring.
void FD_ZERO (fd_set *et); // clear all bits in fdset
void FD_SET (int fd,fd_set *fdset); // turn on the bit for fd in fdset
void FD_CLR (int fd,fd_set *fdset); // turn off the bit for fd in fdset
int(int fd,fd_set *fdset); // is the bit for fd on in fdset
1.2 poll (简略)
struct pollfd {
int fd; /**/
short events; /* 等待的需要测试事件 */
short revents; /* 实际发生了的事件,也就是返回结果 */
};
int poll(struct pollfd fds[], nfds_t nfds, int timeout); //nfds: the length/count of the array of fds. The return value is same with select.
1.3 epoll (详细)
int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第
一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id
/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,它不同与select()是在监听事件时告诉要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的可以写;
EPOLLPRI:表示对应的有紧急的数据可读(这里应该表示有到来);
EPOLLERR:表示对应的发生错误;
EPOLLHUP:表示对应的被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生,类似于select()调用。参数events用来从得
到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
2 select,poll,epoll对比简单结论
epoll与selec、poll的实现机制上有着根本的不同,使用方式上也差别很大。select和poll都是主动轮询机制,需要遍历每一个描述符。epoll是被动触发方式,基于每个描述的callback机制实现。另外epoll通过mmap在内核和用户空间共享同一块内存,而poll和select机制需要内核及用户空间之间的拷贝动作来传递消息。因此,一般网络应用情况下(大量链接描述符,但仅有少部分活跃)epoll比poll和select性能上有着质的提升,select和poll需要遍历每一个描述符,性能是线性下降的。而epoll机制只有活跃的描述符才会通过callback有相应的触发。但特殊情况下(监听的描述符很少,且都很活跃),这时select和poll效率要更高,因为epoll多层函数回调的成本会显得比较高,而且所有活跃描述符都会进行callback。 补充说明:三种机制的核心都是通过相应描述符的等待进程队列来实现的,只不过等待队列的唤醒机制不同,一个主动轮询、一个被动触发。另外,在发生event的描述符鉴别上,select和poll只知道有几个描述发生了事件,但不知道是具体哪几个仍需要,for(n_all)通过判断标志位来找出具体哪几个有事件发生。而epoll会直接返回相应数目的发生事件的描述符for(n_occured)。这里可见select和poll不仅在内核实现中需要定时轮询遍历所有描述符对应设备,而且用户应用程序设计中,也需要遍历整个描述符集合找出相应的若干个发生事件的描述。
时间发生后处理流程对比:
select直到找到n个后退出循环,实际循环次数可能远大于n。(或者干脆循环num of fd次)
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直接对n个发生事件的描述符做出处理
n=epoll_wait(epfd,events,20,500);
for(i=0;i
{
do_something(events[n]);
}
3 select,poll,epoll详细对比
3.1 基本实现
select
|
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1 单个进程可监视的fd数量被限制
2 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大
3 对socket进行扫描时是线性扫描
|
poll
|
poll
本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态,如果设备就绪则在设备等待队列中加入一项并继续遍
历,如果遍历完所有fd后没有发现就绪设备,则挂起当前进程,直到设备就绪或者主动超时,被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。
它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。
poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
|
epoll
|
epoll支持水平触发和边缘触发,最大的特点在于边缘触发,它只告诉进程哪些fd刚刚变为就需态,并且只会通知一次。
在前面说到的复制问题上,epoll使用mmap减少复制开销。
还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知
|
3.2 支持一个进程所能打开的最大连接数
select
|
单
个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是32*32,同理64位机器上
FD_SETSIZE为32*64),当然我们可以对它进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
|
poll
|
poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
|
epoll
|
虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接。
|
3.3 FD剧增后带来的IO效率问题
select
|
因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。
|
poll
|
同上
|
epoll
|
因
为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket
较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。
|
3.4 消息传递方式
select
|
内核需要将消息传递到用户空间,都需要内核拷贝动作
|
poll
|
同上
|
epoll
|
epoll通过内核和用户空间共享一块内存来实现的
|
4 参考内容
http://xingyunbaijunwei.blog.163.com/blog/static/76538067201241685556302/
http://blog.163.com/ma95221@126/blog/static/24822102201172515030855/
http://blog.csdn.net/tianmohust/article/details/6677985
http://blog.csdn.net/zhaozhanyong/article/details/5410887
http://blog.csdn.net/hairetz/article/details/6337817
http://blog.csdn.net/hairetz/article/details/6337817
阅读(7165) | 评论(0) | 转发(0) |