IO多路复用---EPOLL
一、select、poll、epoll核心特点对比
1、三者均用于监测多个文件描述符(fd)的事件,但在存储方式、性能、功能支持上差异显著,具体对比如下:
对比维度 | select | poll | epoll |
---|---|---|---|
文件描述符存储方式 | 位图(数组) | 链表 | 红黑树(二叉树,内核层维护) |
监测fd上限 | 最多1024个 | 无上限 | 无上限(红黑树提升查找效率) |
应用层-内核层数据拷贝 | 需反复拷贝(fd集合表) | 需反复拷贝(fd集合表) | 无需反复拷贝(fd集合创建在内核层) |
事件查找方式 | 需遍历返回的集合表找到达事件 | 需遍历返回的集合表找到达事件 | 直接返回到达的事件,无需遍历 |
触发模式支持 | 仅水平触发(低速模式) | 仅水平触发(低速模式) | 水平触发(低速)+ 边沿触发(高速) |
2、水平触发与边沿触发
文档提及“水平触发与边沿触发的区别”,但未展开细节,仅明确:
- 水平触发(低速模式):select、poll、epoll均支持,适合对实时性要求不高的场景。
- 边沿触发(高速模式):仅epoll支持,适合高并发、低延迟的高速场景。
二、epoll的具体实现(函数与结构体)
epoll通过3个核心函数完成“创建fd集合-操作fd-监测事件”的流程,配合特定结构体传递事件信息,细节如下:
1. 核心结构体定义
用于描述fd对应的事件类型与关联数据:
// 数据联合体:存储与fd关联的用户数据
typedef union epoll_data { void *ptr; // 指针(自定义数据) int fd; // 关注的文件描述符(常用) uint32_t u32; // 32位无符号整数 uint64_t u64; // 64位无符号整数
} epoll_data_t; // epoll事件结构体:描述fd的事件类型与关联数据
struct epoll_event { uint32_t events; // 事件类型(读/写等) epoll_data_t data; // 关联的用户数据(常用data.fd表示关注的fd)
};
其中,events
支持的核心事件类型:
EPOLLIN
:读事件(fd可读取数据)EPOLLOUT
:写事件(fd可写入数据)
2. epoll工作流程
- 调用
epoll_create
创建内核层fd集合,获取epfd。 - 调用
epoll_ctl
(op=EPOLL_CTL_ADD
)将需监测的fd及事件添加到集合。 - 调用
epoll_wait
通知内核监测事件,阻塞等待事件到达。 epoll_wait
返回后,从events
数组中获取到达的事件。- 根据事件类型(读/写)处理对应任务。
3. 核心函数
(1)创建epoll fd集合:epoll_create
- 功能:通知内核创建一个用于存储fd的集合(内核维护)。
- 参数:
size
- 计划监测的fd个数(仅为提示,无实际限制)。 - 返回值:
- 成功:返回代表该集合的文件描述符(epfd)。
- 失败:返回-1。
(2)操作epoll集合(增/删/改fd):epoll_ctl
- 功能:对已创建的epoll集合(epfd)进行fd的添加、修改、删除操作。
- 参数:
epfd
:通过epoll_create
创建的集合描述符。op
:操作类型(3种):EPOLL_CTL_ADD
:向集合添加fd。EPOLL_CTL_MOD
:修改集合中已存在fd的事件。EPOLL_CTL_DEL
:从集合中删除fd。
fd
:需要操作的目标文件描述符。event
:指向struct epoll_event
的指针,描述fd的事件类型与关联数据。
- 返回值:
- 成功:返回0。
- 失败:返回-1。
(3)监测事件:epoll_wait
- 功能:通知内核开始监测epoll集合中fd的事件,阻塞等待事件发生或超时。
- 参数:
epfd
:需监测的epoll集合描述符。events
:指向struct epoll_event
数组的指针,用于存储返回的到达事件。maxevents
:events
数组的最大容量(即最多接收的事件个数)。timeout
:超时时间(单位未明确,通常为毫秒):-1
:不设置超时,一直阻塞等待事件。- 其他值:超时后返回(即使无事件)。
- 返回值:
- 成功:返回实际到达的事件个数。
- 失败:返回-1。
4.代码练习
从终端和管道中获取数据并且输出
#include "head.h"#define MAX_FD_CNT 2 int epoll_fd_add(int spfds, int fd, uint32_t events)
{struct epoll_event ev;ev.events = events;ev.data.fd = fd;int ret = epoll_ctl(spfds, EPOLL_CTL_ADD, fd, &ev);if(ret < 0){perror("epoll_ctl error");return -1;}return 0;
}int main(int argc, char const *argv[])
{char buff [1024] = {0};mkfifo("./wrfifo", 0664);int fifofd = open("./wrfifo", O_RDONLY);if(fifofd < 0){perror("open fifo error");return -1;}// 创建集合int epfds = epoll_create(MAX_FD_CNT);if(epfds <0){perror("create error");return -1;}// 封装函数添加epoll_fd_add(epfds, 0, EPOLLIN);epoll_fd_add(epfds, fifofd, EPOLLIN);// 保存到达事件的集合struct epoll_event evs[MAX_FD_CNT];while(1){// 阻塞等待事件到达int cnt = epoll_wait(epfds, evs, MAX_FD_CNT, -1);if(cnt < 0){perror("epoll_wait error");return -1;}for(int i = 0; i <cnt; i++){if(0 == evs[i].data.fd){fgets(buff, sizeof(buff), stdin);printf("STDIN : %s\n", buff);}else if(fifofd == evs[i].data.fd){memset(buff, 0, sizeof(buff));read(evs[i].data.fd, buff, sizeof(buff));printf("FIFO : %s\n", buff);}}}close(fifofd);return 0;
}
向管道中写入数据
#include "head.h"int main(int argc, char const *argv[])
{mkfifo("./wrfifo", 0664);int fd = open("./wrfifo", O_WRONLY);if(fd < 0){perror("open error");return -1;}while(1){write(fd, "China", strlen("China"));sleep(1);}close(fd);return 0;
}
从网络套接字中获取数据
#include "head.h"#define SER_PORT 50000
#define SER_ID "192.168.0.164"
#define MAX_FD_CNT 10086 int init_tcp_ser()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket error");return -1;}struct sockaddr_in seraddr;seraddr.sin_family = AF_INET;seraddr.sin_port = htons(SER_PORT);seraddr.sin_addr.s_addr = inet_addr(SER_ID);int ret = bind(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));if(ret < 0){perror("bind error");return -1; }ret = listen(sockfd, 10);if(ret < 0){perror("listen error");return -1;}return sockfd;
}int epoll_fd_add(int spfds, int fd, uint32_t events)
{struct epoll_event ev;ev.events = events;ev.data.fd = fd;int ret = epoll_ctl(spfds, EPOLL_CTL_ADD, fd, &ev);if(ret < 0){perror("epoll_ctl error");return -1;}return 0;
}// 删除
int epoll_fd_del(int spfds, int fd, uint32_t events)
{int ret = epoll_ctl(spfds, EPOLL_CTL_DEL, fd, NULL);if(ret < 0){perror("epoll_ctl error");return -1;}return 0;
}int main(int argc, char const *argv[])
{char buff[1024] = {0};struct sockaddr_in cliaddr;socklen_t clilen = sizeof(cliaddr);int sockfd = init_tcp_ser();if(sockfd < 0){return -1;}// 创建集合int epfds = epoll_create(MAX_FD_CNT);if(epfds <0){perror("create error");return -1;}// 封装函数添加epoll_fd_add(epfds, sockfd, EPOLLIN);// 保存到达事件的集合struct epoll_event evs[MAX_FD_CNT];while(1){int cnt = epoll_wait(epfds, evs, MAX_FD_CNT, -1);if(cnt < 0){perror("epoll_wait error");return -1;}for(int i = 0; i < cnt; i++){if(sockfd == evs[i].data.fd){int connfd = accept(sockfd, (struct sockaddr *)&cliaddr, &clilen);if(connfd < 0){perror("accept error");close (sockfd);return -1;}epoll_fd_add(epfds, connfd, EPOLLIN);}else{memset(buff, 0, sizeof(buff));ssize_t num = recv(evs[i].data.fd, buff, sizeof(buff), 0);if(num < 0){perror("recv error");epoll_fd_del(epfds, evs[i].data.fd, EPOLLIN); // i 错误 ,从集合中删除iclose(evs[i].data.fd); // 先删除再关闭continue; // 不能使用 break return}else if (num == 0){epoll_fd_del(epfds, evs[i].data.fd, EPOLLIN);close(evs[i].data.fd);continue; }printf("%s\n", buff);strcat(buff , "-----> ok");num = send(evs[i].data.fd, buff, sizeof(buff), 0);if(num < 0){perror("send error");epoll_fd_del(epfds, evs[i].data.fd, EPOLLIN); close(evs[i].data.fd);continue;}printf("%s\n", buff);}} }close(sockfd);return 0;
}