forked from Meha555/WebServer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
server.c
406 lines (387 loc) · 10.7 KB
/
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
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
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/epoll.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#define SERV_PORT 9999
#define MAXSIZE 2048
//获取一行\r\n结尾的数据
int get_line(int cfd, char *buf, int size)
{
int i = 0;
char c = '\0';
int n;
//当数据小于缓冲区 同时临时字符不为换行符时 循环执行
while ((i < size - 1) && (c != '\n'))
{ //c只是一个临时的字符
n = recv(cfd, &c, 1, 0); //每次读一个字符
if (n > 0)
{
if (c == '\r')
{
n = recv(cfd, &c, 1, MSG_PEEK); //拷贝读一次 读完之后缓冲区还有
if ((n > 0) && (c == '\n'))
{ // /r/n在linux系统中只用/n表示
recv(cfd, &c, 1, 0);
}
else
{
c = '\n';
}
}
buf[i] = c;
i++;
}
else
{
c = '\n';
}
}
buf[i] = '\0';
if (-1 == n) //没有数据返回-1
i = n;
return i;
}
//断开连接
void disconnect(int cfd, int epfd)
{
int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
if (ret != 0)
{
perror("epoll_cnl delete error");
exit(1);
}
close(cfd);
}
//发送服务器本地文件到客户端
void send_file(int cfd, const char *file)
{
//fd打开本地的文件
int fd = open(file, O_RDONLY);
if (fd == -1)
{ //404 错误页面
perror("file open error");
exit(1);
}
int n, ret = 0;
char buf[MAXSIZE] = {0};
while ((n = read(fd, buf, sizeof(buf))) > 0)
{
ret = send(cfd, buf, n, 0);
if (ret == -1)
{
if (errno == EAGAIN || errno == EINTR)
continue;
else
{
perror("send error");
exit(1);
}
}
}
close(fd);
}
//应答http 客户端文件描述符 状态号 状态描述 文本类型 文件长度
void send_respond(int cfd, int no, char *disp, char *type, int len)
{
int ret;
char buf[MAXSIZE] = {0};
sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);
sprintf(buf + strlen(buf), "%s\r\n", type);
sprintf(buf + strlen(buf), "Content-Length:%d\r\n\r\n", len);
do
{
ret = send(cfd, buf, strlen(buf), 0) > 0;
if (ret == -1)
{
if (errno == EAGAIN || errno == EINTR)
continue;
else
{
perror("send error");
exit(1);
}
}
} while (0);
}
const char *get_file_type(const char *name)
{
char *dot;
dot = strrchr(name, '.');
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, "jpeg") == 0 || strcmp(dot, ".ico") == 0)
return "image/jpeg";
if (strcmp(dot, ".gif") == 0)
return "image/gif";
if (strcmp(dot, ".png") == 0)
return "image/png";
if (strcmp(dot, ".css") == 0)
return "text/css";
if (strcmp(dot, ".au") == 0)
return "audio/basic";
if (strcmp(dot, ".wav") == 0)
return "audio/wav";
if (strcmp(dot, ".avi") == 0)
return "video/x-msvideo";
if (strcmp(dot, ".mp3") == 0)
return "audio/mpeg";
return "text/html; charset=utf-8";
}
//详细处理目录
void send_dir(int cfd, const char *dirname)
{
int i, ret;
char buf[MAXSIZE] = {0};
sprintf(buf, "<html><head><title>目录名:%s</title></head>", dirname);
sprintf(buf + strlen(buf), "<body><h3>当前目录:%s</h3><table>", dirname);
//char enstr[1024] = {0};
char path[1024] = {0};
//目录项二级指针
struct dirent **ptr;
int num = scandir(dirname, &ptr, NULL, alphasort);
//遍历
for (i = 2; i < num; i++)
{
char *name = ptr[i]->d_name;
//拼接文件的完整路径
sprintf(path, "%s/%s", dirname, name);
struct stat stt;
stat(path, &stt);
// encode_str(enstr, sizeof(enstr), name); //编码中文字符
if (S_ISREG(stt.st_mode))
{ //生成超链接
sprintf(buf + strlen(buf), "<tr><td> <a href=\"%s\">%s</a> </td><td>%ld</td></tr>", name, name, (long)stt.st_size);
}
else if (S_ISDIR(stt.st_mode))
{
sprintf(buf + strlen(buf), "<tr><td> <a href=\"%s/\">%s</a> </td><td>%ld</td></tr>", name, name, (long)stt.st_size);
}
}
sprintf(buf + strlen(buf), "</body></html>%s", "");
//判断返回值
send(cfd, buf, strlen(buf), 0);
memset(buf, 0, sizeof(buf));
}
//详细处理http
void http_request(int cfd, const char *file)
{
struct stat st;
int ret = stat(file, &st);
if (ret == -1)
{
//回发浏览器404错误页面
send_respond(cfd, 404, "FAIL", "Content-Type: text/html; charset=utf-8", -1);
send_file(cfd, "404.html");
perror("file exist error 404 ");
return;
}
if (S_ISREG(st.st_mode))
{ //是一个普通文件
//回发 http协议应答 回发浏览器内容
// printf("This a regular file!\n");
char buf[MAXSIZE] = {0};
const char *content_type = get_file_type(file);
sprintf(buf, "Content-Type: %s", content_type);
send_respond(cfd, 200, "OK", buf, -1);
send_file(cfd, file);
}
else if (S_ISDIR(st.st_mode))
{
send_respond(cfd, 200, "OK", "Content-Type: text/html; charset=utf-8", -1);
send_dir(cfd, file);
}
}
//处理浏览器发来的http协议请求 readline \r\n
void do_read(int cfd, int epfd)
{
char line[MAXSIZE] = {0};
do
{
int ret = get_line(cfd, line, sizeof(line));
if (ret == 0)
{
//检测到客户端关闭
printf("client closed!\n");
disconnect(cfd, epfd);
}
else if (ret == -1)
{
if (errno == EAGAIN || errno == EINTR)
continue;
else
{
perror("recv error");
exit(1);
}
}
else
{
//对首行字符串拆分
char method[24], path[256], protocol[16];
sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);
//printf("%s %s %s\n", method, path, protocol);
while (1)
{ //把无用的数据从缓冲区中读走
char buf[1024] = {0};
ret = get_line(cfd, buf, sizeof(buf));
if (ret == 1 || ret == -1)
{
break;
}
//printf("%s\n", buf);
}
//sleep(1);
//判断是不是GET请求
if (strncasecmp(method, "GET", 3) == 0)
{
char *file = path + 1; //取出客户端需要访问的文件名
if (strcmp(path, "/") == 0)
file = "./"; //当前资源目录
http_request(cfd, file);
}
}
} while (0);
}
//处理新连接事件
void do_accept(int lfd, int epfd)
{
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr); //这是一个传出参数
int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
if (cfd == -1)
{
perror("accept error");
exit(1);
}
//打印客户端IP和port
char buf[MAXSIZE] = {0};
printf("client IP: %s, client port: %d\n",
inet_ntop(AF_INET, &clientaddr.sin_addr, buf, sizeof(buf)),
ntohs(clientaddr.sin_port));
//设置cfd非阻塞
int flag = fcntl(cfd, F_GETFL);
flag |= O_NONBLOCK;
fcntl(cfd, F_SETFL, flag);
//将新节点cfd挂到epoll树上监听
struct epoll_event ev;
ev.data.fd = cfd;
//边沿触发模式 ET
ev.events = EPOLLIN | EPOLLET;
int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
if (ret == -1)
{
perror("epoll_ctl add cfd error");
exit(1);
}
}
//创建套接字lfd并挂上树,同时返回该文件描述符
int init_listen_fd(int port, int epfd)
{
int lfd;
int ret;
//套接字结构体
struct sockaddr_in servaddr;
//创建套接字
lfd = socket(AF_INET, SOCK_STREAM, 0);
if (lfd == -1)
{
perror("socket error");
exit(1);
}
//初始化servaddr结构体
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//端口复用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//绑定端口
ret = bind(lfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (ret == -1)
{
perror("bind error");
exit(1);
}
//最大同时连接数
ret = listen(lfd, 128);
//lfd添加到 epoll树上
struct epoll_event tep;
tep.events = EPOLLIN;
tep.data.fd = lfd;
//指定监听为读
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tep); //把lfd挂到红黑树上
if (ret == -1)
{
perror("epoll_ctl add lfd error");
exit(1);
}
return lfd;
}
//运行epoll
void epoll_run(int port)
{
int i = 0;
struct epoll_event all_events[MAXSIZE];
//创建一个epoll监听数根
int epfd = epoll_create(MAXSIZE);
if (epfd == -1)
{
perror("epoll_create error");
exit(1);
}
//创建获得套接字文件描述符lfd, 并添加至监听树
int lfd = init_listen_fd(port, epfd);
//监听节点对应的事件
while (1)
{
int ret = epoll_wait(epfd, all_events, MAXSIZE, -1); //没有读到则一直阻塞 否则返回满足事件的个数
if (ret == -1)
{
perror("epoll_wait error");
exit(1);
}
struct epoll_event *pev; //临时指针
for (i = 0; i < ret; i++)
{
pev = &all_events[i];
if (!(pev->events & EPOLLIN)) //如果不是读事件 就继续找
continue;
if (pev->data.fd == lfd)
{
do_accept(lfd, epfd); //处理新连接请求
}
else
{
do_read(pev->data.fd, epfd); //处理读入数据
}
}
}
}
int main(int argc, char *argv[])
{
//命令行参数获取 端口和server提供的目录
if (argc < 3)
{
printf("./server port path\n");
exit(1);
}
//把字符串型的端口转换为整型
int port = atoi(argv[1]);
//改变进程工作目录
int ret = chdir(argv[2]);
if (ret != 0)
{
perror("chdir error");
exit(1);
}
//启动epoll监听
epoll_run(port);
return 0;
}