Epoll 的工作模式:LT 模式 (水平触发)假设委托内核检测读事件 -> 检测 fd 的读缓冲区读缓冲区有数据 - > epoll 检测到了会给用户通知a.用户不读数据,数据一直在缓冲区,epoll 会一直通知b.用户只读了一部分数据,epoll 会通知c.缓冲区的数据读完了,不通知LT ( level - triggered )是缺省的工作方式,并且同时支持 block 和 no-block socket 。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的 fd 进行 IO 操作。如果你不作任何操作,内核还是会继续通知你的。
ET 模式(边沿触发)假设委托内核检测读事件 -> 检测 fd 的读缓冲区读缓冲区有数据 - > epoll 检测到了会给用户通知a.用户不读数据,数据一致在缓冲区中,epoll 下次检测的时候就不通知了b.用户只读了一部分数据,epoll 不通知c.缓冲区的数据读完了,不通知ET( edge - triggered )是高速工作方式,只支持 no-block socket 。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll 告诉你。然后它会假设你知道文件描述符已经就绪, 并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个 fd 作 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once )。ET 模式在很大程度上减少了 epoll 事件被重复触发的次数,因此效率要比 LT 模式高。epoll 工作在 ET 模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。 必须是非阻塞加上循环(一次性读完数据)的非阻塞读或写
LT工作模式 代码
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
int main(){
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
// 使用epoll_create() 创建一个epoll实例
int epfd = epoll_create(100);
// 将监听的文件描述符相关信息加入到epoll实例中
struct epoll_event epev;
epev.events=EPOLLIN;
epev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
while (1){
//检测 -1 表示阻塞
struct epoll_event epevs[1024];
int ret = epoll_wait(epfd,epevs,1024,-1);//不阻塞可能返回0
if(ret==-1){
perror("epoll_wait");
exit(-1); // 退出进程
}
printf("ret = %d",ret);
int i =0;
for(i;i<ret;i++){
if(epevs[i].data.fd =lfd){
// 新的客户端连接
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
epev.events=EPOLLIN | EPOLLOUT;
epev.data.fd=cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
}else{
if(epevs[i].events & EPOLLOUT){
continue;
}
// 说明这个文件描述符对应的客户端发来了数据
char buf[5] = {0};
int len = read(epevs[i].data.fd, buf, sizeof(buf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len == 0) {
printf("client closed...\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);
close(epevs[i].data.fd);
} else if(len > 0) {
printf("read buf = %s\n", buf);
write(epevs[i].data.fd, buf, strlen(buf) + 1);
}
}
}
}
close(lfd);
close(epfd);
return 0;
}
ET工作模式 代码
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
常见的Epoll检测事件:
- EPOLLIN
- EPOLLOUT
- EPOLLERR
- EPOLLET
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
int main(){
// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in saddr;
saddr.sin_port = htons(9999);
saddr.sin_family = AF_INET;
saddr.sin_addr.s_addr = INADDR_ANY;
// 绑定
bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
// 监听
listen(lfd, 8);
// 使用epoll_create() 创建一个epoll实例
int epfd = epoll_create(100);
// 将监听的文件描述符相关信息加入到epoll实例中
struct epoll_event epev;
epev.events=EPOLLIN;
epev.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);
while (1){
//检测 -1 表示阻塞
struct epoll_event epevs[1024];
int ret = epoll_wait(epfd,epevs,1024,-1);//不阻塞可能返回0
if(ret==-1){
perror("epoll_wait");
exit(-1); // 退出进程
}
printf("ret = %d",ret);
int i =0;
for(i;i<ret;i++){
if(epevs[i].data.fd =lfd){
// 新的客户端连接
struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
// 设置cfd属性非阻塞
int flag = fcntl(cfd, F_GETFL);
flag= flag | O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
epev.events=EPOLLIN | EPOLLET; //设置边沿触发
epev.data.fd=cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev);
}else{
if(epevs[i].events & EPOLLOUT){
continue;
}
// 循环读取所有的数据
int len =0;
char buf[5];
while ((len = read(epevs[i].data.fd , buf ,sizeof(buf)))>0){ // 对于read函数,是对其文件描述符设置非阻塞或阻塞
// 打印数据
// printf("recv data : %s\n",buf);
write(STDERR_FILENO,buf,len);
write(epevs[i].data.fd,buf,len);
}
if(len==0){
printf("client closed...\n");
epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);
close(epevs[i].data.fd);
}else if(len==-1){
if(errno = EAGAIN){
printf("data over.....");// 非阻塞的情况才会出现,没有数据了,但还是想读
}else{
perror("read");
exit(-1);
}
}
// // 说明这个文件描述符对应的客户端发来了数据
// char buf[5] = {0};
// int len = read(epevs[i].data.fd, buf, sizeof(buf));
// if(len == -1) {
// perror("read");
// exit(-1);
// } else if(len == 0) {
// printf("client closed...\n");
// epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);
// close(epevs[i].data.fd);
// } else if(len > 0) {
// printf("read buf = %s\n", buf);
// write(epevs[i].data.fd, buf, strlen(buf) + 1);
// }
}
}
}
close(lfd);
close(epfd);
return 0;
}