C++ – 网络编程模型 – Linux EPOLL

  • Post author:
  • Post category:linux





C++ – 网络编程模型 – Linux EPOLL




1.简介




Linux I/O多路复用技术在比较多的TCP网络服务器中有使用,即比较多的用到select函数。Linux 2.6内核中有提高网络I/O性能的新方法,即epoll 。 epoll是什么?按照man手册的说法是为处理大批量句柄而作了改进的poll。要使用epoll只需要以下的三个系统函数调


用:

epoll_create

(2),

epoll_ctl

(2),

epoll_wait

(2)。







2.select模型的缺陷





(1)

在Linux内核中,select所用到的FD_SET是有限的



内核中有个参数__FD_SETSIZE定义了每个FD_SET的句柄个数:#define __FD_SETSIZE 1024。也就是说,如果想要同时检测1025个句柄的可读状态是不可能用select实现的;或者同时检测1025个句柄的可写状态也是不可能的。




(2)

内核中实现select是使用轮询方法



每次检测都会遍历所有FD_SET中的句柄,显然select函数的执行时间与FD_SET中句柄的个数有一个比例关系,即select要检测的句柄数越多就会越费时







3.Windows IOCP模型的缺陷




windows完成端口实现的AIO,实际上也只是使用内部用线程池实现的,最后的结果是IO有个线程池,你的应用程序也需要一个线程池。很多文档其实已经指出了这引发的线程context-switch所带来的代价。







4.EPOLL模型的优点





(1) 支持一个进程打开大数目的socket描述符(FD)




epoll没有select模型中的限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于select 所支持的2048




(2) IO效率不随FD数目增加而线性下降




传统select/poll的另一个致命弱点就是当你拥有一个很大的socket集合,由于网络得延时,使得任一时间只有部分的socket是”活跃”的,而select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对”活跃”的socket进行操作:这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的。于是,只有”活跃”的socket才会主动去调用callback函数,其他idle状态的socket则不会。在这点上,epoll实现了一个”伪”AIO”,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的,比如一个高速LAN环境,epoll也不比select/poll低多少效率,但若过多使用的调用epoll_ctl,效率稍微有些下降。然而一旦使用idle connections模拟WAN环境,那么epoll的效率就远在select/poll之上了。




(3) 使用mmap加速内核与用户空间的消息传递




无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就显得很重要。在这点上,epoll是通过内核于用户空间mmap同一块内存实现。







5.EPOLL模型的工作模式





(1) LT模式






LT:level triggered,这是缺省的工作方式,同时支持block和no-block socket,在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。







(2)



ET模式





LT:edge-triggered,这是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核就通过epoll告诉你,然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作而导致那个文件描述符不再是就绪状态(比如你在发送,接收或是接受请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核就不会发送更多的通知(only once)。不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。







6.EPOLL模型的使用方法






epoll用到的所有函数都是在头文件

sys/epoll.h

中声明的,下面简要说明所用到的数据结构和函数:




(1) epoll_data、epoll_data_t、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 */



};






结构体epoll_event 被用于注册所感兴趣的事件和回传所发生待处理的事件。epoll_event 结构体的events字段是表示感兴趣的事件和被触发的事件,可能的取值为:




EPOLLIN

:      表示对应的文件描述符可以读;




EPOLLOUT

:     表示对应的文件描述符可以写;




EPOLLPRI

:     表示对应的文件描述符有紧急的数据可读;




EPOLLERR

:     表示对应的文件描述符发生错误;




EPOLLHUP

:     表示对应的文件描述符被挂断;




EPOLLET

:      表示对应的文件描述符有事件发生;








联合体epoll_data用来保存触发事件的某个文件描述符相关的数据。例如一个client连接到服务器,服务器通过调用accept函数可以得到于这个client对应的socket文件描述符,可以把这文件描述符赋给epoll_data的fd字段,以便后面的读写操作在这个文件描述符上进行。







(2)epoll_create





函数声明

:int

epoll_create

(intsize)




函数说明:

该函数生成一个epoll专用的文件描述符,其中的参数是指定生成描述符的最大范围。







版权声明:本文为trojanpizza原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。