1. epoll_create
int epoll_create(int size);
size: 忽略,但需要大于0
返回值:一个 epoll 专用的文件描述符,当创建好 epoll 句柄后它就是会占用一个 fd 值,在使用完 epoll 后必须调用 close() 关闭,否则可能导致 fd 被耗尽
该函数会建立一个红黑树用于存储通过 epoll_ctl 注册的fd,一个 rdllist 双向链表用于存储准备就绪的fd信息。所有注册到epoll中的事件都会与设备(如网卡)驱动程序建立回调关系,相应事件发生时会调用回调方法 ep_poll_callback,把发生的事件信息 epitem 放到上面的 rdllist 双向链表中
2. epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd: epoll_create 返回的描述符
op: EPOLL_CTL_ADD: 注册新的 fd 到 epfd
EPOLL_CTL_MOD: 修改已经注册的 fd 的监听事件
EPOLL_CTL_DEL: 从 epfd 中删除注册的 fd
fd: 要ADD/MOD/DEL 的 fd
event: 注册的监听事件
struct epoll_event
{
__uint32_t events;
epoll_data_t data;
};
typedef union epoll_data
{
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
注册监听事件,不同于 select 在监听时告诉内核要监听的事件,epoll 提前注册要监听的事件,把事件的注册和监听分离,避免了多个将注册信息拷贝到内核。检查 fd 在红黑树中是否存在,已存在则返回,不存在则添加到树干上,然后向内核注册回调函数,用于当事件发生时向 rdllist 中插入数据
3. epoll_wait
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
epfd: epoll_create 返回的描述符
events: 用户空间分配好的 epoll_event 结构体数组,用于保存事件发生的结果信息。events 不可以是空指针,内核只负责把数据复制到这个数组,不会在用户态中分配内存
maxevents: 允许同时发生事件的最大个数,通常为 events 数组的成员个数
timeout: 指定等待的毫秒数。为 0 表示非阻塞函数立即返回,为 -1 则阻塞一直等待直到至少一个事件发生
返回值:发生事件的个数
epoll_wait 检查是否有事件发生时,只是检查 rdllist 双向链表是否为空,如果不为空则将链表数据复制到用户态内存,同时将发生的事件个数返回给用户。与 select/poll 不同的是,epoll_wait 只返回发生了事件的fd到用户态,因此我们可以直接对返回的fd进行操作,不需再判断该fd是否有事件发生
4. 触发方式
a. LT (level trigger)
系统默认使用 LT 模式
只要 fd 还有数据可读,每次 epoll_wait 都会返回,即缓冲区剩余未读尽的数据会导致下次 epoll_wait 返回。采用LT模式,系统中一旦有大量不需要读写的就绪fd,它们使每次调用epoll_wait都会返回,这样会大大降低处理程序检索自己关心的fd的效率
b. ET
只要 fd 还有数据可读,则必须将数据全部读取,即缓冲区剩余未读尽的数据不会导致下次 epoll_wait 返回。采用ET模式,如果这次没有把数据全部读写完,那么下次调用 epoll_wait() 时它不会通知你,也就是它只会通知你一次,直到该fd下次发生事件才会通知你!!!这种模式比LT效率高,系统不会充斥大量不关心的就绪fd
工作在 ET 模式,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死
5. 优缺点
a. 优点
- 监视的fd数量不受限制。它所支持的 FD 上限是最大可以打开文件的数目
- 查询效率不会随着监听 fd 数量的增长而下降。select/poll 需要不断轮询所有 fd,而 epoll 只需要判断就绪链表是否为空
- select/poll 每次调用都要把 fd 信息从用户态拷贝到内核态,而 epoll 只要 epoll_ctl() 注册时一次拷贝
b. 缺点
- 不能跨平台
-
在连接数较少的时候,效率也不一定会优于 select/poll
6. epoll 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netdb.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <string.h>
#include <arpa/inet.h>
#define MAXLISTEN 5
#define LISTENPORT 8888
#define MAXEVENTS 16
static void bindSocket(int fd)
{
sockaddr_in addr;
bzero(&addr, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
// addr.sin_addr.s_addr = inet_addr("192.168.100.100");
addr.sin_port = htons(LISTENPORT);
bind(fd, (sockaddr*)&addr, sizeof(sockaddr));
}
static void setNonBlock(int fd)
{
int flags = 0;
flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(fd, F_SETFL, flags);
}
int main(int argc, char *argv[])
{
int listenFd, clientFd, eFd;
int nFds, i;
ssize_t nRead;
char bufRead[128] = {0};
struct sockaddr_in clientAddr;
socklen_t len = sizeof(clientAddr);
struct epoll_event registerEvent;
struct epoll_event resultEvents[MAXEVENTS];
listenFd = socket(AF_INET, SOCK_STREAM, 0);
bindSocket(listenFd);
setNonBlock(listenFd);
listen(listenFd, MAXLISTEN);
eFd = epoll_create(1);
registerEvent.data.fd = listenFd;
registerEvent.events = EPOLLIN | EPOLLET;
epoll_ctl(eFd, EPOLL_CTL_ADD, listenFd, ®isterEvent);
while (1)
{
nFds = epoll_wait(eFd, resultEvents, MAXEVENTS, -1);
for (i=0; i<nFds; i++)
{
if (listenFd == resultEvents[i].data.fd)
{
while (1) // have notification on the listen socket, which means one or more incoming connections
{
clientFd = accept(listenFd, (sockaddr*)&clientAddr, &len);
if ((clientFd == -1) && ((errno == EAGAIN) || (errno == EWOULDBLOCK))) // processed all incoming connections
{
break;
}
printf("accepted connection from %s:%d\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
setNonBlock(clientFd);
registerEvent.data.fd = clientFd;
registerEvent.events = EPOLLIN | EPOLLET;
epoll_ctl(eFd, EPOLL_CTL_ADD, clientFd, ®isterEvent);
}
}
else
{
while (1) // have data on the fd waiting to be read, must read whatever data is available completely
{
nRead = read(resultEvents[i].data.fd, bufRead, sizeof(bufRead));
if ((nRead == -1) && (errno == EAGAIN)) // have read all data
{
break;
}
else if (nRead == 0) // the remote has closed the connection
{
printf("remote closed, close fd: %d\n", resultEvents[i].data.fd);
epoll_ctl(eFd, EPOLL_CTL_DEL, resultEvents[i].data.fd, NULL);
close(resultEvents[i].data.fd);
break;
}
else
{
printf("received data: %s\n", bufRead);
}
}
}
}
}
close(listenFd);
close(eFd);
return 0;
}