epoll把用户关心对文件描述符上的事件放在内核里的一个事件表中,使用一个文件描述符(epoll_create的返回值)来唯一标识内核中的这个事件表。
1.int epoll_create(int size)
创建事件表。
size:
事件表大小
the size argument is ignored
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
op:
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL
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;
注:
epoll_data_t是一个联合体,其中fd是使用最多的。不能同时使用其ptr成员和fd成员,如果要将文件描述符和用户数据关联起来,放弃使用epoll_data_t的fd成员,在ptr指向的用户数据中包含fd。
3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
如果检测到事件,就将所有就绪的事件从内核事件表中复制到第二个参数events指向的数组中。
4. LT、ET模式
- LT模式:(level trigger,水平触发)
采用LT模式的文件描述符,当epoll_wait检测到有事件发生并将此事件通知给应用程序后,应用程序可以不立即处理改事件。
当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通知此事件,直到该事件被处理。
- ET模式:(edge trigger,边沿触发)
采用ET模式的文件描述符,当epoll_wait检测到有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件。
ET模式在很大程度上降低了同一个epoll事件被重复触发的次数。
注:使用ET模式的文件描述符应该是非阻塞的。
因为在ET模式下,触发事件必须立即处理,若文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件一直处于阻塞状态。
LT:高电平触发
EPOLLIN事件产生:
- 内核中socket接收缓冲区不为空
EPOLLOUT事件产生:
- 内核中socket发送缓冲区不满
ET:高电平->低电平触发、低电平->高电平触发
EPOLLIN事件产生:
- 内核中socket接收缓冲区从空到不空
- 内核中socket接收缓冲区从不空到空
EPOLLOUT事件产生:
- 内核中socket发送缓冲区从不满到满
- 内核中socket发送缓冲区从满到不满
5. 简单例子
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd, flag;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
///////////////////////////////////////////////////////////////////////
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET 边沿触发,默认是水平触发 */
//event.events = EPOLLIN;
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
flag = fcntl(connfd, F_GETFL); /* 修改connfd为非阻塞读 */
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); //将connfd加入
while (1) {
printf("epoll_wait begin\n");
res = epoll_wait(efd, resevent, 10, -1); //最多10个, 阻塞监听
printf("epoll_wait end res %d\n", res);
if (resevent[0].data.fd == connfd) {
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) //非阻塞读, 轮询
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}