优于select的epoll
1. epoll的理解与应用
select服用方法由来已久,在《TCP/IP网络编程(6) 》中,介绍了如何使用select方法实现IO复用。但是利用该技术后,无论如何优化程序性能,也难于同时接入上百个客户端(同时也是基于硬件性能的不同)。这种select方式并不适合以web服务器端开发,因此需要了解Linux平台下的epoll。
1.1 基于select的IO复用技术速度慢的原因
针对《TCP/IP网络编程(6) 》中基于select的IO复用代码,很容易分析其中的不合理之处:
- 调用
select()
函数后常见的针对所有文件描述符的循环操作 - 每次调用
select()
函数时都需要向该函数传递监视对象的信息
调用select()
函数之后,并不是把发生变化的文件描述符集中到一起,而是通过观察作为监视对象的fd_set
变量的变化,从而找出发生变化的文件描述符。这一操作无法避免针对所有监视对象的循环语句,而且,作为监视对象的fd_set
变量会发生变化,所以在调用select()
函数之前,应该复制并保存fd_set
变量原有的信息,并在每次调用select()
函数时传递新的监视对象信息(具体可见《TCP/IP网络编程(6) 》中详细代码)
那么是哪些因素阻碍了程序性能的进一步提高呢?
- 循环语句 ?
- 每次传递的对象监视信息 ?
实际上更大的障碍是每次调用select()
函数时向操作系统传递的对象监视信息,应用程序向操作系统传递数据将对程序造成很大的负担,且无法通过优化应用程序代码解决,因此成为性能上的致命弱点
因为select()
与文件描述符有关,更准确的说是监视套接字变化的函数,而套接字是由操作系统进行管理的,因此select()
函数必须借助操作系统才能完成其功能。
select()
函数这一缺点的改进方法:仅向操作系统传第一次监视对象,监视范围或者内容发生变化的时候,只通知发生变化的事项。这样就无需每次调用select()
函数的时候,都要向操作系统传第一次监视对象信息。Linux操作系统通过epoll支持这种处理方式,Windows是通过IOCP进行支持。
select()
函数的优点:
- 具备更好的兼容性 (例如epoll尽在Linux下支持,但是select具备兼容性)
- 服务器端接入的客户端数量很少,即可使用select()
1.2 epoll实现
epoll()
函数具备如下优点:
- 无需编写以监视状态变化为目的的针对所有文件描述符的循环语句
- 调用
epoll_wait()
函数(对应于select()
函数)的时候无需每次都传递监视对象信息
epoll()
服务器端实现中需要的3个函数:
epoll_create
: 创建保存epoll文件描述符的空间epoll_ctl
: 向空间注册并注销文件描述符epoll_wait
: 与select()
函数类似,等待文件描述符发生变化
与
select
方式中需要声明fd_set
变量保存监视对象文件描述符不同的是,epoll
方式下由操作系统负责保存监视对象的文件描述符,因此首先需要向操作系统请求创建保存文件描述符的空间,此操作是通过epoll_create()
函数完成的。
在
select
函数中,添加和删除监视对象的文件描述符通过FD_SET,FD_CLR来完成,在epoll方式下,需要调用epoll_ctl()
函数,请求操作系统完成。
调用
epoll_wait()
函数等待文件描述符变化,在select
方式中,通过fd_set
变量查看监视对象的状态变化(事件是否发生),而在epoll
方式下,通过结构体epoll_event
将发生变化的文件描述符单独集中到一起。
epoll_event
结构体定义
struct epoll_event
{
__uint32 events;
epoll_data_t data;
}
typedef union epoll_data
{
void* ptr;
int fd;
__uint32 u32;
__uint64 u64;
} epoll_data_t;
仅需申明足够大的epoll_event结构体数组后,传递给epoll_wait()
函数,发生变化的文件描述符信息将被填入该结构体数组,无需像select()
函数那样针对所有文件描述符进行循环。
1.3 epoll相关函数介绍
1.3.1 epoll_create函数
epoll_create()
函数是从Linux 2.5.44版本的内核开始引入的,可通过如下命令查看Linux版本内核:
cat /proc/sys/kernel/osrelease
#include <sys/epoll.h>
int epoll_create(int size);
// size : epoll实例的大小 返回值: 文件描述符,与套接字相似
调用epoll_create()
函数创建的文件描述符保存空间称之为epoll例程,参数size的值决定epoll例程的大小,但是size的值只是向操作系统提供建议,size的值并非最后用来决定epoll例程的大小,仅仅供操作系统参考。
注:Linux 2.6.8之后的内核将完全忽略```epoll_create()函数传入的size参数,而是完全由操作系统系统内核自行决定epoll例程大小。
epoll_create()
函数创建的资源与套接字相同,也是由操作系统进行管理。因此,该函数也会返回文件描述符,该函数返回的文件描述符主要用于区分epoll例程,需要终止时,同样需要调用close()
函数进行关闭。
1.3.2 epoll_ctl函数
生成epoll例程后,应在其内部注册监视对象文件描述符。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
参数含义:
- epfd 用于注册监视对象的epoll例程文件描述符
- op 用于指定监视对象的添加,删除,或者修改,其值和含义如下所示
- EPOLL_CTL_ADD : 将文件描述符注册到epoll例程
- EPOLL_CTL_DEL : 从epoll例程中删除文件描述符, 此时event参数应该设为NULL
- EPOLL_CTL_MOD : 更改注册的文件描述符的关注事件发生情况
- fd 需要注册的监视对象的文件描述符
- event 监视对象的事件类型
epoll_ctl()
函数中,第四个参数为epoll_event结构体,此处的作用是用于在epoll例程中注册文件描述符时,用于注册关注的事件。此外之前介绍过epoll_event结构体还可以用于保存发生事件的文件描述符集合。
示例代码: 将sockfd注册到epoll例程epfd当中,并在需要读取数据的情况下产生相应的事件
struct epoll_event event;
event.events = EPOLLIN; // 发生需要读取数据的情况时
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);
epoll_event的成员events可取的值及其含义如下所示:
- EPOLLIN 需要读取数据的情况
- EPOLLOUT 输出缓冲区为空,可以立刻发送数据的情况
- EPOLLPRI 收到OOB数据的情况
- EPOLLRDHUP 套接字在断开连接或者半关闭的情况,在边缘触发方式下非常有用
- EPOLLERR 发生错误的情况
- EPOLLET 以边缘触发的方式得到事件通知
- EPOLLONESHOT 发生一次事件后,相应的文件描述符不再收到事件通知。因此需要向
epoll_ctl()
函数的第二个参数传递EPOLL_CTL_MOD,在此设置事件。
上述的多个参数可通过位或运算同时进行传递。
1.3.3 epoll_wait函数
#include <sys/epoll.h>
int epoll_wait();
标签:文件,13,函数,epoll,描述符,多线程,event,select
From: https://www.cnblogs.com/ncepubye/p/17020588.html