一.概述:
epoll是多路复用的一种,但它比select和poll更加高效。具体体现在以下几个方面:
(1).select能打开的文件描述符是有一定限制的,默认情况下是2048,这对应那些大型服务器来说h是不足的。但 epoll则没有这个限制,它所支持的fd上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
(2).因为文件描述符是内核管理的,所以每次调用select或poll,都需要把fd集合从用户态拷贝到内核态,并且每次检查文件描述符的状态时,都要在内核遍历所有文件描述符,这个开销在fd很多时会很大。而epoll采用了nmap(内存映射)(和共享内存一样),内核和用户空间共用一行份fd集。
(3).另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
其它优化:
(4).epoll会给所有要关注的文件描述符建立一个红黑树,这样在查找某一个文件描述符时效率会有所提升。
(5).epoll会给准备好的文件描述符建立一个链表,这样查找一个已准备好的文件描述符时就不用在以前所有要关注的fd集中查找了。
二.epoll用法篇:
epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel2.5.44),它几乎具备了之前所说select和poll的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll有epoll_create,epoll_ctl,epoll_wait 3个系统调用。
(1).epoll_create:
|
1
|
int
epoll_create(
int
size);
|
创建一个epoll的句柄(后面会根据这个句柄创建红黑树)。自从linux2.6.8之后,size参数是被忽略的(也就是说,可以为任意值)。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
返回值:成功返回一个epoll句柄,失败返回-1;
(2).epoll_ctl
|
1
|
int
epoll_ctl(
int
epfd,
int
op,
int
fd,
struct
epoll_event *event);
|
函数描述:epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。(一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。)
返回值:成功返回0,失败返回-1;
epfd参数:epoll_create创建的一个epoll句柄。
op参数:表示要执行的动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
fd参数:需要监听的文件描述符。
event参数:告诉内核需要监听什么事。struct epoll_event结构如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
|
The event argument describes the object linked to the file descriptor fd. The
struct
epoll_event is defined as :
typedef
union
epoll_data {
void
*ptr;
int
fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
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)来说的。(epoll默认为水平触发)
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
(3).epoll_wait:
|
1
|
int
epoll_wait(
int
epfd,
struct
epoll_event * events,
int
maxevents,
int
timeout);
|
函数功能:监听在epoll监控的事件中已经发送的事件。
返回值:成功返回监听文件描述符集中已经准备好的文件描述符,返回0代表timeout,失败返回-1。
epoll参数:epoll_create创建的epoll句柄。
events参数:输出型参数,保存监听文件描述符集中已经准备好的文件描述符集。
maxevents参数:events数组的大小。
timeout参数:超时时间。单位为毫秒。
三.LT模式下的阻塞模式。
相关代码:
server.c:
|
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
|
1
/****************************************
2 > File Name:epoll_server.c
3 > Author:xiaoxiaohui
4 > mail:1924224891@qq.com
5 > Created Time:2016年05月28日 星期六 15时38分17秒
6 ****************************************/
7
8 #include<stdio.h>
9 #include<stdlib.h>
10 #include<sys/types.h>
11 #include<sys/socket.h>
12 #include<arpa/inet.h>
13 #include<netinet/in.h>
14 #include<string.h>
15 #include<unistd.h>
16 #include<sys/epoll.h>
17
18 #define LEN 1024地
19
const
char
* IP =
"127.0.0.1"
;
20
const
int
PORT = 8080;
21
const
int
BACKLOG = 5;
22
int
timeout = 5000;
23
const
int
MAXEVENTS = 64;
24
struct
sockaddr_in local;
25
struct
sockaddr_in client;
26
int
SIZE_CLIENT =
sizeof
(client);
27
28
typedef
struct
data_buf
//用于存储epoll_event中的data中的不同元素
29 {
30
int
fd;
31
char
buf[LEN];
32 }data_buf_t, *data_buf_p;地
33
34
35
int
ListenSock()
36 {
37
int
listenSock = socket(AF_INET, SOCK_STREAM, 0);
38
if
(listenSock < 0)
39 {
40
perror
(
"socket"
);
41
exit
(1);
42 }
43
44 local.sin_family = AF_INET;
45 local.sin_port = htons(PORT);
46 local.sin_addr.s_addr = inet_addr(IP);
47
if
( bind(listenSock, (
struct
sockaddr*)&local,
sizeof
(local)) < 0)
48 {
49
perror
(
"bind"
);
50
exit
(2);
51 }
52
53
if
( listen(listenSock, BACKLOG) < 0)
54 {
55
perror
(
"listen"
);
56
exit
(3);
57 }
58
59
return
listenSock;
60 }
61
62
static
int
epoll_fd(
int
listenSock)
63 {
64
65
int
epoll_fd = epoll_create(256);
//size随便选一个值
66
67
struct
epoll_event ev;
//把listenSock设置进epoll_fd中
68 ev.events = EPOLLIN;
69 ev.data.fd = listenSock;
70 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenSock, &ev);
//系统会维护一个红黑树
71
72
struct
epoll_event ev_outs[MAXEVENTS];
//准备好的队列
73
int
max = MAXEVENTS;
74
75
while
(1)
76 {
77
int
num = -1;
78
switch
( num = epoll_wait(epoll_fd, ev_outs, max, timeout))
79 {
80
case
0:
//timeout
81
printf
(
"timeout.....\n"
);
82
break
;
83
case
-1:
//error
84
perror
(
"epoll_wait"
);
85
break
;
86
default
:
87
for
(
int
index = 0; index < num; index++)
88 {
89
if
(ev_outs[index].data.fd == listenSock && (ev_outs[index].events & EPOLLIN))
//监听套接字准备就绪
90 {
91
printf
(
"accept is ready\n"
);
92
int
linkSock = accept(listenSock, (s地truct sockaddr*)&client, &SIZE_CLIENT);
93
if
(linkSock < 0)
94 {
95
perror
(
"accept"
);
96
continue
;
//这次可能是一个新客户端的请求,所以后面可能还有文件描述符准备就绪了
97 }地
98
99 ev.events = EPOLLIN;
//把新套接字放到红黑树中
100 ev.data.fd = linkSock;
101 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, linkSock, &ev);
102 }
103
else
//已链接套接字准备就绪
104 {
105
if
(ev_outs[index].events & EPOLLIN)
//读事件准备就绪
106 {
107 data_buf_p mem = (data_buf_p)
malloc
(
sizeof
(data_buf_t));
108
memset
(mem->buf,
'\0'
,
sizeof
(mem->buf));
109 mem->fd = ev_outs[index].data.fd;
110
111
int
ret = read(mem->fd, mem->buf,
sizeof
(mem->buf));
112
if
(ret > 0)
113 {
114 mem->buf[ret] =
'\0'
;
115
printf
(
"client# %s\n"
, mem->buf);
116
117 ev.data.ptr = mem;
//mem中即保持了fd,又保持了buf数据
118 ev.events = EPOLLOUT;
//读事件已经完成,现在要关心写事件
119 epoll_ctl(epoll_fd, EPOLL_CTL_MOD, mem->fd, &ev);
120 }
121
else
if
(ret == 0 )
//客户端已关闭
122 {
123
printf
(
"client is closed\n"
);
124 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, ev_outs[index].data.fd, NULL);
//把该文件描述符从红黑树中移除
125 close(ev_outs[index].data.fd);
126
free
(mem);
127 }
128
else
129 {
130
perror
(
"read"
);
131
continue
;
132 }
133 }
134
else
if
(ev_outs[index].events & EPOLLOUT)
//写事件准备就绪
135 {
136 data_buf_p mem = (data_buf_p)ev_outs[index].data.ptr;
137
int
fd = mem->fd;
138
char
* buf = mem->buf;
139
140
if
( write(fd, buf,
strlen
(buf)) < 0)
141 {
142
perror
(
"write"
);
143
continue
;
144 }
145
146 ev.events = EPOLLIN;
//这个文件描述符的写事件已完成,下次关心读事件
147 ev.data.fd = mem->fd;
148 epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev);
149 }
150
else
//DoNothing
151 {}
152 }
153 }
154
break
;
155 }
156 }
157 }
158
159
int
main()
160 {
161
int
listen地Sock = ListenSock();
162 epoll_fd(listenSock);
163 close(listenSock);
164
return
0;
165 }
|
client.c:
|
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
|
1
/****************************************
2 > File Name:client.c
3 > Author:xiaoxiaohui
4 > mail:1924224891@qq.com
5 > Created Time:2016年05月23日 星期一 12时30分01秒
6 ****************************************/
7
8 #include<stdio.h>
9 #include<stdlib.h>
10 #include<string.h>
11 #include<sys/types.h>
12 #include<sys/socket.h>
13 #include<netinet/in.h>
14 #include<arpa/inet.h>
15 #include<sys/
time
.h>
16 #include<unistd.h>
17
18 #define LEN 1024
19
const
int
PORT = 8080;
20
const
char
* IP =
"127.0.0.1"
;
21
struct
sockaddr_in server;
22
int
clientSock;
23
char
buf[LEN];
24
25
int
main()
26 {
27 clientSock = socket(AF_INET, SOCK_STREAM, 0);
28
if
(clientSock < 0)
29 {
30
perror
(
"socket"
);
31
exit
(1);
32 }
33
34 server.sin_family = AF_INET;
35 server.sin_addr.s_addr = inet_addr(IP);
36 server.sin_port = htons(PORT);
37
38
if
( connect(clientSock, (
struct
sockaddr*)&server,
sizeof
(server)) < 0)
39 {
40
perror
(
"connect"
);
41
exit
(2);
42 }
43
44
while
(1)
45 {
46
memset
(buf,
'\0'
, LEN);
47
printf
(
"please input: "
);
48
gets
(buf);
49 write(clientSock, buf,
strlen
(buf));
50
51
memset
(buf,
'地\0'
, LEN);
52
int
ret = read(clientSock, buf, LEN);
53 buf[ret] =
'\0'
;
54
printf
(
"echo: %s\n"
, buf);
55 }
56
57
return
0;
58 }
|
执行结果:
四.ET模式下的非阻塞模式:
(1).概述:
ET模式要在epoll_ctl中进行设置(具体设置看代码),并且要把套接字设置为非阻塞模式。
下面概况以下ET与LT的区别:
ET (edge-triggered)是高效的工作方式,只支持no-block socket,它效率要比LT更高。ET与LT的区别在于,当一个新的事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新的事件再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个事件的。而LT模式下,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。
因此,LT模式下开发基于epoll的应用要简单些,不太容易出错。而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求得不到响应。
所以在下面代码中要封装一个read_data函数,来确保一次把数据缓冲区内的数据读取完。因为ET模式下只支持非阻塞模式,所以还要把每个套接字设置为非阻塞的。
下面代码实现 在浏览器访问服务器程序,并在浏览器中打印hello world :) ,当服务器程序发送完给浏览器的数据时,服务器程序关闭链接。
相关代码:
|
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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
|
1
/****************************************
2 > File Name:epoll_server.c
3 > Author:xiaoxiaohui
4 > mail:1924224891@qq.com
5 > Created Time:2016年05月28日 星期六 15时38分17秒
6 ****************************************/
7
8 #include<stdio.h>
9 #include<stdlib.h>
10 #include<sys/types.h>
11 #include<sys/socket.h>
12 #include<arpa/inet.h>
13 #include<netinet/in.h>
14 #include<string.h>
15 #include<unistd.h>
16 #include<sys/epoll.h>
17 #include<fcntl.h>
18 #include<
errno
.h>
19
20 #define LEN 1024
21
const
char
* IP =
"127.0.0.1"
;
22
const
int
PORT = 8080;
23
const
int
BACKLOG = 5;
24
int
timeout = 5000;
25
const
int
MAXEVENTS = 64;
26
struct
sockaddr_in local;
27
struct
sockaddr_in client;
28
int
SIZE_CLIENT =
sizeof
(client);
29
30
typedef
struct
data_buf
//用于存储epoll_event中的data中的不同元素
31 {
32
int
fd;
33
char
buf[LEN];
34 }data_buf_t, *data_buf_p;
35
36
static
int
set_no_block(
int
fd)
//把fd设置为非阻塞
37 {
38
int
oldfd = fcntl(fd, F_GETFL);
39
if
(oldfd < 0)
40 {
41
perror
(
"fcntl"
);
42
return
-1;
43 }
44
45
if
( fcntl(fd, F_SETFL, oldfd | O_NONBLOCK))
46 {
47
perror
(
"fcntl"
);
48
return
-1;
49 }
50
return
0;
51 }
52
53
int
read_data(
int
fd,
char
* buf,
int
len)
//ET模式下读取数据,因为ET模式下只通知一次,所以要保证把所有数据都读完
54 {
//成功返回读取的个数,失败返回-1,返回0代表读到文件尾
55
int
index = 0;
56
int
ret = -1;
57
58
while
(index < len)
59 {
60 ret = read(fd, buf + index, len - index);
61
printf
(
"the read return ret is %d\n"
, ret);
62
if
(ret > 0)
63 {
64 index += ret;
65 }
66
else
if
(ret < 0)
67 {
68
printf
(
"the errno is %d\n"
,
errno
);
69
if
(
errno
== EAGAIN)
70 {
71
break
;
72 }
73 }
74
else
75 {
76
return
0;
77 }
78 }
79
80
return
index;
81 }
82
83
84
85
86
static
int
ListenSock()
87 {
88
int
listenSock = socket(AF_INET, SOCK_STREAM, 0);
89
if
(listenSock < 0)
90 {
91
perror
(
"socket"
);
92
exit
(1);
93 }
94
95
int
opt = 1;
96
if
( setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, &opt,
sizeof
(opt)))
//设置端口复用
97 {
98
perror
(
"sersockopt"
);
99
exit
(2);
100 }
101
if
( set_no_block(listenSock) != 0)
//设置为非阻塞 .............修改
102 {
103
printf
(
"set_non_block is error\n"
);
104
exit
(3);
105 }
106
107 local.sin_family = AF_INET;
108 local.sin_port = htons(PORT);
109 local.sin_addr.s_addr = inet_addr(IP);
110
if
( bind(listenSock, (
struct
sockaddr*)&local,
sizeof
(local)) < 0)
111 {
112
perror
(
"bind"
);
113
exit
(4);
114 }
115
116
if
( listen(listenSock, BACKLOG) < 0)
117 {
118
perror
(
"listen"
);
119
exit
(5);
120 }
121
122
return
listenSock;
123 }
124
125
static
int
epoll_fd(
int
listenSock)
126 {
127
128
int
epoll_fd = epoll_create(256);
//size随便选一个值
129
130
struct
epoll_event ev;
//把listenSock设置进epoll_fd中
131 ev.events = EPOLLIN | EPOLLET;
//....................修改
132 ev.data.fd = listenSock;
133 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listenSock, &ev);
//系统会维护一个红黑树
134
135
struct
epoll_event ev_outs[MAXEVENTS];
//准备好的队列
136
int
max = MAXEVENTS;
137
138
while
(1)
139 {
140
int
num = -1;
141
switch
( num = epoll_wait(epoll_fd, ev_outs, max, timeout))
142 {
143
case
0:
//timeout
144
printf
(
"timeout.....\n"
);
145
break
;
146
case
-1:
//error
147
perror
(
"epoll_wait"
);
148
break
;
149
default
:
150
for
(
int
index = 0; index < num; index++)
151 {
152
if
(ev_outs[index].data.fd == listenSock && (ev_outs[index].events & EPOLLIN))
//监听套接字准备就绪
153 {
154
printf
(
"accept is ready\n"
);
155
int
linkSock = accept(listenSock, (
struct
sockaddr*)&client, &SIZE_CLIENT);
156
if
(linkSock < 0)
157 {
158
perror
(
"accept"
);
159
continue
;
//这次可能是一个新客户端的请求,所以后面可能还有文件描述符准备就绪了
160 }
161
162
if
( set_no_block(linkSock) != 0)
//设置为非阻塞 .............修改
163 {
164
printf
(
"set_non_block is error\n"
);
165
exit
(3);
166 }
167
168 ev.events = EPOLLIN | EPOLLET;
//把新套接字放到红黑树中 ...................修改
169 ev.data.fd = linkSock;
170 epoll_ctl(epoll_fd, EPOLL_CTL_ADD, linkSock, &ev);
171 }
172
else
//已链接套接字准备就绪
173 {
174
if
(ev_outs[index].events & EPOLLIN)
//读事件准备就绪
175 {
176 data_buf_p mem = (data_buf_p)
malloc
(
sizeof
(data_buf_t));
177
memset
(mem->buf,
'\0'
,
sizeof
(mem->buf));
178 mem->fd = ev_outs[index].data.fd;
179
180
printf
(
"read is ready\n"
);
181
int
ret = read_data(mem->fd, mem->buf,
sizeof
(mem->buf));
//.........................修改
182
printf
(
"read is over, the ret is %d\n"
, ret);
183
if
(ret > 0)
184 {
185 mem->buf[ret] =
'\0'
;
186
printf
(
"client# %s\n"
, mem->buf);
187
188 ev.data.ptr = mem;
//mem中即保持了fd,又保持了buf数据
189 ev.events = EPOLLOUT;
//读事件已经完成,现在要关心写事件
190 epoll_ctl(epoll_fd, EPOLL_CTL_MOD, mem->fd, &ev);
191 }
192
else
if
(ret == 0 )
//客户端已关闭
193 {
194
printf
(
"client is closed\n"
);
195 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, ev_outs[index].data.fd, NULL);
//把该文件描述符从红黑树中移除
196 close(ev_outs[index].data.fd);
197
free
(mem);
198 }
199
else
200 {
201
perror
(
"read"
);
202
continue
;
203 }
204 }
205
else
if
(ev_outs[index].events & EPOLLOUT)
//写事件准备就绪
206 {
207 data_buf_p mem = (data_buf_p)ev_outs[index].data.ptr;
208
int
fd = mem->fd;
209
char
* buf = mem->buf;
210
211
char
*msg =
"HTTP/1.0 200 OK\r\n\r\nhello world:)\r\n"
;
//.....................修改
212
if
( write(fd, msg,
strlen
(msg)) < 0)
//.........................修改
213 {
214
perror
(
"write"
);
215
continue
;
216 }
217
218 epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, NULL);
//把该文件描述符从红黑树中移除 ............修改
219 close(fd);
//.............................................修改
220
free
(mem);
//.............................................修改
221 mem = NULL;
222
223
// ev.events = EPOLLIN; //这个文件描述符的写事件已完成,下次关心读事件 ..................修改
224
// ev.data.fd = mem->fd; //........................................修改
225
// epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &ev); //....................修改
226 }
227
else
//DoNothing
228 {}
229 }
230 }
231
break
;
232 }
233 }
234 }
235
236
int
main()
237 {
238
int
listenSock = ListenSock();
239 epoll_fd(listenSock);
240 close(listenSock);
241
return
0;
242 }
|
执行结果:


