tinyhttp学习:
1)每个函数的作用:
accept_request: 处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。
bad_request: 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST.
cat: 读取服务器上某个文件写到 socket 套接字。
cannot_execute: 主要处理发生在执行 cgi 程序时出现的错误。
error_die: 把错误信息写到 perror 并退出。
execute_cgi: 运行 cgi 程序的处理,也是个主要函数。
get_line: 读取套接字的一行,把回车换行等情况都统一为换行符结束。
headers: 把 HTTP 响应的头部写到套接字。
not_found: 主要处理找不到请求的文件时的情况。
sever_file: 调用 cat 把服务器文件返回给浏览器。
startup: 初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。
unimplemented: 返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持。
2)建议源码阅读顺序: main -> startup -> accept_request -> execute_cgi, 通晓主要工作流程后再仔细把每个函数的源码看一看。
3)工作流程
(1) 服务器启动,在指定端口或随机选取端口绑定 httpd 服务。
(2)收到一个 HTTP 请求时(其实就是 listen 的端口 accpet 的时候),派生一个线程运行 accept_request 函数。
(3)取出 HTTP 请求中的 method (GET 或 POST) 和 url,。对于 GET 方法,如果有携带参数,则 query_string 指针指向 url 中 ? 后面的 GET 参数。
(4) 格式化 url 到 path 数组,表示浏览器请求的服务器文件路径,在 tinyhttpd 中服务器文件是在 htdocs 文件夹下。当 url 以 / 结尾,或 url 是个目录,则默认在 path 中加上 index.html,表示访问主页。
(5)如果文件路径合法,对于无参数的 GET 请求,直接输出服务器文件到浏览器,即用 HTTP 格式写到套接字上,跳到(10)。其他情况(带参数 GET,POST 方式,url 为可执行文件),则调用 excute_cgi 函数执行 cgi 脚本。
(6)读取整个 HTTP 请求并丢弃,如果是 POST 则找出 Content-Length. 把 HTTP 200 状态码写到套接字。
(7) 建立两个管道,cgi_input 和 cgi_output, 并 fork 一个进程。
(8) 在子进程中,把 STDOUT 重定向到 cgi_outputt 的写入端,把 STDIN 重定向到 cgi_input 的读取端,关闭 cgi_input 的写入端 和 cgi_output 的读取端,设置 request_method 的环境变量,GET 的话设置 query_string 的环境变量,POST 的话设置 content_length 的环境变量,这些环境变量都是为了给 cgi 脚本调用,接着用 execl 运行 cgi 程序。
(9) 在父进程中,关闭 cgi_input 的读取端 和 cgi_output 的写入端,如果 POST 的话,把 POST 数据写入 cgi_input,已被重定向到 STDIN,读取 cgi_output 的管道输出到客户端,该管道输入是 STDOUT。接着关闭所有管道,等待子进程结束。这一部分比较乱,见下图说明:
图 1 管道初始状态
图 2 管道最终状态
(10) 关闭与浏览器的连接,完成了一次 HTTP 请求与回应,因为 HTTP 是无连接的。
fork的项目主页在:https://github.com/ezlippi/tinyhttpd
httpd.c 代码
1 /* J. David's webserver */
2 /* This is a simple webserver.
3 * Created November 1999 by J. David Blackstone.
4 * CSE 4344 (Network concepts), Prof. Zeigler
5 * University of Texas at Arlington
6 */
7 /* This program compiles for Sparc Solaris 2.6.
8 * To compile for Linux:
9 * 1) Comment out the #include <pthread.h> line.
10 * 2) Comment out the line that defines the variable newthread.
11 * 3) Comment out the two lines that run pthread_create().
12 * 4) Uncomment the line that runs accept_request().
13 * 5) Remove -lsocket from the Makefile.
14 */
15 #include <stdio.h>
16 #include <sys/socket.h>
17 #include <sys/types.h>
18 #include <netinet/in.h>
19 #include <arpa/inet.h>
20 #include <unistd.h>
21 #include <ctype.h>
22 #include <strings.h>
23 #include <string.h>
24 #include <sys/stat.h>
25 #include <pthread.h>
26 #include <sys/wait.h>
27 #include <stdlib.h>
28
29 //宏定义,是否是空格
30 #define ISspace(x) isspace((int)(x))
31
32 #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"
33
34 //每次收到请求,创建一个线程来处理接受到的请求
35 //把client_sock转成地址作为参数传入pthread_create
36 void accept_request(void *arg);
37
38 //错误请求
39 void bad_request(int);
40
41 //读取文件
42 void cat(int, FILE *);
43
44 //无法执行
45 void cannot_execute(int);
46
47 //错误输出
48 void error_die(const char *);
49
50 //执行cig脚本
51 void execute_cgi(int, const char *, const char *, const char *);
52
53 //得到一行数据,只要发现c为\n,就认为是一行结束,如果读到\r,再用MSG_PEEK的方式读入一个字符,如果是\n,从socket用读出
54 //如果是下个字符则不处理,将c置为\n,结束。如果读到的数据为0中断,或者小于0,也视为结束,c置为\n
55 int get_line(int, char *, int);
56
57 //返回http头
58 void headers(int, const char *);
59
60 //没有发现文件
61 void not_found(int);
62
63 //如果不是CGI文件,直接读取文件返回给请求的http客户端
64 void serve_file(int, const char *);
65
66 //开启tcp连接,绑定端口等操作
67 int startup(u_short *);
68
69 //如果不是Get或者Post,就报方法没有实现
70 void unimplemented(int);
71
72 // Http请求,后续主要是处理这个头
73 //
74 // GET / HTTP/1.1
75 // Host: 192.168.0.23:47310
76 // Connection: keep-alive
77 // Upgrade-Insecure-Requests: 1
78 // User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36
79 // Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*; q = 0.8
80 // Accept - Encoding: gzip, deflate, sdch
81 // Accept - Language : zh - CN, zh; q = 0.8
82 // Cookie: __guid = 179317988.1576506943281708800.1510107225903.8862; monitor_count = 5
83 //
84
85 // POST / color1.cgi HTTP / 1.1
86 // Host: 192.168.0.23 : 47310
87 // Connection : keep - alive
88 // Content - Length : 10
89 // Cache - Control : max - age = 0
90 // Origin : http ://192.168.0.23:40786
91 // Upgrade - Insecure - Requests : 1
92 // User - Agent : Mozilla / 5.0 (Windows NT 6.1; WOW64) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 55.0.2883.87 Safari / 537.36
93 // Content - Type : application / x - www - form - urlencoded
94 // Accept : text / html, application / xhtml + xml, application / xml; q = 0.9, image / webp, */*;q=0.8
95 // Referer: http://192.168.0.23:47310/
96 // Accept-Encoding: gzip, deflate
97 // Accept-Language: zh-CN,zh;q=0.8
98 // Cookie: __guid=179317988.1576506943281708800.1510107225903.8862; monitor_count=281
99 // Form Data
100 // color=gray
101
102 /**********************************************************************/
103 /* A request has caused a call to accept() on the server port to
104 * return. Process the request appropriately.
105 * Parameters: the socket connected to the client */
106 /**********************************************************************/
107 void accept_request(void *arg)
108 {
109 //socket
110 int client = (intptr_t)arg;
111 char buf[1024];
112 int numchars;
113 char method[255];
114 char url[255];
115 char path[512];
116 size_t i, j;
117 struct stat st;
118 int cgi = 0; /* becomes true if server decides this is a CGI
119 * program */
120 char *query_string = NULL;
121 //根据上面的Get请求,可以看到这边就是取第一行
122 //这边都是在处理第一条http信息
123 //"GET / HTTP/1.1\n"
124 numchars = get_line(client, buf, sizeof(buf));
125 i = 0; j = 0;
126
127 //第一行字符串提取Get
128 while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
129 {
130 method[i] = buf[j];
131 i++; j++;
132 }
133 //结束
134 method[i] = '\0';
135
136 //判断是Get还是Post
137 if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
138 {
139 unimplemented(client);
140 return;
141 }
142
143 //如果是POST,cgi置为1
144 if (strcasecmp(method, "POST") == 0)
145 cgi = 1;
146
147 i = 0;
148 //跳过空格
149 while (ISspace(buf[j]) && (j < sizeof(buf)))
150 j++;
151
152 //得到 "/" 注意:如果你的http的网址为http://192.168.0.23:47310/index.html
153 // 那么你得到的第一条http信息为GET /index.html HTTP/1.1,那么
154 // 解析得到的就是/index.html
155 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
156 {
157 url[i] = buf[j];
158 i++; j++;
159 }
160 url[i] = '\0';
161
162 //判断Get请求
163 if (strcasecmp(method, "GET") == 0)
164 {
165 query_string = url;
166 while ((*query_string != '?') && (*query_string != '\0'))
167 query_string++;
168 if (*query_string == '?')
169 {
170 cgi = 1;
171 *query_string = '\0';
172 query_string++;
173 }
174 }
175 //htdocs是host documents的缩写。直接翻译就是主机文件。
//Hosts文件主要作用是定义IP地址和主机名的映射关系,是一个映射IP地址和主机名的规定。
//可以用文本文件打开!当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从
//Hosts文件中寻找对应的IP地址,一旦找到,浏览器会立即打开对应网页,如果没有找到,
//则浏览器会将网址提交DNS服务器进行IP地址解析。
176 //路径
177 sprintf(path, "htdocs%s", url);
178
179 //默认地址,解析到的路径如果为/,则自动加上index.html
180 if (path[strlen(path) - 1] == '/')
181 strcat(path, "index.html");
182
183 //获得文件信息
184 if (stat(path, &st) == -1) {
185 //把所有http信息读出然后丢弃
186 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
187 numchars = get_line(client, buf, sizeof(buf));
188
189 //没有找到
190 not_found(client);
191 }
192 else
193 {
194 if ((st.st_mode & S_IFMT) == S_IFDIR)
195 strcat(path, "/index.html");
196 //如果你的文件默认是有执行权限的,自动解析成cgi程序,如果有执行权限但是不能执行,会接受到报错信号
197 if ((st.st_mode & S_IXUSR) ||
198 (st.st_mode & S_IXGRP) ||
199 (st.st_mode & S_IXOTH) )
200 cgi = 1;
201 if (!cgi)
202 //接读取文件返回给请求的http客户端
203 serve_file(client, path);
204 else
205 //执行cgi文件
206 execute_cgi(client, path, method, query_string);
207 }
208 //执行完毕关闭socket
209 close(client);
210 }
211
212 /**********************************************************************/
213 /* Inform the client that a request it has made has a problem.
214 * Parameters: client socket */
215 /**********************************************************************/
216 void bad_request(int client)
217 {
218 char buf[1024];
219
220 sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
221 send(client, buf, sizeof(buf), 0);
222 sprintf(buf, "Content-type: text/html\r\n");
223 send(client, buf, sizeof(buf), 0);
224 sprintf(buf, "\r\n");
225 send(client, buf, sizeof(buf), 0);
226 sprintf(buf, "<P>Your browser sent a bad request, ");
227 send(client, buf, sizeof(buf), 0);
228 sprintf(buf, "such as a POST without a Content-Length.\r\n");
229 send(client, buf, sizeof(buf), 0);
230 }
231
232 /**********************************************************************/
233 /* Put the entire contents of a file out on a socket. This function
234 * is named after the UNIX "cat" command, because it might have been
235 * easier just to do something like pipe, fork, and exec("cat").
236 * Parameters: the client socket descriptor
237 * FILE pointer for the file to cat */
238 /**********************************************************************/
239
240 //得到文件内容,发送
241 void cat(int client, FILE *resource)
242 {
243 char buf[1024];
244
245 fgets(buf, sizeof(buf), resource);
246 //循环读
247 while (!feof(resource))
248 {
249 send(client, buf, strlen(buf), 0);
250 fgets(buf, sizeof(buf), resource);
251 }
252 }
253
254 /**********************************************************************/
255 /* Inform the client that a CGI script could not be executed.
256 * Parameter: the client socket descriptor. */
257 /**********************************************************************/
258 void cannot_execute(int client)
259 {
260 char buf[1024];
261
262 sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
263 send(client, buf, strlen(buf), 0);
264 sprintf(buf, "Content-type: text/html\r\n");
265 send(client, buf, strlen(buf), 0);
266 sprintf(buf, "\r\n");
267 send(client, buf, strlen(buf), 0);
268 sprintf(buf, "<P>Error prohibited CGI execution.\r\n");
269 send(client, buf, strlen(buf), 0);
270 }
271
272 /**********************************************************************/
273 /* Print out an error message with perror() (for system errors; based
274 * on value of errno, which indicates system call errors) and exit the
275 * program indicating an error. */
276 /**********************************************************************/
277 void error_die(const char *sc)
278 {
279 perror(sc);
280 exit(1);
281 }
282
283 /**********************************************************************/
284 /* Execute a CGI script. Will need to set environment variables as
285 * appropriate.
286 * Parameters: client socket descriptor
287 * path to the CGI script */
288 /**********************************************************************/
289 void execute_cgi(int client, const char *path,
290 const char *method, const char *query_string)
291 {
292 //缓冲区
293 char buf[1024];
294
295 //2根管道
296 int cgi_output[2];
297 int cgi_input[2];
298
299 //进程pid和状态
300 pid_t pid;
301 int status;
302
303 int i;
304 char c;
305
306 //读取的字符数
307 int numchars = 1;
308
309 //http的content_length
310 int content_length = -1;
311
312 //默认字符
313 buf[0] = 'A'; buf[1] = '\0';
314
315 //忽略大小写比较字符串
316 if (strcasecmp(method, "GET") == 0)
317 //读取数据,把整个header都读掉,以为Get写死了直接读取index.html,没有必要分析余下的http信息了
318 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
319 numchars = get_line(client, buf, sizeof(buf));
320 else /* POST */
321 {
322 numchars = get_line(client, buf, sizeof(buf));
323 while ((numchars > 0) && strcmp("\n", buf))
324 {
325 //如果是POST请求,就需要得到Content-Length,Content-Length:这个字符串一共长为15位,所以
326 //取出头部一句后,将第16位设置结束符,进行比较
327 //第16位置为结束
328 buf[15] = '\0';
329 if (strcasecmp(buf, "Content-Length:") == 0)
330 //内存从第17位开始就是长度,将17位开始的所有字符串转成整数就是content_length
331 content_length = atoi(&(buf[16])); //获取后面字符的个数
332 numchars = get_line(client, buf, sizeof(buf));
333 }
// 注意到了这里后Post请求头后面的附带信息还没有读出来,要在下面才读取。
334 if (content_length == -1) {
335 bad_request(client);
336 return;
337 }
338 }
339
340 sprintf(buf, "HTTP/1.0 200 OK\r\n");
341 send(client, buf, strlen(buf), 0);
342 //建立output管道
343 if (pipe(cgi_output) < 0) {
344 cannot_execute(client);
345 return;
346 }
347
348 //建立input管道
349 if (pipe(cgi_input) < 0) {
350 cannot_execute(client);
351 return;
352 }
353 // fork后管道都复制了一份,都是一样的
354 // 子进程关闭2个无用的端口,避免浪费
355 // ×<------------------------->1 output
356 // 0<-------------------------->× input
357
358 // 父进程关闭2个无用的端口,避免浪费
359 // 0<-------------------------->× output
360 // ×<------------------------->1 input
361 // 此时父子进程已经可以通信
362
363
364 //fork进程,子进程用于执行CGI
365 //父进程用于收数据以及发送子进程处理的回复数据
366 if ( (pid = fork()) < 0 ) {
367 cannot_execute(client);
368 return;
369 }
370 if (pid == 0) /* child: CGI script */
371 {
372 char meth_env[255];
373 char query_env[255];
374 char length_env[255];
375
376 //子进程输出重定向到output管道的1端
377 dup2(cgi_output[1], 1);
378 //子进程输入重定向到input管道的0端
379 dup2(cgi_input[0], 0);
380
381 //关闭无用管道口
382 close(cgi_output[0]);
383 close(cgi_input[1]);
384
385 //CGI环境变量
386 sprintf(meth_env, "REQUEST_METHOD=%s", method);
387 putenv(meth_env); //putenv保存到环境变量中
388 if (strcasecmp(method, "GET") == 0) {
389 sprintf(query_env, "QUERY_STRING=%s", query_string);
390 putenv(query_env);
391 }
392 else { /* POST */
393 sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
394 putenv(length_env);
395 }
396 //替换执行path
397 execl(path, path, NULL);
398 //int m = execl(path, path, NULL);
399 //如果path有问题,例如将html网页改成可执行的,但是执行后m为-1
400 //退出子进程,管道被破坏,但是父进程还在往里面写东西,触发Program received signal SIGPIPE, Broken pipe.
401 exit(0);
402 } else { /* parent */
403
404 //关闭无用管道口
405 close(cgi_output[1]);
406 close(cgi_input[0]);
407 if (strcasecmp(method, "POST") == 0)
408 for (i = 0; i < content_length; i++) {
409 //得到post请求数据,写到input管道中,供子进程使用
410 recv(client, &c, 1, 0);
411 write(cgi_input[1], &c, 1);
412 }
413 //从output管道读到子进程处理后的信息,然后send出去
414 while (read(cgi_output[0], &c, 1) > 0)
415 send(client, &c, 1, 0);
416
417 //完成操作后关闭管道
418 close(cgi_output[0]);
419 close(cgi_input[1]);
420
421 //等待子进程返回
422 waitpid(pid, &status, 0);
423
424 }
425 }
426
427 /**********************************************************************/
428 /* Get a line from a socket, whether the line ends in a newline,
429 * carriage return, or a CRLF combination. Terminates the string read
430 * with a null character. If no newline indicator is found before the
431 * end of the buffer, the string is terminated with a null. If any of
432 * the above three line terminators is read, the last character of the
433 * string will be a linefeed and the string will be terminated with a
434 * null character.
435 * Parameters: the socket descriptor
436 * the buffer to save the data in
437 * the size of the buffer
438 * Returns: the number of bytes stored (excluding null) */
439 /**********************************************************************/
440
441 //得到一行数据,只要发现c为\n,就认为是一行结束,如果读到\r,再用MSG_PEEK的方式读入一个字符,如果是\n,从socket用读出
442 //如果是下个字符则不处理,将c置为\n,结束。如果读到的数据为0中断,或者小于0,也视为结束,c置为\n
443 int get_line(int sock, char *buf, int size)
444 {
445 int i = 0;
446 char c = '\0';
447 int n;
448
449 while ((i < size - 1) && (c != '\n'))
450 {
451 n = recv(sock, &c, 1, 0);
452 /* DEBUG printf("%02X\n", c); */
453 if (n > 0)
454 {
455 if (c == '\r')
456 {
457 //偷窥一个字节,如果是\n就读走
458 n = recv(sock, &c, 1, MSG_PEEK);
459 /* DEBUG printf("%02X\n", c); */
460 if ((n > 0) && (c == '\n'))
461 recv(sock, &c, 1, 0);
462 else
463 //不是\n(读到下一行的字符)或者没读到,置c为\n 跳出循环,完成一行读取
464 c = '\n';
465 }
466 buf[i] = c;
467 i++;
468 }
469 else
470 c = '\n';
471 }
472 buf[i] = '\0';
473
474 return(i);
475 }
476
477 /**********************************************************************/
478 /* Return the informational HTTP headers about a file. */
479 /* Parameters: the socket to print the headers on
480 * the name of the file */
481 /**********************************************************************/
482
483 //加入http的headers
484 void headers(int client, const char *filename)
485 {
486 char buf[1024];
487 (void)filename; /* could use filename to determine file type */
488
489 strcpy(buf, "HTTP/1.0 200 OK\r\n");
490 send(client, buf, strlen(buf), 0);
491 strcpy(buf, SERVER_STRING);
492 send(client, buf, strlen(buf), 0);
493 sprintf(buf, "Content-Type: text/html\r\n");
494 send(client, buf, strlen(buf), 0);
495 strcpy(buf, "\r\n");
496 send(client, buf, strlen(buf), 0);
497 }
498
499 /**********************************************************************/
500 /* Give a client a 404 not found status message. */
501 /**********************************************************************/
502
503 //如果资源没有找到得返回给客户端下面的信息
504 void not_found(int client)
505 {
506 char buf[1024];
507
508 sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
509 send(client, buf, strlen(buf), 0);
510 sprintf(buf, SERVER_STRING);
511 send(client, buf, strlen(buf), 0);
512 sprintf(buf, "Content-Type: text/html\r\n");
513 send(client, buf, strlen(buf), 0);
514 sprintf(buf, "\r\n");
515 send(client, buf, strlen(buf), 0);
516 sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");
517 send(client, buf, strlen(buf), 0);
518 sprintf(buf, "<BODY><P>The server could not fulfill\r\n");
519 send(client, buf, strlen(buf), 0);
520 sprintf(buf, "your request because the resource specified\r\n");
521 send(client, buf, strlen(buf), 0);
522 sprintf(buf, "is unavailable or nonexistent.\r\n");
523 send(client, buf, strlen(buf), 0);
524 sprintf(buf, "</BODY></HTML>\r\n");
525 send(client, buf, strlen(buf), 0);
526 }
527
528 /**********************************************************************/
529 /* Send a regular file to the client. Use headers, and report
530 * errors to client if they occur.
531 * Parameters: a pointer to a file structure produced from the socket
532 * file descriptor
533 * the name of the file to serve */
534 /**********************************************************************/
535
536 //如果不是CGI文件,直接读取文件返回给请求的http客户端
537 void serve_file(int client, const char *filename)
538 {
539 FILE *resource = NULL;
540 int numchars = 1;
541 char buf[1024];
542
543 //默认字符
544 buf[0] = 'A'; buf[1] = '\0';
545 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
546 numchars = get_line(client, buf, sizeof(buf));
547
548 resource = fopen(filename, "r");
549 if (resource == NULL)
550 not_found(client);
551 else
552 {
553 headers(client, filename);
554 cat(client, resource);
555 }
556 fclose(resource);
557 }
558
559 /**********************************************************************/
560 /* This function starts the process of listening for web connections
561 * on a specified port. If the port is 0, then dynamically allocate a
562 * port and modify the original port variable to reflect the actual
563 * port.
564 * Parameters: pointer to variable containing the port to connect on
565 * Returns: the socket */
566 /**********************************************************************/
567 int startup(u_short *port)
568 {
569 int httpd = 0;
570 struct sockaddr_in name;
571
572 httpd = socket(PF_INET, SOCK_STREAM, 0);
573 if (httpd == -1)
574 error_die("socket");
575 memset(&name, 0, sizeof(name));
576 name.sin_family = AF_INET;
577 name.sin_port = htons(*port);
578 name.sin_addr.s_addr = htonl(INADDR_ANY);
579 //绑定socket
580 if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
581 error_die("bind");
582 //如果端口没有设置,提供个随机端口
583 if (*port == 0) /* if dynamically allocating a port */
584 {
585 socklen_t namelen = sizeof(name);
586 if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
587 error_die("getsockname");
588 *port = ntohs(name.sin_port);
589 }
590 //监听
591 if (listen(httpd, 5) < 0)
592 error_die("listen");
593 return(httpd);
594 }
595
596 /**********************************************************************/
597 /* Inform the client that the requested web method has not been
598 * implemented.
599 * Parameter: the client socket */
600 /**********************************************************************/
601
602 //如果方法没有实现,就返回此信息
603 void unimplemented(int client)
604 {
605 char buf[1024];
606
607 sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
608 send(client, buf, strlen(buf), 0);
609 sprintf(buf, SERVER_STRING);
610 send(client, buf, strlen(buf), 0);
611 sprintf(buf, "Content-Type: text/html\r\n");
612 send(client, buf, strlen(buf), 0);
613 sprintf(buf, "\r\n");
614 send(client, buf, strlen(buf), 0);
615 sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");
616 send(client, buf, strlen(buf), 0);
617 sprintf(buf, "</TITLE></HEAD>\r\n");
618 send(client, buf, strlen(buf), 0);
619 sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");
620 send(client, buf, strlen(buf), 0);
621 sprintf(buf, "</BODY></HTML>\r\n");
622 send(client, buf, strlen(buf), 0);
623 }
624
625 /**********************************************************************/
626
627 int main(void)
628 {
629 int server_sock = -1;
630 u_short port = 0;
631 int client_sock = -1;
632 struct sockaddr_in client_name;
633
634 //这边要为socklen_t类型
635 socklen_t client_name_len = sizeof(client_name);
636 pthread_t newthread;
637
638 server_sock = startup(&port);
639 printf("httpd running on port %d\n", port);
640
641 while (1)
642 {
643 //接受请求,函数原型
644 //#include <sys/types.h>
645 //#include <sys/socket.h>
646 //int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
647 client_sock = accept(server_sock,
648 (struct sockaddr *)&client_name,
649 &client_name_len);
650 if (client_sock == -1)
651 error_die("accept");
652 /* accept_request(client_sock); */
653
654 //每次收到请求,创建一个线程来处理接受到的请求
655 //把client_sock转成地址作为参数传入pthread_create
656 if (pthread_create(&newthread, NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0)
657 perror("pthread_create");
658 }
659
660 close(server_sock);
661
662 return(0);
663 }
知识补充:
1)perror()函数
void perror(const char *s)
perror ( )用 来 将 上 一 个 函 数 发 生 错 误 的 原 因 输 出 到 标 准 设备 (stderr) 。
参数 s 所指的字符串会先打印出,后面再加上错误原因字符串。此错误原因依照全局变量errno 的值来决定要输出的字符串。
在库函数中有个errno变量,每个errno值对应着以字符串表示的错误类型。当你调用"某些"函数出错时,
该函数已经重新设置了errno的值。perror函数只是将你输入的一些信息和现在的errno所对应的错误一起输出。
2)send函数
int send( SOCKET s,const char* buf,int len,int flags);
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
该函数的第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
3)recv函数
int recv( SOCKET s,char* buf,int len,int flags);
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
该函数的第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
同步Socket的recv函数的执行流程:当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,
如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR;
如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,
如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议把数据接收完毕;
当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,
所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。
recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数;
如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
4)struct stat结构体简介
在使用这个结构体和方法时,需要引入:
<sys/types.h>
<sys/stat.h>
struct stat这个结构体是用来描述一个linux系统文件系统中的文件属性的结构。
可以有两种方法来获取一个文件的属性:
1、通过路径:
int stat(const char *path, struct stat *struct_stat);
int lstat(const char *path,struct stat *struct_stat);
两个函数的第一个参数都是文件的路径,第二个参数是struct stat的指针。返回值为0,表示成功执行。
执行失败时,error被自动设置为下面的值:
EBADF: 文件描述词无效
EFAULT: 地址空间不可访问
ELOOP: 遍历路径时遇到太多的符号连接
ENAMETOOLONG:文件路径名太长
ENOENT:路径名的部分组件不存在,或路径名是空字串
ENOMEM:内存不足
ENOTDIR:路径名的部分组件不是目录
这两个方法区别在于stat没有处理字符链接(软链接)的能力,如果一个文件是符号链接,stat会直接返回它所指向的文件的属性;而lstat返回的就是这个符号链接的内容。这里需要说明一下的是软链接和硬链接的含义。我们知道目录在linux中也是一个文件,文件的内容就是这个目录下面所有文件与inode的对应关系。那么所谓的硬链接就是在某一个目录下面将一个文件名与一个inode关联起来,其实就是添加一条记录!而软链接也叫符号链接更加简单了,这个文件的内容就是一个字符串,这个字符串就是它所链接的文件的绝对或者相对地址。
simpleclient.c 代码
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int sockfd;
int len;
struct sockaddr_in address;
int result;
char ch = 'A';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr("127.0.0.1");
address.sin_port = htons(9734);
len = sizeof(address);
result = connect(sockfd, (struct sockaddr *)&address, len);
if (result == -1)
{
perror("oops: client1");
exit(1);
}
write(sockfd, &ch, 1);
read(sockfd, &ch, 1);
printf("char from server = %c\n", ch);
close(sockfd);
exit(0);
}
图片解释tinyhttp流程: