《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函数有可能引起服务器端的长时间停顿)
边缘触发可以分离接收数据和处理数据的时间点。