一、创建epoll_create()
1.功能
是 Linux 系统中用于创建一个 epoll 实例的系统调用。epoll 是一种高效的 I/O 事件通知机制,通常用于高性能网络服务器或需要处理大量文件描述符的场景。
2.函数原型
int epoll_create(int size);
3.参数
①. size: 指定 epoll 实例可以监控的最大文件描述符数量。这个参数在 Linux 内核 2.6.8 及更高版本中实际上被忽略,因为内核会动态调整资源分配。
4.返回值
①. 成功时返回一个非负的文件描述符,表示创建的 epoll 实例。 ②. 失败时返回 -1,并设置 errno 为相应的错误码。
二、上树、下树、修改epoll_ctl()
1.功能
用于向 epoll 实例中添加、修改或删除文件描述符的事件监听。
2.函数原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
3.参数
①. epfd: 通过 epoll_create 或 epoll_create1 创建的 epoll 实例的文件描述符。 ②. op: 操作类型,可以是以下之一: EPOLL_CTL_ADD: 添加一个新的文件描述符到 epoll 实例中。 EPOLL_CTL_DEL: 从 epoll 实例中删除一个文件描述符。 EPOLL_CTL_MOD: 修改一个已存在的文件描述符的事件掩码。 ③. fd: 需要操作的文件描述符。 ④. event: 指向 epoll_event 结构体的指针,用于指定监听的事件类型和关联数据。
struct epoll_event {
uint32_t events; // 事件掩码
epoll_data_t data; // 用户数据
};
events: 指定需要监听的事件类型,例如 EPOLLIN(可读)、EPOLLOUT(可写)等。 data: 用户数据,可以是文件描述符、指针或其他自定义数据。
4.返回值
①. 成功时返回 0。 ②. 失败时返回 -1,并设置 errno 为相应的错误码。
三、监听epoll_wait()
1.功能
是 Linux 系统中用于等待 epoll 实例中事件发生的系统调用。它是 epoll 机制的核心部分,用于阻塞等待直到有事件发生或超时。
2.函数原型
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
3.参数
①. epfd: 通过 epoll_create 或 epoll_create1 创建的 epoll 实例的文件描述符。 ②. events: 用于存储返回的事件的数组指针。每个事件由一个 epoll_event 结构体表示。 ③. maxevents: 指定 events 数组的最大容量。必须大于 0。 ④. timeout: 指定等待事件发生的超时时间(以毫秒为单位)。可以是以下值: 正数:等待指定的毫秒数。 0:立即返回,不阻塞。 -1:无限期阻塞,直到有事件发生。
4.返回值
①. 成功时返回触发事件的数量(可能小于 maxevents)。 ②. 失败时返回 -1,并设置 errno 为相应的错误码。 ③. 如果超时返回 0。
四、epoll的两种触发模式
1.水平触发(Level-Triggered,LT)
水平触发是epoll的默认模式,它在文件描述符处于就绪状态时持续通知应用程序,直到事件被处理完毕。 ①. 触发条件:只要文件描述符处于就绪状态(例如可读或可写),epoll_wait就会持续返回该事件。 ②. 事件处理:应用程序需要持续轮询事件,直到事件不再就绪。 ③. 适用场景:适用于简单的场景,特别是当事件处理较快时。
2.边缘触发(Edge-Triggered,ET)
边缘触发只在文件描述符的状态发生变化时通知应用程序一次。
①. 触发条件:只有当文件描述符的状态从非就绪变为就绪时,epoll_wait才会返回该事件。 ②. 事件处理:应用程序需要一次性处理所有就绪的事件,因为后续即使事件仍然就绪,也不会再次通知。 ③. 适用场景:适用于高并发场景,因为它减少了事件通知的次数,从而提高了性能。
五、程序示例
1.管道中运用epoll
#include <stdio.h>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
int main()
{
int fd[2];
pid_t pid;
pipe(fd);
pid = fork();
if(pid < 0)
perror("fork");
else if(pid == 0){
close(fd[0]);
char buf[5];
char ch = 'a';
while(1){
sleep(3);
memset(buf,ch++,sizeof(buf));
write(fd[1], buf, 5);
}
}
else{
close(fd[1]);
int epfd = epoll_create(1);
struct epoll_event ev, evs[1];
ev.data.fd = fd[0];
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd[0], &ev);
while(1){
int n = epoll_wait(epfd, evs, 1, -1);
if(n == 1){
char buf[128] = "";
int ret = read(fd[0], buf, sizeof(buf));
if(ret <= 0){
close(fd[0]);
epoll_ctl(epfd, EPOLL_CTL_DEL,fd[0], &ev);
break;
}
else{
printf("%s\\n", buf);
}
}
}
}
return 0;
}
2.epoll高并发服务器
①.创建套接字。Ifd = tcp4bind=>socket, bind ②.套接字监听。Listen ③.创建树。epfd = epoll_create ④.将Ifd上树。初始化ev、epoll_ctl ⑤.监听while{ ⑥.监听epfd。nready = epoll_wait ⑦.如果返回值nready<0,break跳出循环。 ⑧.如果返回值nready =0,contine继出续循环, ⑨. 如果返回值nready>0,说明epfd监听到变化 ⑩ . 如果是cfd变化,连接cfd。Accept⑪.cfd上树。epoll_ct ⑫.如果是cfd变化 ⑬.检查数据while(1){ ⑭.读客户端的数据n = read() ⑮.如果n == 0,则关闭cfd,并且下树 ⑯.如果n>0,使用write将日志打印到终端页面,并write写回客户端。 } }
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <error.h>
#include <fcntl.h>
#include "wrap.h"
#include <sys/epoll.h>
int main()
{
//创建套接字
int lfd = tcp4bind(8000, NULL);
//套接字监听
Listen(lfd, 128);
//创建树
int epfd = epoll_create(1);
//将lfd上树
struct epoll_event ev, evs[1024];
ev.data.fd = lfd;
ev.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
//监听
while(1){
int nready = epoll_wait(epfd, evs, 1024, -1);
printf("epoll_wait——- \\n");
if(nready < 0){
perror("");
break;
}
else if(nready == 0){
continue;
}
else{
int i;
for(i = 0; i <nready; i++){
//lfd变化
if(evs[i].data.fd == lfd && evs[i].events & EPOLLIN){
struct sockaddr_in cliaddr;
char ip[16] = "";
socklen_t len = sizeof(cliaddr);
int cfd = Accept(lfd,(struct sockaddr *)&cliaddr, &len);
printf("new client ip = %s, port = %d\\n", inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ip, 16), ntohs(cliaddr.sin_port));
//设置cfd非堵塞
int flags = fcntl(cfd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(cfd, F_SETFL,flags);
//cfd上树
ev.data.fd = cfd;
ev.events = EPOLLIN | EPOLLET; //设置为边缘触发
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
}
else if(evs[i].events & EPOLLIN){
//cfd变化
while(1){
char buf[4] = "";
//如果读一个缓冲区,缓冲区没有数据,如果是带阻塞,就阻塞等待,如果
//是非阻塞,返回值等于-1,并且会将errno 值设置为EAGAIN
int n = read(evs[i].data.fd, buf, sizeof(buf));
if(n < 0){
if(errno == EAGAIN){
break;
}
//普通错误
perror("");
close(evs[i].data.fd);//关闭cfd
epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]);//将cfd下树
break;
}
else if(n == 0){
printf("client colse\\n");
close(evs[i].data.fd);//关闭cfd
epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &evs[i]);//将cfd下树
break;
}
else{
write(STDOUT_FILENO, buf, 4);
write(evs[i].data.fd, buf, n);
}
}
}
}
}
}
return 0;
}
六、epoll机制
参考:
Linux epoll完全图解,彻底搞懂epoll机制 – 知乎
评论前必须登录!
注册