1. epoll模型简介
epoll是Linux多路服用IO接口select/poll的增强版,e对应的英文单词就是enhancement,中文翻译为加强,增强,提升,充实的意思。因此epoll模型会显著提升程序在大量并发链接中只有少许活跃的状况下的系统CPU利用率。vim
epoll把用户关心的文件描述符上的时间放在内核的一个事件表中,无需像select和poll那样每次调用都重复传入文件描述符集。
epoll在获取事件的时候,无需遍历整个被监听的文件描述符集合,而是遍历那些被内核IO事件异步唤醒而加入ready队列的描述符集合。
因此,epoll是Linux大规模高并发网络程序的首选模型。缓存
2.epoll模型的API
epoll使用一组函数来完成任务服务器
建立一个epoll句柄,句柄的英文是handle,相通的意思是把手,把柄。网络
#include <sys/epoll.h> int epoll_create(int size); //返回值:若成功,返回一个非负的文件描述符,若出错,返回-1。
查看进程可以打开的最大数目的文件描述符并发
cat /proc/sys/fs/file-max 1215126 //该值与内存大小有关
修改最大文件描述符限制app
vim /etc/security/limits.conf //重启生效
该函数用来操做epoll的内核事件表异步
#include <sys/epoll.h> int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); //返回值:若成功,返回0,若出错返回-1。
epfd就是函数epoll_create建立的句柄。
op是指定操做类型,有一下三种
EPOLL_CTL_ADD,向epfd注册fd的上的event
EPOLL_CTL_MOD,修改fd已注册的event
EPOLL_CTL_DEL,从epfd上删除fd的event
1.fd是操做的文件描述符
2.event指定内核要监听事件,它是struct epoll_event结构类型的指针。epoll_event定义以下:socket
struct epoll_event { uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };
events成员描述事件类型,将如下宏定义经过位或方式组合函数
EPOLLIN :表示对应的文件描述符能够读(包括对端SOCKET正常关闭)
POLLOUT:表示对应的文件描述符能够写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来讲的
EPOLLONESHOT:只监听一次事件,当监听完此次事件以后,若是还须要继续监听这个socket的话,须要再次把这个socket加入到EPOLL队列里
data用于存储用户数据,是epoll_data_t结构类型,该结构定义以下:高并发
typedef union epoll_data { void *ptr; int fd; uint32_t u32; uint64_t u64; } epoll_data_t;
函数epoll_wait用来等待所监听文件描述符上有事件发生
#include <sys/epoll.h> int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout); //返回值:若成功,返回就绪的文件描述符个数,若出错,返回-1,时间超时返回0
epfd就是函数epoll_create建立的句柄
timeout是超时事件,-1为阻塞,0为当即返回,非阻塞,大于0是指定的微妙
events是一个 传入传出参数,它是epoll_event结构的指针,用来从内核获得事件的集合
maxevents告知内核events的大小,但不能大于epoll_create()时建立的size
3. LT和ET模式
LT(Level Triggered,电平触发):LT模式是epoll默认的工做模式,也是select和poll的工做模式,在LT模式下,epoll至关于一个效率较高的poll。
采用LT模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序能够不当即处理此事件,当下一次调用epoll_wait是,epoll_wait还会将此事件通告应用程序。
ET(Edge Triggered,边沿触发):当调用epoll_ctl,向参数event注册EPOLLET事件时,epoll将以ET模式来操做该文件描述符,ET模式是epoll的高效工做模式.
对于采用ET模式的文件描述符,当epoll_wait检测到其上有事件发生并将此通知应用程序后,应用程序必须当即处理该事件,由于后续的epoll_wait调用将不在向应用程序通知这一事件。ET模式下降了赞成epoll事件被触发的次数,效率比LT模式高。
4. LT和ET的服务端和客户端代码
4.1 服务器端
#include <sys/epoll.h> #include <fcntl.h> #include "wrap.h" #define MAX_EVENT_NUM 1024 #define BUFFER_SIZE 10 #define true 1 #define false 0 int setnonblocking(int fd) { int old_opt = fcntl(fd, F_GETFD); int new_opt = old_opt | O_NONBLOCK; fcntl(fd, F_SETFD, new_opt); return old_opt; }//将文件描述符设置为非阻塞的 void addfd(int epollfd, int fd, int enable_et) { struct epoll_event event; event.data.fd = fd; event.events = EPOLLIN; if(enable_et){ event.events |= EPOLLET; } epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); // setnonblocking(fd); }//将文件描述符fd的EPOLLIN注册到epollfd指示的epoll内核事件表中,enable_et表示是否对fd启用ET模式 void lt(struct epoll_event *events, int num, int epollfd, int listenfd) { char buf[BUFFER_SIZE]; for(int i = 0; i < num; i++){ int sockfd = events[i].data.fd; if(sockfd == listenfd){ struct sockaddr_in clientaddr; socklen_t clilen = sizeof(clientaddr); int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen); addfd(epollfd, connfd, false);//对connfd使用默认的lt模式 }else if(events[i].events & EPOLLIN){//只要socket读缓存中还有未读的数据,这段代码就会触发 printf("event trigger once\n"); memset(buf, '\0', BUFFER_SIZE); int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0); if(ret <= 0){ Close(sockfd); continue; } printf("get %d bytes of content:%s\n", ret, buf); }else{ printf("something else happened\n"); } } } void et(struct epoll_event *event, int num, int epollfd, int listenfd) { char buf[BUFFER_SIZE]; for(int i = 0; i < num; i++){ int sockfd = event[i].data.fd; if(sockfd == listenfd){ struct sockaddr_in clientaddr; int clilen = sizeof(clientaddr); int connfd = Accept(listenfd, (struct sockaddr *)&clientaddr, &clilen); addfd(epollfd, connfd, true);//多connfd开启ET模式 }else if(event[i].events & EPOLLIN){ printf("event trigger once\n"); while(1){//这段代码不会重复触发,因此要循环读取数据 memset(buf, '\0', BUFFER_SIZE); int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0); if(ret < 0){ if((errno == EAGAIN) || (errno == EWOULDBLOCK)){ printf("read later\n"); break; } Close(sockfd); break; }else if(ret == 0){ Close(sockfd); }else{ printf("get %d bytes of content:%s\n", ret, buf); } } }else{ printf("something else happened \n"); } } } int start_ser(char *ipaddr, char *port) { int sock = Socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serveraddr; bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi(port)); inet_pton(AF_INET, ipaddr, &serveraddr.sin_addr); Bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); Listen(sock, 128); return sock; } int main(int argc, char *argv[]) { int listenfd = start_ser(argv[1], argv[2]); struct epoll_event events[MAX_EVENT_NUM]; int epollfd = epoll_create(5); if(epollfd < 0){ perr_exit("epoll_create err"); } addfd(epollfd, listenfd, true); while(1){ int ret = epoll_wait(epollfd, events, MAX_EVENT_NUM, -1); if(ret < 0){ printf("epoll failure\n"); break; } lt(events, ret, epollfd, listenfd);//lt模式 //et(events, ret, epollfd, listenfd);//et模式 } Close(listenfd); return 0; } //warp.h文件是将socket,bind,listen等函数封装为第一个字母大写的头文件
#include "wrap.h" int main(int argc, char *argv[]) { int connfd; struct sockaddr_in serveraddr; char buf[1024]; connfd = Socket(AF_INET, SOCK_STREAM, 0); bzero(&serveraddr, sizeof(serveraddr)); serveraddr.sin_family = AF_INET; serveraddr.sin_port = htons(atoi(argv[2])); inet_pton(AF_INET, argv[1], &serveraddr.sin_addr); Connect(connfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)); while(fgets(buf, 1024, stdin) != NULL){ Write(connfd, buf, strlen(buf)); } Close(connfd); return 0; }