epoll的接口非常简单,一共就三个函数。
epoll用到的所有函数都是在头文件sys/epoll.h中声明:
epoll用到的所有函数都是在头文件sys/epoll.h中声明:
int epoll_create(int size);
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,
在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,
在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注册函数,在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值,
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,
struct epoll_event结构如下:
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
epoll的事件注册函数,在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值,
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,
struct epoll_event结构如下:
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
{
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生。
参数events用来从内核得到事件的集合,
maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。
等待事件的产生。
参数events用来从内核得到事件的集合,
maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。
ET和LT
EPOLL事件有两种模型:
Edge Triggered (ET)
Level Triggered (LT)
Edge Triggered (ET)
Level Triggered (LT)
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。
在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。
如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。
如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET(edge-triggered) 是高速工作方式,只支持no-block socket。
在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。
在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。
另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,
读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,
那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取。
读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,
那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取。
还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),
由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,
这样不断的读和发,当缓冲区满后会产生EAGAIN错误,同时,不理会这次请求发送的数据。
所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回- 1表示出错。
在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试。
这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.
由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,
这样不断的读和发,当缓冲区满后会产生EAGAIN错误,同时,不理会这次请求发送的数据。
所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回- 1表示出错。
在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试。
这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.
epoll_wait运行的原理是
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,
则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。
这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
并且将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,
则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)来重新设置socket fd的事件类型。
这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。
实例代码:
-
#include <stdio.h>
-
#include <string.h>
-
#include <fcntl.h>
-
#include <sys/epoll.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
-
#define MAX_SOCKET 10000
-
-
void
-
add_event(int epfd, int fd, struct
epoll_event *event)
-
{
-
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, event);
-
}
-
-
void
-
mod_event(int epfd, int fd, struct
epoll_event *event)
-
{
-
epoll_ctl(epfd, EPOLL_CTL_MOD, fd, event);
-
}
-
-
void
-
del_event(int epfd, int fd, struct
epoll_event *event)
-
{
-
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, event);
-
}
-
-
int
-
init_listen(int epfd, int port)
-
{
-
-
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
-
fcntl(listenfd, F_SETFL, O_NONBLOCK);
-
-
struct epoll_event event;
-
event.data.fd = listenfd;
-
event.events = EPOLLIN | EPOLLET;
-
add_event(epfd, listenfd, &event);
-
-
struct sockaddr_in serveraddr;
-
memset(&serveraddr, 0, sizeof(struct
sockaddr_in));
-
serveraddr.sin_family = AF_INET;
-
inet_aton("127.0.0.1", &(serveraddr.sin_addr));
-
serveraddr.sin_port = htons(port);
-
bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(struct
sockaddr_in));
-
listen(listenfd, 20);
-
return listenfd;
-
}
-
-
void
-
accept_conn(int epfd, int listenfd)
-
{
-
socklen_t length;
-
struct sockaddr_in clientaddr;
-
memset(&clientaddr, 0, sizeof(struct
sockaddr_in));
-
int fd = accept(listenfd, (struct
sockaddr *)&clientaddr, &length);
-
fcntl(fd, F_SETFL, O_NONBLOCK);
-
-
char *clientip = inet_ntoa(clientaddr.sin_addr);
-
unsigned short clientport = ntohs(clientaddr.sin_port);
-
printf("client connected: %s:%d\n", clientip, clientport);
-
-
struct epoll_event event;
-
event.data.fd = fd;
-
event.events = EPOLLIN | EPOLLET;
-
add_event(epfd, fd, &event);
-
}
-
-
void
-
recv_data(int epfd, int fd)
-
{
-
char buffer[1024];
-
memset(buffer, 0, sizeof(buffer));
-
-
ssize_t count = read(fd, buffer, sizeof(buffer));
-
if (count <= 0) {
-
close(fd);
-
return;
-
}
-
-
printf("fd %d recv: %s\n", fd, buffer);
-
-
struct epoll_event event;
-
event.data.fd = fd;
-
event.events = EPOLLOUT | EPOLLET;
-
mod_event(epfd, fd, &event);
-
}
-
-
void
-
send_data(int epfd, int fd)
-
{
-
char buffer[1024];
-
memset(buffer, 0, sizeof(buffer));
-
sprintf(buffer, "hello fd %d", fd);
-
ssize_t count = write(fd, buffer, strlen(buffer));
-
if (count <= 0) {
-
close(fd);
-
return;
-
}
-
-
printf("fd %d send: %s\n", fd, buffer);
-
-
struct epoll_event event;
-
event.data.fd = fd;
-
event.events = EPOLLIN | EPOLLET;
-
mod_event(epfd, fd, &event);
-
}
-
-
int
-
main()
-
{
-
int port = 12345;
-
int epfd = epoll_create(MAX_SOCKET);
-
int listenfd = init_listen(epfd, port);
-
printf("fd %d listen: %d\n", listenfd, port);
-
-
while (1) {
-
printf("epoll_wait...\n");
-
struct epoll_event events[20];
-
int fds = epoll_wait(epfd, events, 20, 5000);
-
printf("epoll_wait fds: %d\n", fds);
-
-
int i;
-
for (i = 0; i < fds; i++) {
-
int fd = events[i].data.fd;
-
if (fd == listenfd) {
-
printf("accept_conn...\n");
-
accept_conn(epfd, listenfd);
-
continue;
-
}
-
if (events[i].events & EPOLLIN) {
-
printf("recv_data fd: %d\n", fd);
-
recv_data(epfd, fd);
-
}
-
if (events[i].events & EPOLLOUT) {
-
printf("send_data fd: %d\n", fd);
-
send_data(epfd, fd);
-
}
-
}
-
}
-
-
close(epfd);
- }