参考
- epoll机制
- 相关函数
- 代码示例
一、epoll函数
epoll详解(使用、原理、实验)
提供三种系统调用:
- epoll_create():创建一个 eventpoll 对象(epoll池)
- epoll_ctl():想epoll对象(epoll池)添加或删除所要监听的socket
- epoll_wait():收集在epoll监控的事件中已经发生的事件
1 epoll_create()
epoll_create详解
创建一个epoll实例(epoll池)并返回该实例对应的文件描述符fd。
该文件描述符用于随后的所有对epoll的调用接口。每创建一个epoll句柄,会占用一个fd,因此当不再需要时,应使用close关闭epoll_create()返回的文件描述符,否则可能导致fd被耗尽。
#include <sys / epoll.h>
nfd = epoll_creat(max_size);
max_size:这个监听的数目最大有多大.
2. epoll_ctl()
通过指定的 epfd 参数来操作特定的 epoll 实例
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op 参数表示操作类型
- EPOLL_CTL_ADD:将指定的文件描述符 fd 添加到 epoll 实例中,并注册相应的事件。这样,当该文件描述符上的事件就绪时,就会通知应用程序。
- EPOLL_CTL_MOD:修改已经注册在 epoll 实例中的文件描述符 fd 对应的事件。可以修改事件的类型、关注的事件、关联的用户数据等。
- EPOLL_CTL_DEL:删除已经注册在 epoll 实例中的文件描述符 fd。
fd 参数是目标文件描述符,用于指定需要进行操作的文件描述符。
event 参数是一个指向 struct epoll_event 结构体的指针,用于指定事件相关的配置。
- uint32_t events:表示注册的事件类型,可以是 EPOLLIN(可读事件)、EPOLLOUT(可写事件)、EPOLLRDHUP(对端关闭连接)、EPOLLPRI(有紧急数据可读)、EPOLLERR(错误事件)等。可以使用位掩码进行组合。
- epoll_data_t data:用于存储用户数据信息,可以是文件描述符本身的值,也可以是用户自定义的数据结构指针。
函数的返回值为 0 表示操作成功,-1 表示出现错误,具体的错误信息可以通过检查 errno 变量获得。
3.epoll_wait()
等待监听的所有fd相应事件的产生.
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
参数说明:
- int epfd: epoll_create()函数返回的epoll实例的句柄。
- struct epoll_event * events: 接口的返回参数,epoll把发生的事件的集合从内核复制到 events数组中。events数组是一个用户分配好大小的数组,数组长度大于等于maxevents。(events不可以是空指针,内核只负责把数据复制到这个 events数组中,不会去帮助我们在用户态中分配内存)
- int maxevents: 表示本次可以返回的最大事件数目,通常maxevents参数与预分配的events数组的大小是相等的。
- int timeout: 表示在没有检测到事件发生时最多等待的时间,超时时间(>=0),单位是毫秒ms,-1表示阻塞,0表示不阻塞。
返回需要处理的事件数目。失败返回0,表示等待超时。
4.epoll_event结构体
#include<sys/epoll.h>
struct epoll_event {
uint32_t events; // epoll 事件类型,包括可读,可写等
epoll_data_t data; // 用户数据,可以是一个指针或文件描述符等
};
events字段表示要监听的事件类型,可以是以下值之一:
- EPOLLIN:表示对应的文件描述符上有数据可读
- EPOLLOUT:表示对应的文件描述符上可以写入数据
- EPOLLRDHUP:表示对端已经关闭连接,或者关闭了写操作端的写入
- EPOLLPRI:表示有紧急数据可读
- EPOLLERR:表示发生错误
- EPOLLHUP:表示文件描述符被挂起
- EPOLLET:表示将epoll设置为边缘触发模式
- EPOLLONESHOT:表示将事件设置为一次性事件
data字段表示用户数据,它的类型是一个union,可以存放一个指针或文件描述符等数据。
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
- ptr可以指向任何类型的用户数据
- fd表示文件描述符
- u32和u64分别表示一个32位和64位的无符号整数。
使用时,用户可以将自己需要的数据存放到这个字段中,当事件触发时,epoll系统调用会返回这个数据,以便用户处理事件。
5.总结
总结来说,当某个进程调用epoll_create方法时,内核会创建一个eventpoll对象(即文件描述符epfd所代表的对象,可以通俗地称之为epoll池)。之后就可以往池子里增、删、改需要监视(感兴趣)的fd,这需要使用epoll_ctl调用。
Linux内核使用红黑树来实现管理epoll池中的fd。红黑树是一种平衡二叉树,其增删改操作时间复杂度为O ( log n ),能够保证稳定的查找性能。
二、epoll实现服务器
主要步骤
- 创建socket:使用socket函数创建一个监听套接字,用于接受客户端的连接请求。
- 绑定socket:使用bind函数将监听套接字绑定到一个特定的IP地址和端口。
- 监听连接:使用listen函数开始监听连接请求,指定服务器可接受的最大连接数。
- 创建epoll实例:使用epoll_create函数创建一个epoll实例,返回一个文件描述符。
- 将监听套接字添加到epoll实例:使用epoll_ctl函数将监听套接字添加到epoll实例中,并注册对读事件的关注。
- 进入事件循环:循环调用epoll_wait函数来等待事件的发生,该函数会阻塞当前线程直至有事件发生。一旦有事件发生,它将返回一个就绪事件的列表。
- 处理就绪事件:遍历就绪事件列表,对每个事件进行处理。根据事件类型,可以进行接受连接、读取数据、发送数据或关闭连接等操作。
- 根据需要添加或删除文件描述符:在处理完一个事件后,可以根据需要使用epoll_ctl函数动态地添加或删除文件描述符,以便继续监听其他事件。
- 重复步骤6-8:继续循环执行步骤6-8,处理新的就绪事件,直到服务器主动关闭或出现错误条件为止。