Unix网络编程(八、epoll)

三种I/O模型的对比

简单总结select/poll/epoll三者的区别如下:

select:

  • 监听的文件描述符个数有最高上限
  • 就绪检查时需要遍历所有事件,效率低
  • 内核态和用户态需要内存拷贝,开销大

poll:

  • 没有最大连接数的限制,基于链表来实现
  • 就绪检查时需要遍历所有事件,效率低
  • 内核态和用户态需要内存拷贝,开销大

epoll:

  • 没有最大连接数的限制,支持的文件描述符上限是最大可以打开文件的数目,1G内存的机器上是大约10万左右
  • 就绪检查时只需要遍历已就绪的事件,效率高
  • 内核态和用户态不需要内存拷贝,内核可以直接将就绪事件列表添加到用户提供的内存区

epoll简介

1
2
3
int epoll_create (int __size);
int epoll_ctl (int __epfd, int __op, int __fd, struct epoll_event *__event);
epoll_wait (int __epfd, struct epoll_event *__events, int __maxevents, int __timeout);

以上三个是内核提供的epoll的系统调用。

  1. epoll_create用于创建一个epoll句柄。同时,他会占用一个文件描述符,也就是说,在使用完了之后需要调用close关闭该文件描述符,否则可能导致fd被耗尽。返回值为占用的fd。
  2. epoll_ctl为epoll的事件注册函数,它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
  3. epoll_wait用于检测和等待激活的事件,有就绪事件后就会被添加到events

关于上面三个函数更详细的解释可以参考这篇博客

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
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>
#include <sys/stropts.h>
#include <sys/epoll.h>

static const int MAXLINE = 4096;
static const int MAXLISTEN = 1024;
static const int PORT = 45678;
static const int OPEN_MAX = 1024;

#define INFTIM -1

struct data {
int fd;
char buf[MAXLINE];
};

int e_socket(int domain, int type, int protocol);
int e_bind(int sockfd, sockaddr_in *myaddr, socklen_t addrlen);
int e_listen(int sockfd, int backlog);
int e_accept(int sockfd, sockaddr_in *myaddr, socklen_t *addrlen);
void init_clientfd(pollfd clients[], int socklen);
void init_sockaddr(sockaddr_in sockaddr);
int handle_client_connect(int epfd, int listenfd);
void handle_client_read(int epfd, struct epoll_event *event);
void handle_client_write(int epfd, struct epoll_event *event);

int e_socket(int domain, int type, int protocol)
{
int sockfd = socket(domain, type, protocol);
if (sockfd < 0)
{
exit(0);
}
return sockfd;
}

int e_bind(int sockfd, sockaddr_in *myaddr, socklen_t addrlen)
{
int ret = bind(sockfd, (sockaddr *)myaddr, addrlen);
if (ret != 0)
{
close(sockfd);
exit(0);
}
return ret;
}

int e_listen(int sockfd, int backlog)
{
int ret = listen(sockfd, backlog);
if (ret != 0)
{
close(sockfd);
exit(0);
}
return ret;
}

int e_accept(int sockfd, sockaddr_in *myaddr, socklen_t *addrlen)
{
int ret = accept(sockfd, (sockaddr *)myaddr, addrlen);
if (ret == -1)
{
printf("%d", errno);
exit(0);
}
return ret;
}

void init_clientfd(pollfd clients[], int socklen)
{
for (int i = 1; i < socklen; ++i)
{
clients[i].fd = -1;
}
}

void init_sockaddr(sockaddr_in *sockaddr)
{
if (!sockaddr)
{
return;
}

bzero(sockaddr, sizeof(sockaddr_in));
sockaddr->sin_family = AF_INET;
sockaddr->sin_addr.s_addr = htonl(INADDR_ANY);
sockaddr->sin_port = htons(PORT);
}

int handle_client_connect(int epfd, int listenfd)
{
sockaddr_in clientaddr;
socklen_t clientlen = sizeof(clientaddr);
int connfd = e_accept(listenfd, &clientaddr, &clientlen);

struct epoll_event ep_event;
ep_event.data.fd = connfd;
ep_event.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ep_event);

return connfd;
}

void handle_client_read(int epfd, struct epoll_event *event)
{
char buf[MAXLINE] = {0};
int sockfd = event->data.fd;

if (sockfd < 0)
{
return;
}

int read_len = read(sockfd, buf, MAXLINE);
if (read_len == 0) {
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, event);
close(sockfd);
}
else if (read_len < 0) {
if (errno == ECONNRESET) {
epoll_ctl(epfd, EPOLL_CTL_DEL, sockfd, event);
close(sockfd);
}
else {
exit(0);
}
}
else {
// 此处需要用一个结构体记录一下需要发送数据的fd,因为在改变
// event->data.ptr的时候,event中对应的fd会被改变。
// 而且还是为ptr赋值之后立即改变。百思不得其解。
// 先用这种方式解决,等找到原因后再来解释。
struct data send_data;
send_data.fd = sockfd;
strcpy(send_data.buf, buf);

event->data.ptr = (void*)&send_data;
event->events = EPOLLOUT;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, event);
}
}

void handle_client_write(int epfd, struct epoll_event *event)
{
struct data *send_data = (struct data*)event->data.ptr;
int sockfd = send_data->fd;

if (sockfd < 0) {
printf("write sockfd error");
exit(-1);
}

char *buf = (char *)send_data->buf;
write(sockfd, buf, strlen(buf));

event->data.fd = sockfd;
event->events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, event);
}

int main(int argc, const char *argv[])
{

int listen_sock = e_socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in servaddr;
init_sockaddr(&servaddr);

e_bind(listen_sock, &servaddr, sizeof(servaddr));
e_listen(listen_sock, MAXLISTEN);

int epfd = epoll_create(OPEN_MAX);
struct epoll_event ep_event;
ep_event.data.fd = listen_sock;
ep_event.events = EPOLLIN;
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ep_event);

struct epoll_event clients[OPEN_MAX];
while (true)
{
int readycount = epoll_wait(epfd, clients, OPEN_MAX, -1);

for (int i = 0; i < readycount; ++i)
{
if (clients[i].data.fd == listen_sock && (clients[i].events & EPOLLIN))
{
handle_client_connect(epfd, listen_sock);
}
else if (clients[i].events & EPOLLIN)
{
handle_client_read(epfd, &clients[i]);
}
else if (clients[i].events & EPOLLOUT)
{
handle_client_write(epfd, &clients[i]);
}
}
}
close(epfd);
return 0;
}