ET模式下,需要循环从缓存中读取,直到返回EAGAIN没有数据可读后,一个被通知的事件才算结束。如果还读取过程中,同一个连接又有新的事件到来,触发其他线程处理同一个socket,就乱了。EPOLL_ONESHOT就是用来避免这种情况发生的。将事件设置为EPOLL_ONESHOT后,每次事件触发后就会将这个FD从rbtree中删除,就不会再监听该事件了。
下面是我模仿别人写的一段代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <string.h> 6 #include <fcntl.h> 7 #include <libgen.h> 8 #include <assert.h> 9 #include <stdbool.h> 10 11 #include <sys/types.h> 12 #include <sys/socket.h> 13 #include <netinet/in.h> 14 #include <arpa/inet.h> 15 #include <sys/epoll.h> 16 #include <pthread.h> 17 18 #define MAX_EVENT_NUMBER 1024 19 #define BUFFER_SIZE 1024 20 21 #define ONE_SHOT true 22 #define NOT_ONE_SHOT false 23 24 struct data { 25 int epollfd; 26 int sockfd; 27 }; 28 29 void setnonblockling(int fd) 30 { 31 int fd_option = fcntl(fd, F_GETFL); 32 fd_option |= O_NONBLOCK; 33 fcntl(fd, F_SETFL, fd_option); 34 } 35 36 void add_fd(int epollfd, int fd, bool flag) 37 { 38 struct epoll_event event; 39 event.data.fd = fd; 40 event.events = EPOLLIN | EPOLLET; //监听可读事件,ET模式 41 if (flag) 42 event.events |= EPOLLONESHOT; 43 44 epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); 45 setnonblockling(fd); 46 } 47 48 void reset_oneshot(int epollfd, int fd) 49 { 50 struct epoll_event event; 51 event.data.fd = fd; 52 event.events = EPOLLIN | EPOLLET | EPOLLONESHOT; 53 epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); 54 } 55 56 void *threadMain(void *instance) 57 { 58 struct data *data = (struct data *)instance; 59 int epollfd = data->epollfd; 60 int sockfd = data->sockfd; 61 pthread_t pid = pthread_self(); //pid or tid, confused 62 63 printf("start new thread %u to recv data on fd: %d ", pid, sockfd); 64 char buf[BUFFER_SIZE]; 65 memset(buf, 0, sizeof(buf)); 66 67 for(;;) { 68 int n = recv(sockfd, buf, BUFFER_SIZE-1, 0); 69 if (n == 0) { 70 close(sockfd); 71 printf("foreiner closed the connection "); 72 break; 73 } 74 else if (n < 0) { 75 if (errno == EAGAIN) { 76 reset_oneshot(epollfd, sockfd); 77 printf("EAGAIN. Read later "); 78 break; 79 } 80 } 81 else { 82 buf[n] = '