epoll的接口非常简单,一共就三个函数。
epoll用到的所有函数都是在头文件sys/epoll.h中声明:
int epoll_create(int size);
创建一个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;
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队列里。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的产生。
参数events用来从内核得到事件的集合,
maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
该函数返回需要处理的事件数目,如返回0表示已超时。
ET和LT
EPOLL事件有两种模型:
Edge Triggered (ET)
Level Triggered (LT)
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。
在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。
如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。
ET(edge-triggered) 是高速工作方式,只支持no-block socket。
在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。
另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,
读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,
那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取。
还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快),
由于是非阻塞的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并未清空,只是事件类型清空。这一步非常重要。
实例代码:
- #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);
- }
阅读(3254) | 评论(1) | 转发(0) |