1.定义
epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。
2.工作方式
LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。
ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。
区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。
3.epool使用步骤
1). 创建一个epoll句柄,参数size用来告诉内核监听的数目
int epoll_create(int size)
2). epoll事件注册函数(ADD,MOD,DEL)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
参数epfd为epoll的句柄;
参数op表示动作,用3个宏来表示:
EPOLL_CTL_ADD(注册新的fd到epfd),
EPOLL_CTL_MOD(修改已经注册的fd的监听事件),
EPOLL_CTL_DEL(从epfd删除一个fd);
参数fd为需要监听的标示符;
参数event告诉内核需要监听的事件,event的结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64
}epoll_data_t;</span>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
参数epfd为epoll的句柄;
参数op表示动作,用3个宏来表示:
EPOLL_CTL_ADD(注册新的fd到epfd),
EPOLL_CTL_MOD(修改已经注册的fd的监听事件),
EPOLL_CTL_DEL(从epfd删除一个fd);
参数fd为需要监听的标示符;
参数event告诉内核需要监听的事件,event的结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64
}epoll_data_t;</span>
3).events的宏集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:表示对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
4).等待时间段产生,类似select()调用
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
参数events用来从内核得到事件的集合
参数maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)该函数返回需要处理的事件数目,如返回0表示已超时
4.epoll server例子
/*************************************************************************
> File Name: epoll_socket_comm.c
> Author: Bian xin Dong
> Mail: 690****149@qq.com
> Function:
> Created Time: 2014年09月20日 星期六 10时09分45秒
************************************************************************/
#include <unistd.h>
#include <sys/types.h> /* basic system data types */
#include <sys/socket.h> /* basic socket definitions */
#include <netinet/in.h> /* sockaddr_in{} and other Internet defns */
#include <arpa/inet.h> /* inet(3) functions */
#include <sys/epoll.h> /* epoll function */
#include <fcntl.h> /* nonblocking */
#include <sys/resource.h> /*setrlimit */
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define MAXEPOLLSIZE 10
#define MAXLINE 10240
int handle(int, int);
int main(int argc, char **argv)
{
int listenfd, connfd, kdpfd, nfds, n;
int curfds,acceptCount = 0;
int servPort = 8000;
struct sockaddr_in servaddr, cliaddr;
socklen_t socklen = sizeof(struct sockaddr_in);
struct epoll_event ev;
struct epoll_event events[MAXEPOLLSIZE];
char str[INET_ADDRSTRLEN];
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons (servPort);
listenfd = socket(AF_INET, SOCK_STREAM, 0); // 3
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(struct sockaddr)) == -1){
perror("bind error");
return -1;
}
listen(listenfd, 10);
/* 创建 epoll 句柄,把监听 socket 加入到 epoll 集合里 */
kdpfd = epoll_create(MAXEPOLLSIZE); // 4
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = listenfd;
epoll_ctl(kdpfd, EPOLL_CTL_ADD, listenfd, &ev);
/* 标准输入加入非阻塞属性 */
if(fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK) == -1){
perror("fcnlt error");
exit(1);
}
/* 将标准输入加入 epoll 监听集合 */
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = STDIN_FILENO;
epoll_ctl(kdpfd, EPOLL_CTL_ADD, STDIN_FILENO, &ev);
curfds = 2;
for (;;) {
/* 等待有事件发生 */
nfds = epoll_wait(kdpfd, events, curfds, -1);
/* 处理所有事件 */
for (n = 0; n < nfds; ++n){
if ( events[n].data.fd == listenfd ) {
connfd = accept(events[n].data.fd, (struct sockaddr *)&cliaddr, &socklen);
printf("link with %s at port %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = connfd;
epoll_ctl(kdpfd, EPOLL_CTL_ADD, connfd, &ev);
curfds++;
} else if ( events[n].data.fd == STDIN_FILENO ){
if( handle(STDIN_FILENO, connfd) < 0){
}
} else if (events[n].events & EPOLLIN){
if ( handle(events[n].data.fd, STDOUT_FILENO) < 0) {
epoll_ctl(kdpfd, EPOLL_CTL_DEL, events[n].data.fd,&ev);
close(events[n].data.fd);
curfds--;
}
}
}
}
close(listenfd);
return 0;
}
int handle(int readfd, int writefd)
{
char *rev = "Receive: ";
int nread;
char buf[MAXLINE];
nread = read(readfd, buf, MAXLINE); /* 读入信息 */
if (nread == 0) {
printf("client close the connection\n");
return -1;
}
if (nread < 0) {
perror("read error");
return -1;
}
write(writefd, rev, strlen(rev)); /* 提示语句 */
write(writefd, buf, nread); /* 写出信息 */
return 0;
}
5.示例效果
本博文就到此结束,epoll是linux下处理高并发,很好的选择。希望本博文能给大家提供帮助和参考。