epoll对文件描述符的操作有两种模式:LT(Level Trigger 电平触发)模式和ET(Edge Trigger 边沿触发)模式。
LT是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该使事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。
ET是高效的工作模式,对于采用ET模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。
ET模式在很大程序上降低了同一epoll事件被重复触发的次数,因此效率要比LT模式高。每个使用ET模式的文件描述符都应该是非阻塞的。如果文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件而一直处于阻塞状态。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #define MAX_EVENT_NUMBER 1024
15 #define BUFFER_SIZE 10
16
17 //将文件描述符设置成非阻塞的
18 int setnonblocking(int fd);
19 //将文件描述符fd上的EPOLLIN注册到epollfd指示的epoll内核事件表中
20 //参数enable_et指定是否对fd启用ET模式
21 void addfd(int epollfd, int fd, bool enable_et);
22 //LT模式工作流程
23 void lt(epoll_event* events, int number, int epollfd, int listenfd);
24 //ET模式工作流程
25 void et(epoll_event* events, int number, int epollfd, int listenfd);
26
27 int main(int argc, char* argv[])
28 {
29 if(argc <= 2)
30 {
31 printf("usage: %s ip_address port_number", basename(argv[0]));
32 return 1;
33 }
34 const char* ip = argv[1];
35 int port = atoi(argv[2]);
36 int ret = 0;
37 struct sockaddr_in address;
38 bzero(&address,sizeof(address));
39 address.sin_family = AF_INET;
40 inet_pton(AF_INET,ip,&address.sin_addr);
41 address.sin_port = htons(port);
42 int listenfd = socket(PF_INET,SOCK_STREAM,0);
43 assert(listenfd>=0);
44 ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
45 assert(ret!=-1);
46 ret = listen(listenfd,5);
47 assert(ret!=-1);
48
49 epoll_event events[MAX_EVENT_NUMBER];
50 int epollfd = epoll_create(5);
51 assert(epollfd!=-1);
52 addfd(epollfd, listenfd, true); //ET模式
53 while(1)
54 {
55 int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
56 if(ret < 0)
57 {
58 printf("epoll failure\n");
59 break;
60 }
61 lt(events,ret,epollfd,listenfd); //使用LT模式
62 //et(events,ret,epollfd,listenfd); //使用ET模式
63 }
64 close(listenfd);
65 return 0;
66 }
67
68 int setnonblocking(int fd)
69 {
70 int old_option = fcntl(fd, F_GETFL);
71 int new_option = old_option|O_NONBLOCK;
72 fcntl(fd,F_SETFL,new_option);
73 return old_option;
74 }
75 void addfd(int epollfd, int fd, bool enable_et)
76 {
77 epoll_event event;
78 event.data.fd = fd;
79 event.events = EPOLLIN;
80 if(enable_et)
81 {
82 event.events |= EPOLLET;
83 }
84 epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
85 setnonblocking(fd);
86 }
87 void lt(epoll_event* events, int number, int epollfd, int listenfd)
88 {
89 char buf[BUFFER_SIZE];
90 for(int i = 0; i < number; i++)
91 {
92 int sockfd = events[i].data.fd;
93 if(sockfd == listenfd)
94 {
95 struct sockaddr_in client_address;
96 socklen_t client_addrlength = sizeof(client_address);
97 int connfd = accept(listenfd, (struct sockaddr*)&client_address, &clie nt_addrlength);
98 addfd(epollfd, connfd, false);
99 }else if(events[i].events & EPOLLIN){
100 //只要socket读缓存中还有未读出的数据,这段代码就被触发
101 printf("event trigger once");
102 memset(buf, '\0', BUFFER_SIZE);
103 int ret = recv(sockfd,buf,BUFFER_SIZE-1,0);
104 if(ret<=0)
105 {
106 close(sockfd);
107 continue;
108 }
109 printf("get %d bytes of content: %s\n",ret,buf);
110
111 }else{
112 printf("something else happend\n");
113 }
114 }
115 }
116 void et(epoll_event* events, int number, int epollfd, int listenfd)
117 {
118 char buf[BUFFER_SIZE];
119 for(int i = 0; i < number; i++)
120 {
121 int sockfd = events[i].data.fd;
122 if(sockfd == listenfd)
123 {
124 struct sockaddr_in client_address;
125 socklen_t client_addrlength = sizeof(client_address);
126 int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client _addrlength);
127 addfd(epollfd,connfd,true);
128 }else if(events[i].events & EPOLLIN){
129 //这段代码不会被重复触发,所以我们循环读取数据,确保把socket读缓存中的
所有数据读出
130 printf("event trigger once\n");
131 while(1)
132 {
133 memset(buf,'\0', BUFFER_SIZE);
134 int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
135 if(ret<0)
136 {
137 //对于非阻塞IO,下面的条件成立表示数据已经全部读取完毕
,此后,epoll就再次触发sockfd上的EPOLLIN事件,以驱动下一次读操作
138 if((errno==EAGAIN)||(errno==EWOULDBLOCK))
139 {
140 printf("read later\n");
141 break;
142 }
143 close(sockfd);
144 break;
145 }else if(ret==0){
146 close(sockfd);
147 }else{
148 printf("get %d bytes of content: %s\n",ret,buf);
149 }
150 }
151 }else{
152 printf("something else happened\n");
153 }
154 }
155 }
EPOLLONESHOT事件:即使使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程(或进程,下同)在读取完某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读(EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个socket的局面。而我们期望的是一个socket连接在任一时刻都只被一个线程处理。这点可以使用epoll的EPOLLONESHOT事件实现。
对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。但反过来思考,注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #include
10 #include
11 #include
12 #include
13 #include
14 #define MAX_EVENT_NUMBER 1024
15 #define BUFFER_SIZE 1024
16 struct fds{
17 int epollfd;
18 int sockfd;
19 };
20 //将文件描述符设置成非阻塞的
21 int setnonblocking(int fd);
22 //将fd上的EPOLLIN和EPOLLET事件注册到epollfd指示的epoll内核事件表中
23 //参数oneshot指定是否注册fd上的EPOLLONESHOT事件
24 void addfd(int epollfd, int fd, bool oneshot);
25 //重置fd上的事件,操作系统会触发fd上的EPOLLIN事件且只触发一次
26 void reset_oneshot(int epollfd, int fd);
27 //工作线程
28 void* worker(void* arg);
29
30 int main(int argc, char* argv[])
31 {
32 if( argc<=2 )
33 {
34 printf("usage: %s ip_address port_number\n", basename(argv[0]));
35 return 1;
36 }
37 const char* ip = argv[1];
38 int port = atoi(argv[2]);
39 int ret = 0;
40 struct sockaddr_in address;
41 bzero(&address,sizeof(address));
42 address.sin_family = AF_INET;
43 inet_pton(AF_INET, ip, &address.sin_addr);
44 address.sin_port = htons(port);
45
46 int listenfd = socket(PF_INET, SOCK_STREAM, 0);
47 assert(listenfd>=0);
48 ret = bind(listenfd, (struct sockaddr*)&address, sizeof(address));
49 assert(ret!=-1);
50 ret = listen(listenfd,5);
51 assert(ret!=-1);
52
53 epoll_event events[MAX_EVENT_NUMBER];
54 int epollfd = epoll_create(5);
55 assert(ret!=-1);
56 //监听socket listenfd是不能注册EPOLLONESHOT事件,否则应用程序只能处理一个客户连接
57 //后续客户连接请求不再触发listenfd上的EPOLLIN事件
58 addfd(epollfd, listenfd, false);
59
60 while(1)
61 {
62 int ret = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
63 if( ret<0 )
64 {
65 printf("epoll failure\n");
66 break;
67 }
68 for(int i = 0; i < ret; i++)
69 {
70 int sockfd = events[i].data.fd;
71 if(sockfd==listenfd)
72 {
73 struct sockaddr_in client_address;
74 socklen_t client_addrlength = sizeof(client_address);
75 int connfd = accept(listenfd, (struct sockaddr*)&client_addres s,&client_addrlength);
76 //对每个非监听文件描述符都注册EPOLLONESHOT事件
77 addfd(epollfd, connfd, true);
78 }else if(events[i].events & EPOLLIN)
79 {
80 pthread_t thread;
81 fds fds_for_new_worker;
82 fds_for_new_worker.epollfd = epollfd;
83 fds_for_new_worker.sockfd = sockfd;
84 //启动一个工作线程为sockfd服务
85 pthread_create(&thread, NULL, worker, (void*)&fds_for_new_work er);
86 }else{
87 printf("something else happened\n");
88 }
89 }
90 }
91 close(listenfd);
92 return 0;
93 }
94
95 int setnonblocking(int fd)
96 {
97 int old_option = fcntl(fd,F_GETFL);
98 int new_option = old_option | O_NONBLOCK;
99 fcntl(fd,F_SETFL,new_option);
100 return old_option;
101 }
102 void addfd(int epollfd, int fd, bool oneshot)
103 {
104 epoll_event event;
105 event.data.fd = fd;
106 event.events = EPOLLIN|EPOLLET;
107 if(oneshot)
108 {
109 event.events |= EPOLLONESHOT;
110 }
111 epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
112 setnonblocking(fd);
113 }
114 void reset_oneshot(int epollfd, int fd)
115 {
116 epoll_event event;
117 event.data.fd = fd;
118 event.events = EPOLLIN|EPOLLET|EPOLLONESHOT;
119 epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
120 }
121 void* worker(void* arg)
122 {
123 int sockfd = ((fds*)arg)->sockfd;
124 int epollfd = ((fds*)arg)->epollfd;
125 printf("start new thread to receive data on fd: %d\n", sockfd);
126 char buf[BUFFER_SIZE];
127 memset(buf, '\0', BUFFER_SIZE);
128 while(1)
129 {
130 int ret = recv(sockfd, buf, BUFFER_SIZE-1, 0);
131 if(ret==0)
132 {
133 close(sockfd);
134 printf("foreiner closed the connection\n");
135 break;
136 }else if(ret<0){
137 if(errno==EAGAIN)
138 {
139 reset_oneshot(epollfd,sockfd);
140 printf("read later\n");
141 break;
142 }else{
143 printf("get content: %s\n",buf);
144 sleep(5);
145 }
146 }
147 }
148 printf("end thread receiving data on fd: %d\n",sockfd);
149 }
从工作线程函数worker来看,如果一个工作线程处理完某个socket上的一次请求(用休眠5s来模拟这个过程)之后,又接收到该socket上新的客户请求,则该线程将继续为这个socket服务。并且因为该socket上注册了EPOLLONESHOT事件,其他线程没有机会接触这个socket,如果工作线程处理5s后仍然没有收到该socket上的下一批客户数据,则它将放弃为该socket服务。同时,它调用reset_oneshot函数来重置该socket上的注册事件,这将使epoll有机会再次检测到该socket上的EPOLLIN事件,进而使得其他线程有机会为该socket服务。