网站建立失败的原因是,手机浏览器主页网站推荐,辽宁建设工程信息网ca锁激活,网页设计aiIO多路复用 —— epoll
前言
在之前我们就已经介绍过了select和poll,在作为io多路复用的最后一个的epoll,我们来总结一下它们之间的区别: a
select
实现原理
select 通过一个文件描述符集合#xff08;fd_set#xff09;来工作#xff0c;该集合可以包含需要监控的文件…IO多路复用 —— epoll
前言
在之前我们就已经介绍过了select和poll,在作为io多路复用的最后一个的epoll,我们来总结一下它们之间的区别: a
select
实现原理
select 通过一个文件描述符集合fd_set来工作该集合可以包含需要监控的文件描述符。调用者会指定一个超时时间如果在这个时间内没有任何描述符准备好则函数返回。select 可以同时监听读、写和异常三种类型的事件。
优点
select 函数调用和实现比较简单同时它支持跨平台
缺点 select是基于位图这一数据结构来存储与遍历文件描述符相关的信息由于位图数据结构的限制select最多能同时监听1024个文件描述符如果超过1024个文件描述符,无法实现大规模的并发处理。 每次调用select需要拷贝位图而且select属于用户态网络通信属于内核态需要拷贝两次会影响select的性能。 select的每次监听都需要遍历一整个位图随着需要监听的socket增加性能会大大下降。
poll
实现原理
poll 通过一个链表来存储需要监控的文件描述符当文件描述符就绪时链表中的节点会被移动到就绪链表中当链表为空时poll会阻塞。poll与select类似也是通过一个文件描述符集合来工作但是poll所使用的数据结构是一个结构体数组它的结构如下:
struct pollfd
{int fd; //存储的socketshort events; // socket触发的事件short revents; // 返回的事件
}优点
poll 函数调用和实现简单同时它支持跨平台同时相对于selectpoll没有了1024的限制可以实现对更多文件描述符的监听。
缺点
poll监视的连接数没有1024的限制但是随着socket的增多poll的效率会降低无法处理超大规模并发。
epoll
epoll的原理
epoll 全名是 eventpoll,是Linux内核2.5.44 版本之后才出现的一个事件通知机制。它属于IO多路复用技术的一种形式,IO多路复用技术指的是一个操作里同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其进行读写操作。上面的select和poll都具有一个通病,那就是都有性能瓶颈,而epoll可以承受百万级别的连接属于select和poll模式的升级版。
epoll的优点 select和poll监听都是线性检测的而epoll是基于红黑树来管理文件描述符,对于事件的发生它不像select和poll那样需要遍历整个文件描述符集合而是通过回调函数来通知所以epoll的效率更高. select和poll在工作中需要对集合进行判断来看哪些文件描述符已经就绪而epoll则不需要epoll通过回调函数来通知所以epoll的效率更高。
当我们需要监听大量的文件描述符时epoll的效率会更高所以epoll是当前最常用的IO多路复用技术。
epoll的操作函数
在Linux内核中主要给我们提供了一下三个函数来操作epoll:
#include sys/epoll.h
//创建一个epoll实例,通过红黑树来管理文件描述符
int epoll_create(int size);
//管理红黑树中的文件描述符(添加,修改,删除)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
//等待文件描述符就绪
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);epoll_create
epoll_create函数用于创建一个epoll实例参数size表示要监听的文件描述符的数量但是这个参数在Linux 2.6.8之后已经没有意义了因为epoll的红黑树可以动态扩展所以大于0即可。
int epfd epoll_create(0);返回值:
成功:返回epoll实例的文件描述符失败:返回-1,并设置errno
epoll_ctl
epoll_ctl函数用于管理红黑树中的文件描述符参数epfd表示epoll实例的文件描述符参数op表示要进行的操作参数fd表示要监听的文件描述符参数event表示要监听的事件。
在epoll中有以下和事件相关的数据结构
typedef union epoll_data {void *ptr;int fd; // 通常情况下使用这个成员, 和epoll_ctl的第三个参数相同即可uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event {uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
};
而在epool_ctl函数中主要要注意下面几个参数
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);epfd:epoll实例的文件描述符op:要进行的操作 EPOLL_CTL_ADD:添加文件描述符EPOLL_CTL_MOD:修改文件描述符EPOLL_CTL_DEL:删除文件描述符 fd:要监听的文件描述符event:epoll事件,用来修饰fd,指定检测什么事件 events:事件类型 EPOLLIN:读事件EPOLLOUT: 写事件EPOLLERR: 错误事件 data:用户数据,通常情况下使用fd即可
epoll_wait
epoll_wait函数用于等待事件的发生参数epfd表示epoll实例的文件描述符参数events表示要监听的事件参数maxevents表示最多监听多少个事件参数timeout表示等待时间。
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);epfd:epoll实例的文件描述符events:要监听的事件maxevents:最多监听多少个事件timeout:等待时间 0函数不阻塞不管epoll实例中有没有就绪的文件描述符函数被调用后都直接返回大于0如果epoll实例中没有已就绪的文件描述符函数阻塞对应的毫秒数再返回-1函数一直阻塞直到epoll实例中有已就绪的文件描述符之后才解除阻塞
epoll的使用
这里为实现了一个简单的基于epoll实现的服务端与客户端通讯大家可以自己测试一下: //server.cpp
#include iostream
#include unistd.h
#include ctype.h
#include stdlib.h
#include sys/stat.h
#include sys/socket.h
#include arpa/inet.h
#include sys/epoll.h
#include string.hint main(int argc,char* argv[])
{if(argc!3){std::cout命令行参数数量不对std::endl;std::cout./demo port timeoutstd::endl;exit(-1);}// 创建套接字int lfdsocket(AF_INET,SOCK_STREAM,0);if(lfd0){perror(create socket);return -1;}//绑定struct sockaddr_in server_addr;memset(server_addr,0,sizeof(server_addr));server_addr.sin_porthtons(atoi(argv[1]));server_addr.sin_familyAF_INET;server_addr.sin_addr.s_addrhtonl(INADDR_ANY);//设置端口复用int opt1;setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt));//绑定端口int retbind(lfd,(struct sockaddr*)server_addr,sizeof(server_addr));if(ret0){perror(bind);exit(-1);}//监听retlisten(lfd,10);if(ret0){perror(listen);}//创建epoll实例int epfdepoll_create(100);if(epfd0){perror(epfd);exit(-1);}struct epoll_event ev;ev.data.fdlfd;ev.eventsEPOLLIN;retepoll_ctl(epfd,EPOLL_CTL_ADD,lfd,ev);if(ret0){perror(epoll_ctl);}struct epoll_event evs[1024];int size(sizeof(evs)/sizeof(struct epoll_event));while(1){int numepoll_wait(epfd,evs,size,atoi(argv[2]));for(int i0;inum;i){int fdevs[i].data.fd;if(fdlfd) //如果是监听的socket{int cfdaccept(fd,0,0); //接收客户端的连接ev.eventsEPOLLIN;ev.data.fdcfd;int retepoll_ctl(epfd,EPOLL_CTL_ADD,cfd,ev);if(ret0){perror(epoll_ctl);exit(-1);}}else //不是监听的说明要接收客户端的消息{char buffer[1024];memset(buffer,0,sizeof(buffer));int lenrecv(fd,buffer,sizeof(buffer)-1,0);std::coutlenstd::endl;if(len0) //客户端已经断开连接{int retepoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);if(ret0){perror(epoll_ctl_del);exit(-1);}close(fd);}else if(len0){std::cout333std::endl;std::coutclient:bufferstd::endl;char* recebufok;send(fd,recebuf,sizeof(recebuf),0);}else{perror(recv);exit(-1);}}}}return 0;
}//client.cpp
#include iostream
#include unistd.h
#include ctype.h
#include stdlib.h
#include sys/socket.h
#include arpa/inet.h
#include string.hint main(int argc, char* argv[])
{if (argc ! 3) {std::cout 命令行参数数量不对 std::endl;std::cout ./client server_ip port std::endl;return -1;}// 创建套接字int sockfd socket(AF_INET, SOCK_STREAM, 0);if (sockfd 0) {perror(socket);return -1;}// 设置服务器地址信息struct sockaddr_in server_addr;memset(server_addr, 0, sizeof(server_addr));server_addr.sin_family AF_INET;server_addr.sin_port htons(atoi(argv[2]));if (inet_pton(AF_INET, argv[1], server_addr.sin_addr) 0) {perror(inet_pton);close(sockfd);return -1;}// 连接到服务器if (connect(sockfd, (struct sockaddr*)server_addr, sizeof(server_addr)) 0) {perror(connect);close(sockfd);return -1;}// 发送数据给服务器const char* sendbuf Hello from client!;ssize_t send_len send(sockfd, sendbuf, strlen(sendbuf), 0);if (send_len 0) {perror(send);close(sockfd);return -1;}std::cout Sent: sendbuf std::endl;// 接收服务器的数据char buffer[1024];memset(buffer, 0, sizeof(buffer));ssize_t recv_len recv(sockfd, buffer, sizeof(buffer)-1, 0);if (recv_len 0) {std::cout Received from server: buffer std::endl;} else if (recv_len 0) {std::cout Server closed the connection. std::endl;} else {perror(recv);}// 关闭套接字close(sockfd);return 0;
}# makefile
all: client serverserver: server.cppg -g -o server server.cppclient: client.cppg -g -o client client.cppepoll的工作模式
epoll有两种工作模式LTLevel Triggered水平触发和ETEdge Triggered边缘触发。
LT模式
LT又叫水平模式,是epoll的默认工作模式。在这种模式下内核会不断地通知你文件描述符是否就绪即使你已经读取了数据。也就是说即使你读取了数据文件描述符仍然被认为是就绪的内核会继续通知你。这种模式适用于需要不断地检查文件描述符是否就绪的场景。
水平模式主要有以下特点:
这是epoll的默认工作模式。在LT模式下当一个文件描述符准备好进行读写操作时epoll会通知应用程序。如果应用程序没有立即处理该事件或者在处理过程中没有完全读取或写入所有数据那么只要文件描述符仍然处于就绪状态epoll将继续通知应用程序。
其实对于大多数应用来说LT模式足够使用并且它的行为与传统的poll和select相似
ET模式
ET又叫边缘模式是epoll的高级模式。在这种模式下内核只会通知你文件描述符从非就绪状态变为就绪状态一次即使你读取了数据文件描述符仍然被认为是非就绪的内核不会继续通知你。这种模式适用于需要高效处理大量并发连接的场景。
边缘模式主要有以下特点:
ET模式是一种低延迟、高性能的工作模式。在ET模式下epoll只会在状态发生变化时通知应用程序一次。例如如果一个文件描述符从非就绪变为就绪epoll将通知应用程序但是如果应用程序未能在第一次通知后立即处理完所有可用的数据那么即使该文件描述符仍然是就绪状态epoll也不会再次发送通知直到该文件描述符的状态再次发生变化即从就绪变回非就绪再由非就绪变成就绪。
因此在ET模式下应用程序必须确保每次收到通知时都尽可能多地读取或写入数据以避免错过后续的通知。这通常意味着在循环中尽可能多地尝试读写操作直到遇到EAGAIN或EWOULDBLOCK错误为止这表明当前没有更多可读写的就绪数据。
PS:
LT模式会不断通知应用程序即使应用程序已经开始读取数据,这样可以保证应用程序能够及时处理数据但是 频繁的通知会带来性能的损耗而ET模式只会在状态发生变化时通知应用程序一次因此应用程序需要确保每次收到通知时都尽可能多地读取或写入数据以避免错过后续的通知。所以ET模式要求应用程序在每次收到通知时都尽可能多地读取或写入数据否则可能会错过后续的通知。因此ET模式通常需要更复杂的编程逻辑并且对应用程序的设计和实现有更高的要求。
2.我们在使用ET模式要设置EPOLLET标志如下:
if(fdlfd) //如果是监听的socket
{int cfdaccept(fd,0,0); //接收客户端的连接ev.eventsEPOLLIN|EPOLLET; //设置边沿触发ev.data.fdcfd;int retepoll_ctl(epfd,EPOLL_CTL_ADD,cfd,ev);if(ret0){perror(epoll_ctl);exit(-1);}
}3.LT模式下支持阻塞和非阻塞而ET模式下只支持非阻塞。