1、基本知識
epoll是在2.6內核中提出的,是之前的select和poll的增強版本。相對于select和poll來說,epoll更加靈活,沒有描述符限制。epoll使用一個文件描述符管理多個描述符,將用戶關系的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。
2、epoll接口
epoll操作過程需要三個接口,分別如下:
1
2
3
4
|
#include < sys /epoll.h> int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); |
(1) int epoll_create(int size);
創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epoll的事件注冊函數,它不同與select()是在監聽事件時告訴內核要監聽什么類型的事件epoll的事件注冊函數,它不同與select()是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。第一個參數是epoll_create()的返回值,第二個參數表示動作,用三個宏來表示:
epoll_ctl_add:注冊新的fd到epfd中;
epoll_ctl_mod:修改已經注冊的fd的監聽事件;
epoll_ctl_del:從epfd中刪除一個fd;
第三個參數是需要監聽的fd,第四個參數是告訴內核需要監聽什么事,struct epoll_event結構如下:
1
2
3
4
|
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隊列里
(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
等待事件的產生,類似于select()調用。參數events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size,參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。
3、工作模式
epoll對文件描述符的操作有兩種模式:lt(level trigger)和et(edge trigger)。lt模式是默認模式,lt模式與et模式的區別如下:
lt模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序可以不立即處理該事件。下次調用epoll_wait時,會再次響應應用程序并通知此事件。
et模式:當epoll_wait檢測到描述符事件發生并將此事件通知應用程序,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次響應應用程序并通知此事件。
et模式在很大程度上減少了epoll事件被重復觸發的次數,因此效率要比lt模式高。epoll工作在et模式的時候,必須使用非阻塞套接口,以避免由于一個文件句柄的阻塞讀/阻塞寫操作把處理多個文件描述符的任務餓死。
4、測試程序
編寫一個服務器回射程序echo,練習epoll過程。
服務器代碼如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
|
#include < stdio.h > #include < stdlib.h > #include < string.h > #include < errno.h > #include < netinet /in.h> #include < sys /socket.h> #include < arpa /inet.h> #include < sys /epoll.h> #include < unistd.h > #include < sys /types.h> #define ipaddress "127.0.0.1" #define port 8787 #define maxsize 1024 #define listenq 5 #define fdsize 1000 #define epollevents 100 //函數聲明 //創建套接字并進行綁定 static int socket_bind(const char* ip,int port); //io多路復用epoll static void do_epoll(int listenfd); //事件處理函數 static void handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf); //處理接收到的連接 static void handle_accpet(int epollfd,int listenfd); //讀處理 static void do_read(int epollfd,int fd,char *buf); //寫處理 static void do_write(int epollfd,int fd,char *buf); //添加事件 static void add_event(int epollfd,int fd,int state); //修改事件 static void modify_event(int epollfd,int fd,int state); //刪除事件 static void delete_event(int epollfd,int fd,int state); int main(int argc,char *argv[]) { int listenfd; listenfd = socket_bind(ipaddress,port); listen(listenfd,listenq); do_epoll(listenfd); return 0; } static int socket_bind(const char* ip,int port) { int listenfd; struct sockaddr_in servaddr; listenfd = socket(af_inet,sock_stream,0); if (listenfd == -1) { perror("socket error:"); exit(1); } bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = af_inet; inet_pton(af_inet,ip,&servaddr.sin_addr); servaddr.sin_port = htons(port); if (bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr)) == -1) { perror("bind error: "); exit(1); } return listenfd; } static void do_epoll(int listenfd) { int epollfd; struct epoll_event events[epollevents]; int ret; char buf[maxsize]; memset(buf,0,maxsize); //創建一個描述符 epollfd = epoll_create(fdsize); //添加監聽描述符事件 add_event(epollfd,listenfd,epollin); for ( ; ; ) { //獲取已經準備好的描述符事件 ret = epoll_wait(epollfd,events,epollevents,-1); handle_events(epollfd,events,ret,listenfd,buf); } close(epollfd); } static void handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf) { int i; int fd; //進行選好遍歷 for (i = 0;i < num;i++) { fd = events[i].data.fd; //根據描述符的類型和事件類型進行處理 if ((fd == listenfd) &&(events[i].events & epollin)) handle_accpet(epollfd,listenfd); else if (events[i].events & epollin) do_read(epollfd,fd,buf); else if (events[i].events & epollout) do_write(epollfd,fd,buf); } } static void handle_accpet(int epollfd,int listenfd) { int clifd; struct sockaddr_in cliaddr; socklen_t cliaddrlen; clifd = accept(listenfd,(struct sockaddr*)&cliaddr,&cliaddrlen); if (clifd == -1) perror("accpet error:"); else { printf("accept a new client: %s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port); //添加一個客戶描述符和事件 add_event(epollfd,clifd,epollin); } } static void do_read(int epollfd,int fd,char *buf) { int nread; nread = read(fd,buf,maxsize); if (nread == -1) { perror("read error:"); close(fd); delete_event(epollfd,fd,epollin); } else if (nread == 0) { fprintf(stderr,"client close.\n"); close(fd); delete_event(epollfd,fd,epollin); } else { printf("read message is : %s",buf); //修改描述符對應的事件,由讀改為寫 modify_event(epollfd,fd,epollout); } } static void do_write(int epollfd,int fd,char *buf) { int nwrite; nwrite = write(fd,buf,strlen(buf)); if (nwrite == -1) { perror("write error:"); close(fd); delete_event(epollfd,fd,epollout); } else modify_event(epollfd,fd,epollin); memset(buf,0,maxsize); } static void add_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,epoll_ctl_add,fd,&ev); } static void delete_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,epoll_ctl_del,fd,&ev); } static void modify_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,epoll_ctl_mod,fd,&ev); } |
客戶端也用epoll實現,控制stdin_fileno、stdout_fileno、和sockfd三個描述符,程序如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
|
#include < netinet /in.h> #include < sys /socket.h> #include < stdio.h > #include < string.h > #include < stdlib.h > #include < sys /epoll.h> #include < time.h > #include < unistd.h > #include < sys /types.h> #include < arpa /inet.h> #define maxsize 1024 #define ipaddress "127.0.0.1" #define serv_port 8787 #define fdsize 1024 #define epollevents 20 static void handle_connection(int sockfd); static void handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf); static void do_read(int epollfd,int fd,int sockfd,char *buf); static void do_read(int epollfd,int fd,int sockfd,char *buf); static void do_write(int epollfd,int fd,int sockfd,char *buf); static void add_event(int epollfd,int fd,int state); static void delete_event(int epollfd,int fd,int state); static void modify_event(int epollfd,int fd,int state); int main(int argc,char *argv[]) { int sockfd; struct sockaddr_in servaddr; sockfd = socket(af_inet,sock_stream,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family = af_inet; servaddr.sin_port = htons(serv_port); inet_pton(af_inet,ipaddress,&servaddr.sin_addr); connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr)); //處理連接 handle_connection(sockfd); close(sockfd); return 0; } static void handle_connection(int sockfd) { int epollfd; struct epoll_event events[epollevents]; char buf[maxsize]; int ret; epollfd = epoll_create(fdsize); add_event(epollfd,stdin_fileno,epollin); for ( ; ; ) { ret = epoll_wait(epollfd,events,epollevents,-1); handle_events(epollfd,events,ret,sockfd,buf); } close(epollfd); } static void handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf) { int fd; int i; for (i = 0;i < num;i++) { fd = events[i].data.fd; if (events[i].events & epollin) do_read(epollfd,fd,sockfd,buf); else if (events[i].events & epollout) do_write(epollfd,fd,sockfd,buf); } } static void do_read(int epollfd,int fd,int sockfd,char *buf) { int nread; nread = read(fd,buf,maxsize); if (nread == -1) { perror("read error:"); close(fd); } else if (nread == 0) { fprintf(stderr,"server close.\n"); close(fd); } else { if (fd == stdin_fileno) add_event(epollfd,sockfd,epollout); else { delete_event(epollfd,sockfd,epollin); add_event(epollfd,stdout_fileno,epollout); } } } static void do_write(int epollfd,int fd,int sockfd,char *buf) { int nwrite; nwrite = write(fd,buf,strlen(buf)); if (nwrite == -1) { perror("write error:"); close(fd); } else { if (fd == stdout_fileno) delete_event(epollfd,fd,epollout); else modify_event(epollfd,fd,epollin); } memset(buf,0,maxsize); } static void add_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,epoll_ctl_add,fd,&ev); } static void delete_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,epoll_ctl_del,fd,&ev); } static void modify_event(int epollfd,int fd,int state) { struct epoll_event ev; ev.events = state; ev.data.fd = fd; epoll_ctl(epollfd,epoll_ctl_mod,fd,&ev); } |
5、測試結果
以上就是小編為大家帶來的io多路復用之epoll全面總結(必看篇)全部內容了,希望大家多多支持服務器之家~