之前的文章中使用C语言实现基于TCP的WinSock套接字编程。基于此,同样可以使用C语言来实现简单的Web服务器。

基础知识

TCP协议和WinSock套接字

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP协议的通信需要经过创建连接(三次握手)、数据传送、终止连接(四次挥手)三个步骤。

TCP三次握手

TCP三次握手

TCP四次挥手

TCP四次挥手

WinSock套接字是Windows操作系统所提供的网络编程接口,是一个抽象层,应用程序可以通过套接字来实现数据的发送和接收。

基于TCP的WinSock工作流程

基于TCP的WinSock工作流程

HTTP协议

超文本传输协议(HTTP,Hypertext Transfer Protocol),是一个基于请求与响应模式的、无状态的、应用层的协议,通常运行在TCP之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码的形式给出,消息内容则具有一个类似MIME的格式。
可以抽象的认为,基于TCP的WinSock编程已经铺好了一条路,接下来需要的就是选用一辆好的卡车来输送客户端和服务器端的“交流”信息了。HTTP协议正是这辆卡车。

HTTP请求报文的基本格式构成

HTTP请求报文的基本格式构成

详细设计

Web服务器与客户端之间的大致工作流程

Web服务器与客户端之间的大致工作流程
整个Web服务器设计可分为以下几个部分:
1. 初始化Windows Socket,为TCP连接的建立做准备
①加载Windows Socket;
②创建套接字;
③根据服务器端的相关IP地址和端口号信息,进行套接字绑定。

2. 启动监听,接受客户端请求建立TCP连接,接收HTTP请求报文
①Web服务器启动监听;
②接收来自客户端的连接请求,建立TCP连接;
③接收来自客户端的TCP传输数据,即HTTP请求报文。

3. 处理HTTP请求报文并做出响应
①处理HTTP请求报文的请求行,提取“请求方法”、“URL”、“HTTP版本“三个关键要素;
②判断处理“请求方法”,根据判断处理的结果,构造相应的响应报文,反馈相关给客户端,并且在服务器端打印反馈的结果;
③判断处理“URL”,根据判断处理的结果,构造相应的响应报文,反馈相关给客户端,并且在服务器端打印反馈的结果;
④由于Web服务器的要求不太高以及目前主流的客户端浏览器都会采用较“统一”的HTTP协议版本,所以“HTTP版本”的关键信息可以做忽略处理。

4. 关闭连接及Windows Socket
①关闭所建立连接的套接字;
②关闭Windows Socket。

注:根据实际情况,可运用循环多线程等方式对以上步骤进行重构。

代码实现

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
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define _CRT_SECURE_NO_WARNNINGS

#define SERVER_IP_ADDR "127.0.0.1" //服务器IP地址
#define SERVER_PORT 80 //服务器端口号
#define BACKLOG 10
#define BUF_SIZE 1024
#define OK 1
#define ERROR 0

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

const char* Server_name = "Server: Web Server 1.0 - BooLo\r\n";
//Web服务器信息

int Server_Socket_Init();
int Handle_Request_Message(char* message, int Socket);
int Judge_URI(char* URI, int Socket);
int Send_Ifon(int Socket, const char* sendbuf, int Length);
int Error_Request_Method(int Socket);
int Inquire_File(char* URI);
int File_not_Inquire(int Socket);
int Send_File(char* URI, int Socket);
int Logo();
const char* Judge_Method(char* method, int Socket);
const char* Judge_File_Type(char* URI, const char* content_type);
const char* Get_Data(const char* cur_time);
const char* Post_Value(char* message);

int Server_Socket_Init() {
//初始化和构造套接字
WORD wVersionrequested;
WSADATA wsaData;
SOCKET ServerSock;
struct sockaddr_in ServerAddr;
int rval;

/* 加载Winsock */
wVersionrequested = MAKEWORD(2, 2);
if (WSAStartup(wVersionrequested, &wsaData) != 0) {
printf("Failed to load Winsock!\n");
system("pause");
return -1;
}
printf("Succeed to load Winsock!\n");

/* 创建套接字 */
ServerSock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ServerSock == INVALID_SOCKET) {
printf("Failed to create socket!\n");
system("pause");
exit(1);
}
printf("Succeed to create socket!\n");

/* 配置服务器IP、端口信息 */
memset(&ServerAddr, 0, sizeof(struct sockaddr)); //每一个字节都用0来填充
ServerAddr.sin_family = AF_INET;
ServerAddr.sin_port = htons(SERVER_PORT);
ServerAddr.sin_addr.s_addr = inet_addr(SERVER_IP_ADDR);

/* 绑定 */
rval = bind(ServerSock, (SOCKADDR*)&ServerAddr, sizeof(struct sockaddr));
if (rval == SOCKET_ERROR) {
printf("Failed to bind stream socket!\n");
system("pause");
exit(1);
}
printf("Succeed to bind stream socket!\n");

return ServerSock;
}

int Handle_Request_Message(char* message, int Socket) {
//处理HTTP请求报文信息
int rval = 0;
char Method[BUF_SIZE];
char URI[BUF_SIZE];
char Version[BUF_SIZE];

if (sscanf(message, "%s %s %s", Method, URI, Version) != 3) {
printf("Request line error!\n");
return ERROR;
} //提取"请求方法"、"URL"、"HTTP版本"三个关键要素

if (Judge_Method(Method, Socket) == ERROR) {
return ERROR;
}
else if(Judge_Method(Method, Socket) == "POST") {
Post_Value(message);
} //判断处理"请求方法"

if (Judge_URI(URI, Socket) == ERROR) {
return ERROR;
} //判断处理"URI"
else
rval = Send_File(URI, Socket);

if (rval == OK) {
printf("The process is successfully finished!\n");
}

return OK;
}

const char* Judge_Method(char* method, int Socket) {
//判断请求方式
if (strcmp(method, "GET") == 0) {
return "GET";
}
else if (strcmp(method, "POST") == 0) {
return "POST";
}
else{
Error_Request_Method(Socket);
return ERROR;
}
}

int Judge_URI(char* URI, int Socket) {
//判断请求URI
if (Inquire_File(URI) == ERROR) {
File_not_Inquire(Socket);
return ERROR;
}
else
return OK;
}

int Send_Ifon(int Socket, const char* sendbuf, int Length) {
//发送信息到客户端
int sendtotal = 0, bufleft, rval = 0;

bufleft = Length;
while (sendtotal < Length) {
rval = send(Socket, sendbuf + sendtotal, bufleft, 0);
if (rval < 0) {
break;
}
sendtotal += rval;
bufleft -= rval;
}

Length = sendtotal;

return rval < 0 ? ERROR : OK;
}

int Error_Request_Method(int Socket) {
//501 Not Implemented响应
const char* Method_err_line = "HTTP/1.1 501 Not Implemented\r\n";
const char* cur_time = "";
const char* Method_err_type = "Content-type: text/plain\r\n";
const char* File_err_length = "Content-Length: 41\r\n";
const char* Method_err_end = "\r\n";
const char* Method_err_info = "The request method is not yet completed!\n";

printf("The request method from client's request message is not yet completed!\n");

if (Send_Ifon(Socket, Method_err_line, strlen(Method_err_line)) == ERROR) {
printf("Sending method_error_line failed!\n");
return ERROR;
}

if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {
printf("Sending Server_name failed!\n");
return ERROR;
}

cur_time = Get_Data(cur_time);
Send_Ifon(Socket, "Data: ", 6);
if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {
printf("Sending cur_time error!\n");
return ERROR;
}

if (Send_Ifon(Socket, Method_err_type, strlen(Method_err_type)) == ERROR) {
printf("Sending method_error_type failed!\n");
return ERROR;
}

if (Send_Ifon(Socket, Method_err_end, strlen(Method_err_end)) == ERROR) {
printf("Sending method_error_end failed!\n");
return ERROR;
}

if (Send_Ifon(Socket, Method_err_info, strlen(Method_err_info)) == ERROR) {
printf("Sending method_error_info failed!\n");
return ERROR;
}

return OK;
}

int Inquire_File(char* URI) {
//查找文件
struct stat File_info;

if (stat(URI, &File_info) == -1)
return ERROR;
else
return File_info.st_size;
}

int File_not_Inquire(int Socket) {
//404 Not Found响应
const char* File_err_line = "HTTP/1.1 404 Not Found\r\n";
const char* cur_time = "";
const char* File_err_type = "Content-type: text/plain\r\n";
const char* File_err_length = "Content-Length: 42\r\n";
const char* File_err_end = "\r\n";
const char* File_err_info = "The file which is requested is not found!\n";

printf("The request file from client's request message is not found!\n");

if (Send_Ifon(Socket, File_err_line, strlen(File_err_line)) == ERROR) {
printf("Sending file_error_line error!\n");
return ERROR;
}

if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {
printf("Sending Server_name failed!\n");
return ERROR;
}

cur_time = Get_Data(cur_time);
Send_Ifon(Socket, "Data: ", 6);
if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {
printf("Sending cur_time error!\n");
return ERROR;
}

if (Send_Ifon(Socket, File_err_type, strlen(File_err_type)) == ERROR) {
printf("Sending file_error_type error!\n");
return ERROR;
}

if (Send_Ifon(Socket, File_err_length, strlen(File_err_length)) == ERROR) {
printf("Sending file_error_length error!\n");
return ERROR;
}

if (Send_Ifon(Socket, File_err_end, strlen(File_err_end)) == ERROR) {
printf("Sending file_error_end error!\n");
return ERROR;
}

if (Send_Ifon(Socket, File_err_info, strlen(File_err_info)) == ERROR) {
printf("Sending file_error_info failed!\n");
return ERROR;
}

return OK;
}

int Send_File(char* URI, int Socket) {
//200 OK响应
const char* File_ok_line = "HTTP/1.1 200 OK\r\n";
const char* cur_time = "";
const char* File_ok_type = "";
const char* File_ok_length = "Content-Length: ";
const char* File_ok_end = "\r\n";

FILE* file;
struct stat file_stat;
char Length[BUF_SIZE];
char sendbuf[BUF_SIZE];
int send_length;

if (Judge_File_Type(URI, File_ok_type) == ERROR) {
printf("The request file's type from client's request message is error!\n");
return ERROR;
}

file = fopen(URI, "rb");
if (file != NULL) {
fstat(fileno(file), &file_stat);
itoa(file_stat.st_size, Length, 10);

if (Send_Ifon(Socket, File_ok_line, strlen(File_ok_line)) == ERROR) {
printf("Sending file_ok_line error!\n");
return ERROR;
}

if (Send_Ifon(Socket, Server_name, strlen(Server_name)) == ERROR) {
printf("Sending Server_name failed!\n");
return ERROR;
}

cur_time = Get_Data(cur_time);
Send_Ifon(Socket, "Data: ", 6);
if (Send_Ifon(Socket, cur_time, strlen(cur_time)) == ERROR) {
printf("Sending cur_time error!\n");
return ERROR;
}

File_ok_type = Judge_File_Type(URI, File_ok_type);
if (Send_Ifon(Socket, File_ok_type, strlen(File_ok_type)) == ERROR) {
printf("Sending file_ok_type error!\n");
return ERROR;
}

if (Send_Ifon(Socket, File_ok_length, strlen(File_ok_length)) != ERROR) {
if (Send_Ifon(Socket, Length, strlen(Length)) != ERROR) {
if (Send_Ifon(Socket, "\n", 1) == ERROR) {
printf("Sending file_ok_length error!\n");
return ERROR;
}
}
}

if (Send_Ifon(Socket, File_ok_end, strlen(File_ok_end)) == ERROR) {
printf("Sending file_ok_end error!\n");
return ERROR;
}

while (file_stat.st_size > 0) {
if (file_stat.st_size < 1024) {
send_length = fread(sendbuf, 1, file_stat.st_size, file);
if (Send_Ifon(Socket, sendbuf, send_length) == ERROR) {
printf("Sending file information error!\n");
continue;
}
file_stat.st_size = 0;
}
else {
send_length = fread(sendbuf, 1, 1024, file);
if (Send_Ifon(Socket, sendbuf, send_length) == ERROR) {
printf("Sending file information error!\n");
continue;
}
file_stat.st_size -= 1024;
}
}
}
else {
printf("The file is NULL!\n");
return ERROR;
}

return OK;
}

const char* Judge_File_Type(char* URI, const char* content_type) {
//文件类型判断
const char* suffix;

if ((suffix = strrchr(URI, '.')) != NULL)
suffix = suffix + 1;

if (strcmp(suffix, "html") == 0) {
return content_type = "Content-type: text/html\r\n";
}
else if (strcmp(suffix, "jpg") == 0) {
return content_type = "Content-type: image/jpg\r\n";
}
else if (strcmp(suffix, "png") == 0) {
return content_type = "Content-type: image/png\r\n";
}
else if (strcmp(suffix, "gif") == 0) {
return content_type = "Content-type: image/gif\r\n";
}
else if (strcmp(suffix, "txt") == 0) {
return content_type = "Content-type: text/plain\r\n";
}
else if (strcmp(suffix, "xml") == 0) {
return content_type = "Content-type: text/xml\r\n";
}
else if (strcmp(suffix, "rtf") == 0) {
return content_type = "Content-type: text/rtf\r\n";
}
else
return ERROR;
}

const char* Get_Data(const char* cur_time) {
//获取Web服务器的当前时间作为响应时间
time_t curtime;
time(&curtime);
cur_time = ctime(&curtime);

return cur_time;
}

const char* Post_Value(char* message) {
//获取客户端POST请求方式的值
const char* suffix;

if ((suffix = strrchr(message, '\n')) != NULL)
suffix = suffix + 1;
printf("\n\nPost Value: %s\n\n", suffix);

return suffix;
}

int Logo() {
//Web服务器标志信息
printf("___________________________________________________________\n");
printf(" __ ________ _______\n");
printf(" \\ \\ / / ____| _____\\\n");
printf(" \\ \\ /\\ / /| |____ |____) )\n");
printf(" \\ \\/ \\/ / | ____| ____( __ __ __ ___\n");
printf(" \\ /\\ / | |____ |____) )(__ |_ \\ /|_ |___)\n");
printf(" \\/ \\/ |______|_______/ __)|__ \\/ |__| \\\n");
printf("\n");
printf(" Welcome to use the Web Server!\n");
printf(" Version 1.0\n\n");
printf(" BooLo\n");
printf("___________________________________________________________\n\n");

return OK;
}

int main() {
//实现主要功能
SOCKET ServerSock, MessageSock;
struct sockaddr_in ClientAddr;
int rval, Length;
char revbuf[BUF_SIZE];

Logo();
printf("Web Server 1.0 is starting......\n\n");
ServerSock = Server_Socket_Init();
printf("\n-----------------------------------------------------------\n");

while (OK) {
/* 启动监听 */
rval = listen(ServerSock, BACKLOG);
if (rval == SOCKET_ERROR) {
printf("Failed to listen socket!\n");
system("pause");
exit(1);
}
printf("Listening the socket ......\n");

/* 接受客户端请求建立连接 */
Length = sizeof(struct sockaddr);
MessageSock = accept(ServerSock, (SOCKADDR*)&ClientAddr, &Length);
if (MessageSock == INVALID_SOCKET) {
printf("Failed to accept connection from client!\n");
system("pause");
exit(1);
}
printf("Succeed to accept connection from [%s:%d] !\n\n", inet_ntoa(ClientAddr.sin_addr), ntohs(ClientAddr.sin_port));

/* 接收客户端请求数据 */
memset(revbuf, 0, BUF_SIZE); //每一个字节都用0来填充
rval = recv(MessageSock, revbuf, BUF_SIZE, 0);
revbuf[rval] = 0x00;
if (rval <= 0)
printf("Failed to receive request message from client!\n");
else {
printf("%s\n", revbuf); //输出请求数据内容
rval = Handle_Request_Message(revbuf, MessageSock);
}

closesocket(MessageSock);
printf("\n-----------------------------------------------------------\n");
}

closesocket(ServerSock); //关闭套接字
WSACleanup(); //停止Winsock

return OK;
}


功能测试

程序运行测试

程序运行结果

index.html页面请求测试

服务器端信息
响应页面
响应状态信息
响应头信息
响应头信息

picture1.jpg图片请求测试

服务器端信息
响应页面
响应状态信息
响应头信息

download_test.rtf文档下载测试

服务器端信息
响应状态信息
响应头信息
下载文件

POST请求方式测试

客户端POST请求数据
服务器端信息

404响应状态测试

服务器端信息
响应页面
响应状态信息
响应头信息

内容总结

本文中Web服务器程序的设计和开发,关键在于TCP协议、Windows Socket网络接口、HTTP协议三大知识点。在实现最基本的Web服务器核心功能的过程中,这三者缺一不可。
在Web服务器程序的设计和开发过程中,需要充分了解Web服务器的工作过程:从浏览器发起请求到浏览器显示请求的页面内容的整个过程,来逐一完成各个阶段的设计和开发。
开发过程中,特别是编码的过程中,会遇到很多的难题。这些难题通过参考一些官方的代码示例能够得到一定的启示,从而解决问题。

评论区 (输入正确的邮箱可以收到回复哦!网址可选)