概念:
Level-triggered :水平触发,缺省模式
edge-triggered :边缘触发
通知模式:
LT模式时,事件就绪时,假设对事件没做处理,内核会反复通知事件就绪
ET模式时,事件就绪时,假设对事件没做处理,内核不会反复通知事件就绪
事件通知的细节:
1.调用epoll_ctl,ADD或者MOD事件EPOLLIN
LT:如果此时缓存区没有可读数据,则epoll_wait不会返回EPOLLIN,如果此时缓冲区有可读数据,则epoll_wait会持续返回EPOLLIN
ET:如果此时缓存区没有可读数据,则epoll_wait不会返回EPOLLIN,如果此时缓冲区有可读数据,则epoll_wait会返回一次EPOLLIN
2.调用epoll_ctl,ADD或者MOD事件EPOLLOUT
LT:如果不调用epoll_ctl将EPOLLOUT修改为EPOLLIN,则epoll_wait会持续返回EPOLLOUT
ET:epoll_wait只会返回一次EPOLLOUT
针对TCP的测试详请,都是non-blocking:
1.listenfd设置为LT
struct epoll_event ev, events[MAX_EVENTS]; int epollfd = epoll_create(10); if (-1 == epollfd) { perror("epoll_create fail"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; // LT ev.data.fd = listenfd; if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) { perror("epoll_ctl: listenfd fail"); exit(EXIT_FAILURE); } for (;;) { int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
2.listenfd设置为ET
struct epoll_event ev, events[MAX_EVENTS]; int epollfd = epoll_create(10); if (-1 == epollfd) { perror("epoll_create fail"); exit(EXIT_FAILURE); } ev.events = EPOLLIN | EPOLLET; // ET ev.data.fd = listenfd; if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) { perror("epoll_ctl: listenfd fail"); exit(EXIT_FAILURE); } for (;;) { int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
3.connectfd设置为LT
if (events[n].data.fd == listenfd) { int connfd = accept(listenfd, NULL, NULL); if (-1 == connfd) { perror("accept fail"); continue; } setnonblocking(connfd); struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = connfd; if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev)) { perror("epoll_ctl: sock"); continue; }
1.客户端发送了24个字节
2.服务器一次只读取8个字节
3.内核会连续通知,epoll_wait会持续返回,直到缓冲区的数据被读取完毕或者说socket不在处于readable/writable状态
4.connectfd设置为ET
if (events[n].data.fd == listenfd) { int connfd = accept(listenfd, NULL, NULL); if (-1 == connfd) { perror("accept fail"); continue; } setnonblocking(connfd); struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; ev.data.fd = connfd; if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev)) { perror("epoll_ctl: sock"); continue; }
表现:
1.客户端发送了24个字节
2.服务器一次只读取8个字节
3.内核只会通知一次,剩下的数据会留在缓冲区中
在ET模式下何时会再次通知可读写事件,也是epoll_wait有返回?
只有socket从unreadable/unwritable变为readable/writable状态:
4.当客户端再次发送数据时,则内核会继续通知可读事件
5.当服务器在每次读取完数据,显式的调用epoll_ctl来注册可读事件,如果缓冲区有可读数据则内核会继续通知可读事件
下面提供简单的测试源码,需要微调才能测试完以上几种情况:
------------------------------------------------------------------------------------------------------------------------------------------------
客户端代码:client.cpp
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> #include <string.h> #define MAX_EVENTS 10 int main() { // socket struct sockaddr_in servaddr; short port = 9527; int sockfd = socket(AF_INET, SOCK_STREAM, 0); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); servaddr.sin_port = htons(port); if (connect(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in)) < 0) { perror("connect fail"); exit(EXIT_FAILURE); } const char* buf = "daiyudong"; for (;;) { int len = (int)write(sockfd, buf, strlen(buf)); if (len > 0) { printf("write len=%d\n", len); } sleep(1); } }
服务器代码:server.cpp
#include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <stdlib.h> #include <unistd.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> #define MAX_EVENTS 10 static void setnonblocking(int fd) { int flag = fcntl(fd, F_GETFL, 0); if (flag < 0) { perror("fcntl F_GETFL:"); return; } if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) { perror("fcntl F_SETFL:"); } } static int epoll_add(int efd, int sock) { struct epoll_event ev; ev.events = EPOLLIN; ev.data.fd = sock; if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, sock, &ev)) { perror("epoll_ctl: sock"); return 1; } return 0; } static void epoll_write(int efd, int sock, bool enable) { struct epoll_event ev; ev.events = EPOLLIN | (enable ? EPOLLOUT : 0); ev.data.fd = sock; epoll_ctl(efd, EPOLL_CTL_MOD, sock, &ev); } static void epoll_del(int efd, int sock) { epoll_ctl(efd, EPOLL_CTL_DEL, sock , NULL); } int main() { // socket int listenfd; struct sockaddr_in servaddr; short port = 9527; servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(port); listenfd = socket(AF_INET, SOCK_STREAM, 0); setnonblocking(listenfd); int res = bind(listenfd, (sockaddr *)&servaddr, sizeof(sockaddr_in)); if (0 == res) printf("server bind success, 0.0.0.0:%d\n", port); else { perror("bind fail"); exit(EXIT_FAILURE); } res = listen(listenfd, 100); if (0 == res) printf("server listen success\n"); else { perror("listen fail"); exit(EXIT_FAILURE); } // epoll struct epoll_event ev, events[MAX_EVENTS]; int epollfd = epoll_create(10); if (-1 == epollfd) { perror("epoll_create fail"); exit(EXIT_FAILURE); } ev.events = EPOLLIN; // LT ev.data.fd = listenfd; if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) { perror("epoll_ctl: listenfd fail"); exit(EXIT_FAILURE); } for (;;) { int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (-1 == nfds) { perror("epoll_wait fail"); exit(EXIT_FAILURE); } for (int n = 0; n < nfds; ++n) { if (events[n].data.fd == listenfd) { int connfd = accept(listenfd, NULL, NULL); if (-1 == connfd) { perror("accept fail"); continue; } setnonblocking(connfd); epoll_add(epollfd, connfd); printf("connfd:%d\n", connfd); } else if (events[n].events & EPOLLIN) { char buf[2] = {0}; int fd = events[n].data.fd; size_t count = 1; int len = (int)read(fd, buf, count); if (len > 0) { printf("read len=%d buf=%s\n", len, buf); //epoll_write(epollfd, fd, false); } else if (len < 0) { switch(errno) { case EINTR: case EAGAIN: printf("try again\n"); //epoll_write(epollfd, fd, false); break; default: epoll_del(epollfd, fd); close(fd); printf("game over\n"); } } else if (len == 0) { epoll_del(epollfd, fd); close(fd); printf("game over\n"); } } else { // pass } } } }
小结:
针对listenfd,默认使用LT模式,ET模式并无实际意义,也无收益
针对connectfd,使用ET模式则需要注意到与LT模式的读写逻辑不同,比如读取数据,则需要在一个事件内读取完毕
参考man 7 epoll
http://linux.die.net/man/7/epoll