《TCP/IP网络编程》 尹圣雨 P263~P265

实现I/O复用的传统方法有select函数和epoll函数。select函数的使用方法由于各种原因导致无法得到令人满意的性能。因此有了Linux下的epoll、BSD的kqueue、Solaris的/dev/poll 和 Windows的IOCP等复用技术

epoll理解及应用

select复用方法,无法同时接入上百个客户端,并不适合以web服务器端开发为主流的现代开发环境,需要学习Linux下的epoll。


基于select的I/O复用技术速度慢的原因

(1)调用select函数后常见的针对所有文件描述符的循环语句

(2)每次调用select函数时都需要向该函数传递监视对象信息 (更大障碍)/传递监视对象信息:每次调用select函数时向操作系统传递监视对象信息。


        应用程序向操作系统传递数据将对程序造成很大负担,而且无法通过优化代码解决,因此将成为性能上的致命弱点。

select函数与文件描述符有关,准确的说,是监视套接字变化的函数。而套接字是由操作系统管理的,所以select函数绝对需要借助于操作系统才能完成功能。select函数可以通过如下方式弥补:

仅向操作系统传递1次监视对象,监视范围或内容发生变化时只通知发生变化的事项。

这样就无需每次调用select函数时都向操作系统传递监视对象信息。


select优点

epoll方式只在Linux下提供支持,改进的I/O复用模型不具有兼容性。而大部分系统支持select函数。只要满足以下两个条件,Linux平台也不拘泥于epoll。

(1)服务器端接入者少

(2)程序应具有兼容性


实现epoll时必要的函数和结构体

能够克服select函数缺点的epoll函数具有如下特点,这些特点正好与之前的select函数缺点相反。

(1)无需编写以监视状态变化为目的的针对所有文件描述符的循环语句

(2)调用对应于select函数的epoll_wait函数时无需每次传递监视对象信息


epoll服务器端实现中的3个函数:

(1)epoll_create:创建保存epoll文件描述符的空间。

(2)epoll_ctl:向空间注册并注销文件描述符。

(3)epoll_wait:与select函数类类似,等待文件描述符发生变化。


epoll发生下由操作系统负责保存监视对象文件描述符,因此需要向操作系统请求创建保存文件描述符的空间,使用的函数就是epoll_create.


epoll_create

epoll是从Linux2.5.44版内核(操作系统的核心模块)开始引入。

#include<sys/epoll.h>
int epoll_create(int size);

成功时返回epoll文件描述符,失败时返回-1.

调用epoll_create函数时创建的文件描述符保存空间称为“epoll例程”,但有些情况不同。参数size传递的值决定epoll例程的大小,但该值只是向操作系统提的建议。size并非用来决定epoll例程的大小,而仅供操作系统参考。

epoll_create函数创建的资源与套接字相同,也由操作系统管理。


epoll_ctl

生成epoll例程后,应在其内部注册监视对象文件描述符,此时使用epoll_ctl函数。

#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);

成功时返回0,失败返回-1

epfd:用于注册监视对象的epoll例程的文件描述符

op:用于指定监视对象的添加、删除或更改等操作

fd:需要注册的监视对象文件描述符

event:监视对象的事件类型


第二个参数传递常量及含义:

EPOLL_CTL_ADD:将文件描述符注册到epoll例程

EPOLL_CTL_DEL::从epoll例程中删除文件描述符

EPOLL_CTL_MOD:更改注册的文件描述符的关注事件发生情况


第四个参数,epoll_event结构体用于保存发生事件的文件描述符集合。但是也可以在epoll例程中注册文件描述符时,用于注册关注的事件。

只记住EPOLLIN:需要读取数据的情况。


epoll_wait


#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);

成功 时返回发生事件的文件描述符,失败返回-1.

epfd:表示事件发生监视范围的epoll例程的文件描述符

events:保存发生事件的文件描述符集合的结构体地址值

maxevents:第二个参数可以保存的最大事件数

timeout:以1/1000秒为单位的等待时间,传递-1时,一直等待直到发生事件


注意:第二个参数所指缓冲需要动态分配、


条件触发Level Trigger 和边缘触发 Edge Trigger


两者区别在于发生事件的时间点

条件触发方式中,只要输入缓冲有数据就会一直通知该事件。

边缘触发中输入缓冲收到数据时仅注册1次,即使输入缓冲中留有数据,也不会再进行注册。


select模型是以条件触发的方式工作,输入缓冲中如果还剩有数据,肯定会注册事件。


边缘触发的服务器端实现中必知的两点

通过errno变量验证错误原因   (边缘触发中,接收数据仅注册1次该事件。一旦发生输入事件,就应该读取输入缓冲中的全部数据,需要验证输入缓冲是否为空。)

为了完成非阻塞Non-blocking I/O,更改套接字特性(边缘触发下,以阻塞方式工作的read&write函数有可能引起服务器端的长时间停顿)

边缘触发可以分离接收数据和处理数据的时间点。