針對epoll api的兩種觸發模式,lt和et,仿照一些例子寫了代碼進行實驗。api
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <pthread.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <fcntl.h> #define MAX_EVENT_NUMBER 1024 #define BUFFER_SIZE 10 int setnonblocking(int fd) { int old_option = fcntl(fd, F_GETFL); int new_option = old_option | O_NONBLOCK; fcntl(fd, F_SETFL, new_option); return old_option; } void addfd(int epollfd, int fd, bool enable_et) { epoll_event event; event.data.fd = fd; event.events = EPOLLIN; if (enable_et) { event.events |= EPOLLET; } epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); setnonblocking(fd); } void lt(epoll_event *events, int number, int epollfd, int listenfd) { char buf[BUFFER_SIZE]; for (int i=0; i<number; i++) { int sockfd = events[i].data.fd; if (sockfd == listenfd) { sockaddr_in client_address; socklen_t client_addrlen = sizeof(client_address); int connfd = accept(listenfd, (sockaddr*)&client_address, &client_addrlen); addfd(epollfd, connfd, false); } else if (events[i].events & EPOLLIN) { printf("lt event trigger once\n"); memset(buf, '\0', BUFFER_SIZE); int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0); if (ret <= 0) { close(sockfd); continue; } printf("get %d bytes of content: %s\n", ret, buf); } else { printf("something else happened\n"); } } } void et(epoll_event *events, int number, int epollfd, int listenfd) { char buf[BUFFER_SIZE]; for (int i=0; i<number; i++) { int sockfd = events[i].data.fd; if (sockfd == listenfd) { sockaddr_in client_address; socklen_t client_addrlen = sizeof(client_address); int connfd = accept(listenfd, (sockaddr*)&client_address, &client_addrlen); addfd(epollfd, connfd, true); } else if(events[i].events & EPOLLIN) { // Need to read complete printf("et event trigger once\n"); while (true) { memset(buf, '\0', BUFFER_SIZE); int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0); if (ret < 0) { // Below shows complete if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { printf("read later\n"); break; } printf("some error happens\n"); close(sockfd); break; } else if (ret == 0) { close(sockfd); break; } else { printf("get %d bytes of content: %s\n", ret, buf); } } } else { printf("something else happened\n"); } } } int main(int argc, char *argv[]) { if (argc <= 1) { printf("usage: %s port_number ip_address\n", basename(argv[0])); return 1; } int port = atoi(argv[1]); int ret = 0; sockaddr_in address; bzero(&address, sizeof(address)); address.sin_family = AF_INET; if (argc >= 3) { const char *ip =argv[2]; inet_pton(AF_INET, ip, &address.sin_addr); } else { address.sin_addr.s_addr = INADDR_ANY; } address.sin_port = htons(port); int listenfd = socket(PF_INET, SOCK_STREAM, 0); assert(listenfd >= 0); ret = bind(listenfd, (sockaddr*)&address, sizeof(address)); assert(ret != - 1); ret = listen(listenfd, 5); assert(ret != -1); epoll_event events[MAX_EVENT_NUMBER]; int epollfd = epoll_create(5); assert(epollfd != -1); addfd(epollfd, listenfd, true); while(true) { ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1); if (ret < 0) { printf("epoll failure\n"); break; } lt(events, ret, epollfd, listenfd); // lt //et(events, ret, epollfd, listenfd); // et } close(listenfd); return 0; }
Makefile文件:服務器
epoll_test : epoll_test.cpp
g++ -o epoll_test epoll_test.cpp -lpthread
以上程序有個問題,就是在端口被佔用時候,由於bind失敗,會assert失敗而後core dump. 在重複測試時候,能夠換個端口。app
首先,註釋掉et,使用lt:socket
lt(events, ret, epollfd, listenfd); // lt //et(events, ret, epollfd, listenfd); // et
運行 ./epoll_test 12888 並在另外一個窗口用telnet輸入超過10個(BUFFERSIZE)字符:函數
$telnet 127.0.0.1 12888 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. 12345678901234567890123456
1234567890123456789012345678
在服務器端獲得:測試
$./epoll_test 12888 lt event trigger once get 9 bytes of content: 123456789 lt event trigger once get 9 bytes of content: 012345678 lt event trigger once get 9 bytes of content: 90123456 lt event trigger once get 1 bytes of content: lt event trigger once get 9 bytes of content: 123456789 lt event trigger once get 9 bytes of content: 012345678 lt event trigger once get 9 bytes of content: 901234567 lt event trigger once get 3 bytes of content: 8
能夠看出由於buffersize的限制,服務器端進行了屢次讀取,event也觸發了屢次。spa
換成et模式:code
//lt(events, ret, epollfd, listenfd); // lt et(events, ret, epollfd, listenfd); // et
運行服務器後,telnet客戶端輸入:blog
$telnet 127.0.0.1 12889 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. 12345678901234567890123456 1234567890123456789012345678
服務器顯示:ip
$./epoll_test 12889 et event trigger once get 9 bytes of content: 123456789 get 9 bytes of content: 012345678 get 9 bytes of content: 90123456 get 1 bytes of content: read later et event trigger once get 9 bytes of content: 123456789 get 9 bytes of content: 012345678 get 9 bytes of content: 901234567 get 3 bytes of content: 8 read later
能夠看出,每次客戶端的字符串,只觸發了一次。
其實,上面的例子還不夠嚴謹,由於服務器一次已經把字符都讀完了。那麼若是沒讀完,會繼續出發嗎。
以下修改服務器代碼:
//修改 //while (true) { if (true) { memset(buf, '\0', BUFFER_SIZE); int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0); if (ret < 0) { // Below shows complete if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { printf("read later\n"); break; } printf("some error happens\n"); close(sockfd); //修改 //break; } else if (ret == 0) { close(sockfd); //修改 //break; } else { printf("get %d bytes of content: %s\n", ret, buf); } }
運行服務器以後,telnet輸入長字符串:
$telnet 127.0.0.1 12888 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. 12345678901234567890123456
服務器端只顯示了BUFFERSIZE長度的一行,沒有讀入的數據也沒有進行event觸發:
$./epoll_test 12888 et event trigger once get 9 bytes of content: 123456789
若是客戶端,再輸入一行:
$telnet 127.0.0.1 12888 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. 12345678901234567890123456 1234567890123456789012345678
服務器端也僅僅把以前沒讀入的上一次客戶端發來的數據中,再讀入BUFFERSIZE長度:
$./epoll_test 12888 et event trigger once get 9 bytes of content: 123456789 et event trigger once get 9 bytes of content: 012345678
另外,對上面的服務器端程序,增長了et模式下對recv函數的返回ret=0的打印:
else if (ret == 0) { printf("get 0 data\n"); close(sockfd); break; }
發如今et模式下,沒有走到ret==0的分支:
$telnet 127.0.0.1 12889 Trying 127.0.0.1... Connected to 127.0.0.1. Escape character is '^]'. 12345678901234567890123456
服務器端:
$./epoll_test 12889 et event trigger once get 9 bytes of content: 123456789 get 9 bytes of content: 012345678 get 9 bytes of content: 90123456 get 1 bytes of content: read later
走的是以下的判斷結束的分支:
if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { printf("read later\n"); break; }
以上。:)