一.epoll介绍
epoll是linux下多路io复用接口select/poll的增强版,它能显著提高程序在大量并发连接中少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要要被监听的文件描述符集合,另一点原因就是获取事件的时候,无需遍历整个被监听的的文件描述符集合,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。【什么意思呢:比如 教作业,老师(转接模型), n多个学生(client),当少数学生作业写好了(有IO事件了),老师如果是select/poll模型的话,就一个一个问学生,老师:你写完了嘛?(这样效率怎么会高呢),但如果 老师是epoll模型的话,就是 这样的情况了,少数学生作业写完之后,直接举手表示写完,老师直接到他那里去收就行了】
目前epoll是linux大规模并发网络程序中的热门首选模型
epoll除了提供select / poll 那种IO 事件的电平触发(level triggered),还提供了边沿触发(edge triggered)【后面单独一篇讲解】,这就使得用户空间程序有可能缓存IO状态,减少 epoll_wait 和 epoll_pwait的调用,提高了程序的执行效率。
二.epoll相关函数
1.创建一个epoll句柄,参数size【建议值:意思说你超过了也没有关系】用来告诉内核监听的文件描述符的个数,跟内存大小有关。
#include <sys/epoll.h>
int epoll_create(int size) // size:监听的数目
2.控制某个epoll监控的文件描述符上的事件:注册,修改,删除
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
epfd:为epoll_create返回的epoll句柄,(根结点fd)
op:表示动作,用三个宏来表示
EPOLL_CTL_ADD:向红黑树上加入fd【注册】
EPOLL_CTL_DEL:从红黑树上摘下fd【删除】
EPOLL_CTL_MOD:修改 已经在红黑树上的fd的 监听事件【修改】
event: 告诉内核需要监听的事件【结构体】
struct event{
_uint32_t events; // 监听事件
epoll_data_t data; // 用户数据
}
typedef union epoll_data{// 联合体 同一时间只有一种是格式有效 前面C基础的文章已经介绍过了
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
}epoll_data_t
events监听事件类型:
EPOLLIN : 表示读事件 (包括对端socket 正常关闭)
EPOLLOUT: 表示写事件
EPOLLPRI: 表示 文件描述符有紧急的数据可读
EPOLLERR: 表示文件描述符发生错误
EPOLLHUP: 表示文件描述符被挂断
EPOLLET: 表示 边沿触发
EPOLLONESHOT: 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到epoll中
注意:红色部分是重点掌握的
3.等待所监控的文件描述符上有事件的发生, 类似于 select(...) / poll(....)
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
events: 用来存内核的事件集合【传出参数】
maxevents: 用来告诉内核 events (数组)有多大
timeout: 是超时时间
- -1 : 阻塞等待 事件发生
- 0: 立即返回,非阻塞(epoll_wait:没事件发生,算了 老子不等你们了)
- >0 : 等待指定时间
返回值:成功返回多少个文件描述符就绪,时间到时 返回 0, 出错返回-1
下面我画的一张原理图【有点搓,见谅了】
代码Demo
#include <iostream>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <string.h>
#include <unistd.h>
using namespace std;
#define MAXLINE 8192
#define SERVER_PORT 9999
#define OPEN_MAX 4000
#define LISTEN_NUM 100
static int Debug = 1;
void
perror_exit(const char* strerr)
{
perror(strerr);
exit(1);
}
int
main(int argc, char*argv[])
{
int i, listenfd, connfd, sockfd;
int n, num = 0;
ssize_t nready, efd, res;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clie_addr_len;
struct sockaddr_in clie_addr, serv_addr;
struct epoll_event tep, ep[OPEN_MAX];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERVER_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
listen(listenfd, LISTEN_NUM);
if(Debug)printf("------listening------\n");
efd = epoll_create(OPEN_MAX);
if(efd == -1)
perror_exit("create epoll error");
tep.events = EPOLLIN;
tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if(res == -1)
perror_exit("listenfd add error in epoll_ctl");
while(true) {
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
if(nready == -1)
perror_exit("epoll_wait error");
for(i=0; i<nready; i++){
if(!(ep[i].events & EPOLLIN)) // 这里我们只处理 读事件 其他的都随浮云把
continue;
if(ep[i].data.fd == listenfd){ // have client connect event
clie_addr_len = sizeof(clie_addr);
connfd = accept(listenfd, (struct sockaddr*)&clie_addr, &clie_addr_len);
if(Debug)printf("client IP:%s, PORT:%d \n",
inet_ntop(AF_INET, &clie_addr.sin_addr, str, sizeof(str)),
ntohs(clie_addr.sin_port));
num++;
if(Debug)printf("connfd:%d ----- client %d \n", connfd, num);
tep.events = EPOLLIN;
tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
if(res == -1)
perror_exit("connfd add error in epoll_ctl");
}else{
sockfd = ep[i].data.fd;
n = read(sockfd, buf, sizeof(buf));
if(n == 0){
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if(res == -1)
perror_exit("sockfd delete error in epoll_ctl");
close(sockfd);
printf("client[%d] closed connection \n", num);
num--;
}else if(n < 0){
perror("read n < 0 error");
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if(res == -1)
perror_exit("sockfd delete error in epoll_ctl");
close(sockfd);
num--;
}else{
buf[n] = '\0';
if(Debug)printf("test from client is %s \n", buf);
for(i=0; i<n; i++)
buf[i] = toupper(buf[i]);
write(sockfd, buf, n);
}
}
}
}
res = epoll_ctl(efd, EPOLL_CTL_DEL, listenfd, NULL);
close(listenfd);
return 0;
}