29 |
30 | //C库函数int isspace(int c),检查传递的字符是否是空白。也就是判断是否为空格(' ')、
31 | //定位字符(' \t ')、CR(' \r ')、换行(' \n ')、垂直定位字符(' \v ')或翻页(' \f ')的情况。
32 | //若参数c 为空白字符,则返回非 0,否则返回 0。
33 | #define ISspace(x) isspace((int)(x))
34 |
35 | #define SERVER_STRING "Server: lzx-tiny-httpd/0.1.0\r\n"
36 |
37 | void accept_request(int);
38 | void bad_request(int);
39 | void cat(int, FILE *);
40 | void cannot_execute(int);
41 | void error_die(const char *);
42 | void execute_cgi(int, const char *, const char *, const char *);
43 | int get_line(int, char *, int);
44 | void headers(int, const char *);
45 | void not_found(int);
46 | void serve_file(int, const char *);
47 | int startup(u_short *);
48 | void unimplemented(int);
49 | void readBufferBeforeSend(int);
50 |
51 | /**********************************************************************/
52 | /* A request has caused a call to accept() on the server port to
53 | * return. Process the request appropriately.
54 | * Parameters: the socket connected to the connfd */
55 | /**********************************************************************/
56 | void accept_request(int connfd)
57 | {
58 | char buf[1024]; //缓冲从socket中读取的字节
59 | int numchars; //读取字节数
60 | char method[255]; //请求方法
61 | char url[255]; //请求的url,包括参数
62 | char path[512]; //文件路径,不包括参数
63 | size_t i, j;
64 | struct stat st;
65 | int cgi = 0; /* 如果确定是cgi请求需要把这个变量置为1 */
66 | char *query_string = NULL; //参数
67 |
68 | //从socket中读取一行数据
69 | //这里就是读取请求行(GET /index.html HTTP/1.1),行数据放到buf中,长度返回给numchars
70 | numchars = get_line(connfd, buf, sizeof(buf));
71 |
72 | i = 0; j = 0; //这里使用两个变量i,j因为后边i被重置为0了。j用来保持请求行的seek
73 | while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) //小于method-1是因为最后一位要放\0
74 | {
75 | method[i] = buf[j]; //获取请求方法放入method
76 | i++; j++;
77 | }
78 | method[i] = '\0';
79 |
80 | //如果请求的方法不是 GET 或 POST 任意一个的话就直接发送 response 告诉客户端没实现该方法
81 | //strcasecmp 忽略大小写
82 | if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
83 | {
84 | //清除缓冲区消息头和消息体
85 | readBufferBeforeSend(connfd);
86 |
87 | unimplemented(connfd);
88 |
89 | close(connfd); //TODO 在调用没有实现的方法后没有关闭链接,这里要把connfd关闭
90 |
91 | return;
92 | }
93 |
94 | //如果是 POST 方法就将 cgi 标志变量置一(true)
95 | if (strcasecmp(method, "POST") == 0) {
96 | cgi = 1;
97 | }
98 |
99 | i = 0;
100 | //跳过所有的空白字符
101 | //此时buf里装的是请求行的内容(GET /index.html HTTP/1.1),而j的指针是读完GET之后的位置
102 | //所以跳过空格后获取的就是请求url了
103 | while (ISspace(buf[j]) && (j < sizeof(buf)))
104 | j++;
105 |
106 | //然后把 URL 读出来放到 url 数组中,url结尾要补充\0,所以长度要-1,buf已经存在了,判断长度只是避免越界
107 | while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
108 | {
109 | url[i] = buf[j];
110 | i++; j++;
111 | }
112 | url[i] = '\0';
113 |
114 | //如果这个请求是一个 GET 方法的话
115 | //TODO 对于不是GET方法的请求其实也是需要解析query_string的,
116 | //TODO 否则对于POST:http://10.33.106.82:8008/check.cgi?name=foo 带参数的情况path解析是失败的。
117 | //TODO 但这里只是简单的区分GET和POST请求,这个就不考虑了吧
118 | if (strcasecmp(method, "GET") == 0)
119 | {
120 | //用一个指针指向 url
121 | query_string = url;
122 |
123 | //去遍历这个 url,跳过字符 ?前面的所有字符,如果遍历完毕也没找到字符 ?则退出循环
124 | while ((*query_string != '?') && (*query_string != '\0'))
125 | query_string++;
126 |
127 | //退出循环后检查当前的字符是 ?还是字符串(url)的结尾
128 | //因为如果存在?的话经过上边的循环query_string的指针正好在?处,如果没有?的话query_string指针正好在url字符串的结尾\0处
129 | if (*query_string == '?')
130 | {
131 | //如果是 ? 的话,证明这个请求需要调用 cgi,将cgi标志变量置一(true),因为请求静态文件不用参数的。
132 | cgi = 1;
133 | //从字符 ? 处把字符串 url 给分隔为两份
134 | *query_string = '\0'; //把url的?改为\0,这样url只是?的前边部分了。
135 | //使指针指向字符 ?后面的那个字符
136 | query_string++;
137 | }
138 | }
139 |
140 | //将url字符串(只包含url,参数已经被截断了),拼接在字符串htdocs的后面之后就输出存储到path中。
141 | //因为url的第一个字符是/,所以不用加/
142 | sprintf(path, "htdocs%s", url);
143 |
144 | //如果 path 数组中的这个字符串的最后一个字符是以字符 / 结尾的话,就拼接上一个"index.html"的字符串。首页的意思
145 | if (path[strlen(path) - 1] == '/') {
146 | strcat(path, "index.html");
147 | }
148 |
149 | //在系统上去查询该文件是否存在
150 | //int stat(const char * file_name, struct stat *buf);
151 | //stat()用来将参数file_name 所指的文件状态, 复制到参数buf 所指的结构中。执行成功则返回0,失败返回-1,错误代码存于errno。
152 | if (stat(path, &st) == -1) {
153 | //如果不存在,那把这次 http 的请求后续的内容(head 和 body)全部读完并忽略
154 | readBufferBeforeSend(connfd);
155 |
156 | //返回方法不存在
157 | not_found(connfd);
158 | } else {
159 | //文件存在,那去跟常量S_IFMT相与,相与之后的值可以用来判断该文件是什么类型的
160 | if ((st.st_mode & S_IFMT) == S_IFDIR)
161 | //如果这个文件是个目录,那就需要再在 path 后面拼接一个"/index.html"的字符串
162 | //注意此时访问需要http://10.33.106.82:8008/static/,后边不带/不能识别为目录
163 | strcat(path, "/index.html");
164 |
165 | //S_IXUSR:用户可以执行
166 | // S_IXGRP:组可以执行
167 | // S_IXOTH:其它人可以执行
168 | //如果这个文件是一个可执行文件,不论是属于用户/组/其他这三者类型的,就将 cgi 标志变量置一
169 | if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)) {
170 | cgi = 1;
171 | }
172 |
173 | if (!cgi) {
174 | //静态解析
175 | serve_file(connfd, path);
176 | } else {
177 | //CGI解析
178 | execute_cgi(connfd, path, method, query_string);
179 | }
180 | }
181 |
182 | close(connfd);
183 | }
184 |
185 | /**********************************************************************/
186 | /* Inform the connfd that a request it has made has a problem.
187 | * Parameters: connfd socket */
188 | /**********************************************************************/
189 | void bad_request(int connfd)
190 | {
191 | char buf[1024];
192 |
193 | sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");
194 | send(connfd, buf, sizeof(buf), 0);
195 | sprintf(buf, "Content-type: text/html\r\n");
196 | send(connfd, buf, sizeof(buf), 0);
197 | sprintf(buf, "\r\n");
198 | send(connfd, buf, sizeof(buf), 0);
199 | sprintf(buf, "Your browser sent a bad request, ");
200 | send(connfd, buf, sizeof(buf), 0);
201 | sprintf(buf, "such as a POST without a Content-Length.\r\n");
202 | send(connfd, buf, sizeof(buf), 0);
203 | }
204 |
205 | /**********************************************************************/
206 | /* Put the entire contents of a file out on a socket. This function
207 | * is named after the UNIX "cat" command, because it might have been
208 | * easier just to do something like pipe, fork, and exec("cat").
209 | * Parameters: the connfd socket descriptor
210 | * FILE pointer for the file to cat */
211 | /**********************************************************************/
212 | void cat(int connfd, FILE *resource)
213 | {
214 | char buf[1024];
215 |
216 | //从文件描述符中读取指定内容
217 | fgets(buf, sizeof(buf), resource);
218 | while (!feof(resource))
219 | {
220 | send(connfd, buf, strlen(buf), 0);
221 | fgets(buf, sizeof(buf), resource);
222 | }
223 | }
224 |
225 | /**********************************************************************/
226 | /* Inform the connfd that a CGI script could not be executed.
227 | * Parameter: the connfd socket descriptor. */
228 | /**********************************************************************/
229 | void cannot_execute(int connfd)
230 | {
231 | char buf[1024];
232 |
233 | sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");
234 | send(connfd, buf, strlen(buf), 0);
235 | sprintf(buf, "Content-type: text/html\r\n");
236 | send(connfd, buf, strlen(buf), 0);
237 | sprintf(buf, "\r\n");
238 | send(connfd, buf, strlen(buf), 0);
239 | sprintf(buf, "
Error prohibited CGI execution.\r\n");
240 | send(connfd, buf, strlen(buf), 0);
241 | }
242 |
243 | /**********************************************************************/
244 | /* Print out an error message with perror() (for system errors; based
245 | * on value of errno, which indicates system call errors) and exit the
246 | * program indicating an error. */
247 | /**********************************************************************/
248 | void error_die(const char *sc)
249 | {
250 | // #include ,void perror(char *string);
251 | // 在输出错误信息前加上字符串sc:
252 | perror(sc);
253 | exit(1);
254 | }
255 |
256 | /**********************************************************************/
257 | /* 执行一个cgi脚本,设置一些环境变量
258 |
259 | * Parameters: connfd socket descriptor
260 | * path to the CGI script */
261 | /**********************************************************************/
262 | void execute_cgi(int connfd, const char *path, const char *method, const char *query_string)
263 | {
264 | char buf[1024];
265 | int cgi_output[2]; //重定向输出的管道
266 | int cgi_input[2]; //重定向输入的管道
267 | pid_t pid;
268 | int status;
269 | int i;
270 | char c;
271 | int numchars = 1;
272 | int content_length = -1;
273 |
274 | buf[0] = 'A'; buf[1] = '\0';
275 |
276 | //如果是 http 请求是 GET 方法的话读取并忽略请求剩下的内容
277 | if (strcasecmp(method, "GET") == 0) {
278 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
279 | numchars = get_line(connfd, buf, sizeof(buf));
280 | } else {
281 | //只有 POST 方法才继续读内容
282 | numchars = get_line(connfd, buf, sizeof(buf));
283 | //这个循环的目的是读出指示 body 长度大小的参数,并记录 body 的长度大小。其余的 header 里面的参数一律忽略
284 | //注意这里只读完 header 的内容,body 的内容没有读
285 | while ((numchars > 0) && strcmp("\n", buf))
286 | {
287 | buf[15] = '\0';
288 | if (strcasecmp(buf, "Content-Length:") == 0)
289 | content_length = atoi(&(buf[16])); //记录 body 的长度大小
290 | numchars = get_line(connfd, buf, sizeof(buf));
291 | }
292 |
293 | //如果 http 请求的 header 没有指示 body 长度大小的参数,则报错返回
294 | if (content_length == -1) {
295 | bad_request(connfd);
296 | return;
297 | }
298 | }
299 |
300 | sprintf(buf, "HTTP/1.0 200 OK\r\n");
301 | send(connfd, buf, strlen(buf), 0);
302 |
303 | //下面这里创建两个管道,用于两个进程间通信
304 | //管道是一种进程间通信的方法,但是管道是半双工的,就是流只能向一个方向流动,
305 | //所以如果想把一个进程的输入和输出都重定向的话,则需要建立两条管道,并且需要关闭不需要的管道的读端或者写端
306 | if (pipe(cgi_output) < 0) {
307 | cannot_execute(connfd);
308 | return;
309 | }
310 | if (pipe(cgi_input) < 0) {
311 | cannot_execute(connfd);
312 | return;
313 | }
314 |
315 | //创建一个子进程
316 | if ( (pid = fork()) < 0 ) {
317 | cannot_execute(connfd);
318 | return;
319 | }
320 |
321 | //子进程用来执行 cgi 脚本
322 | if (pid == 0)
323 | {
324 | /* child: CGI script */
325 | char meth_env[255];
326 | char query_env[255];
327 | char length_env[255];
328 |
329 | //将子进程的输出由标准输出重定向到 cgi_ouput 的管道写端上
330 | dup2(cgi_output[1], 1);
331 | //将子进程的输入由标准输入重定向到 cgi_input 的管道读端上
332 | dup2(cgi_input[0], 0);
333 | //关闭 cgi_ouput 管道的读端与cgi_input 管道的写端
334 | close(cgi_output[0]);
335 | close(cgi_input[1]);
336 |
337 | //构造一个环境变量
338 | sprintf(meth_env, "REQUEST_METHOD=%s", method);
339 |
340 | //将这个环境变量加进子进程的运行环境中
341 | putenv(meth_env);
342 |
343 | //根据http 请求的不同方法,构造并存储不同的环境变量
344 | if (strcasecmp(method, "GET") == 0) {
345 | sprintf(query_env, "QUERY_STRING=%s", query_string);
346 | putenv(query_env);
347 | } else {
348 | /* POST */
349 | sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
350 | putenv(length_env);
351 | }
352 |
353 | execl(path, path, NULL);
354 | exit(0);
355 |
356 | } else { /* parent */
357 | //父进程则关闭了 cgi_output管道的写端和 cgi_input 管道的读端
358 | close(cgi_output[1]);
359 | close(cgi_input[0]);
360 |
361 | //如果是 POST 方法的话就继续读 body 的内容,并写到 cgi_input 管道里让子进程去读
362 | if (strcasecmp(method, "POST") == 0) {
363 |
364 | for (i = 0; i < content_length; i++) {
365 | recv(connfd, &c, 1, 0);
366 | write(cgi_input[1], &c, 1);
367 | }
368 | }
369 |
370 | //然后从 cgi_output 管道中读子进程的输出,并发送到客户端去
371 | while (read(cgi_output[0], &c, 1) > 0)
372 | send(connfd, &c, 1, 0);
373 |
374 | //关闭管道
375 | close(cgi_output[0]);
376 | close(cgi_input[1]);
377 | //等待子进程的退出
378 | waitpid(pid, &status, 0);
379 | }
380 | }
381 |
382 | /**********************************************************************/
383 | /* 从socket读取一行。
384 | * 如果行以换行(\n)或者回车(\r)或者CRLF(\r\n)做为结束,就使用'\0'停止字符串读取
385 | * 如果buffer读取完都没有发现换行符,使用'\0'结束字符串
386 | * 如果上边三个任意一个行终止符被读出,都会被替换为\n,并且在末尾补充'\0'
387 | * 意思就是不管换行符是\r还是\n还是\r\n,都会被替换为\n\0
388 | *
389 | * 关于回车和换行:
390 | * '\r'是回车,使光标到行首,(carriage return)
391 | * '\n'是换行,使光标下移一格,(line feed / newline)
392 | * '\r\n'就是CRLF啦
393 | *
394 | * Parameters: the socket descriptor
395 | * the buffer to save the data in
396 | * the size of the buffer
397 | * Returns: the number of bytes stored (excluding null) */
398 | /**********************************************************************/
399 | int get_line(int sock, char *buf, int size)
400 | {
401 | int i = 0;
402 | char c = '\0'; //补充到结尾的字符
403 | int n;
404 |
405 | while ((i < size - 1) && (c != '\n')) //因为字符串的最后一位要使用'\0'结束,所以size需要 -1
406 | {
407 | //从sock中一次读一个字符,循环读
408 | //int recv(int s, void *buf, int len, unsigned int flags);
409 | //recv()用来接收远端主机经指定的socket传来的数据, 并把数据存到由参数buf指向的内存空间, 参数len为可接收数据的最大长度.
410 | n = recv(sock, &c, 1, 0);
411 | if (n > 0) {
412 | if (c == '\r') { //这个if里边处理了\r和\r\n结尾的情况,把c统一改为\n,如果是\n结尾那无需处理直接就赋值给buf啦
413 |
414 | //参数 flags 一般设0,MSG_PEEK表示返回来的数据并不会在系统内删除, 如果再调用recv()会返回相同的数据内容.
415 | //这个选项用于测试下一个字符是不是\n,并不会把当前读出的字符从缓冲区中删掉
416 | n = recv(sock, &c, 1, MSG_PEEK);
417 | /* DEBUG printf("%02X\n", c); */
418 | if ((n > 0) && (c == '\n')) //如果是\n,就把下个字符读取出来,如果不是\n就把\r改为\n
419 | recv(sock, &c, 1, 0);
420 | else
421 | c = '\n';
422 | }
423 |
424 | buf[i] = c;
425 | i++;
426 | } else {
427 | //读取失败直接赋值\n
428 | c = '\n';
429 | }
430 | }
431 |
432 | //读取完一行数据后在获得的字符串末尾补上\0
433 | buf[i] = '\0';
434 |
435 | return (i);
436 | }
437 |
438 | /**
439 | * 处理请求方法不是GET和POST的情况
440 | * @param connfd [description]
441 | */
442 | void readBufferBeforeSend(int connfd)
443 | {
444 | int numchars = 1;
445 | char buf[1024];
446 |
447 | int content_length = -1; //post要读取的长度
448 | int i; //for循环准备
449 | char c; //for循环读取消息体准备
450 |
451 | buf[0] = 'A'; buf[1] = '\0';
452 |
453 | //对于GET和HEAD方法,只有请求头部分,不会发送请求体
454 | //注意HEAD方法只会返回请求头,不返回请求体,页面上看不到输出是正常的,只要返回状态码正确就行了。
455 | //除了GET和HEAD其他方法都会发送请求体,这里先读出请求头,根据Content-Length判断是否有请求体
456 |
457 | //strcmp若参数s1 和s2 字符串相同则返回0。s1 若大于s2 则返回大于0 的值。s1 若小于s2 则返回小于0 的值。
458 | //完全等于"\n"代表当前行是消息头和消息体之前的那个空行
459 | while ((numchars > 0) && strcmp("\n", buf))
460 | {
461 | buf[15] = '\0';
462 | if (strcasecmp(buf, "Content-Length:") == 0) {
463 | content_length = atoi(&(buf[16])); //把ascii码转为整形,http协议传输的是ascii码
464 | }
465 | numchars = get_line(connfd, buf, sizeof(buf));
466 | }
467 |
468 | //读出消息体并忽略,这里不能多读也不能少读
469 | if(content_length != -1){
470 | for (i = 0; i < content_length; i++) {
471 | recv(connfd, &c, 1, 0);
472 | }
473 | }
474 | }
475 |
476 | /**********************************************************************/
477 | /* Return the informational HTTP headers about a file. */
478 | /* Parameters: the socket to print the headers on
479 | * the name of the file */
480 | /**********************************************************************/
481 | void headers(int connfd, const char *filename)
482 | {
483 | char buf[1024];
484 | (void)filename; /* could use filename to determine file type */
485 |
486 | strcpy(buf, "HTTP/1.0 200 OK\r\n");
487 | send(connfd, buf, strlen(buf), 0);
488 | strcpy(buf, SERVER_STRING);
489 | send(connfd, buf, strlen(buf), 0);
490 | sprintf(buf, "Content-Type: text/html\r\n");
491 | send(connfd, buf, strlen(buf), 0);
492 | strcpy(buf, "\r\n");
493 | send(connfd, buf, strlen(buf), 0);
494 | }
495 |
496 | /**********************************************************************/
497 | /* Give a connfd a 404 not found status message. */
498 | /**********************************************************************/
499 | void not_found(int connfd)
500 | {
501 | char buf[1024];
502 |
503 | sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");
504 | send(connfd, buf, strlen(buf), 0);
505 | sprintf(buf, SERVER_STRING);
506 | send(connfd, buf, strlen(buf), 0);
507 | sprintf(buf, "Content-Type: text/html\r\n");
508 | send(connfd, buf, strlen(buf), 0);
509 | sprintf(buf, "\r\n");
510 | send(connfd, buf, strlen(buf), 0);
511 | sprintf(buf, "Not Found\r\n");
512 | send(connfd, buf, strlen(buf), 0);
513 | sprintf(buf, "The server could not fulfill\r\n");
514 | send(connfd, buf, strlen(buf), 0);
515 | sprintf(buf, "your request because the resource specified\r\n");
516 | send(connfd, buf, strlen(buf), 0);
517 | sprintf(buf, "is unavailable or nonexistent.\r\n");
518 | send(connfd, buf, strlen(buf), 0);
519 | sprintf(buf, "\r\n");
520 | send(connfd, buf, strlen(buf), 0);
521 | }
522 |
523 | /**********************************************************************/
524 | /* Send a regular file to the connfd. Use headers, and report
525 | * errors to connfd if they occur.
526 | * Parameters: a pointer to a file structure produced from the socket
527 | * file descriptor
528 | * the name of the file to serve */
529 | /**********************************************************************/
530 | void serve_file(int connfd, const char *filename)
531 | {
532 | FILE *resource = NULL;
533 | int numchars = 1;
534 | char buf[1024];
535 |
536 | buf[0] = 'A'; buf[1] = '\0';
537 |
538 | //循环读出请求头内容并忽略,这里也可以调用我添加的函数readBufferBeforeSend完成.
539 | while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */
540 | numchars = get_line(connfd, buf, sizeof(buf));
541 |
542 | //打开这个传进来的这个路径所指的文件
543 | resource = fopen(filename, "r");
544 | if (resource == NULL) {
545 | not_found(connfd);
546 | } else {
547 | //打开成功后,将这个文件的基本信息封装成 response 的头部(header)并返回
548 | headers(connfd, filename);
549 | //接着把这个文件的内容读出来作为 response 的 body 发送到客户端
550 | cat(connfd, resource);
551 | }
552 |
553 | fclose(resource);
554 | }
555 |
556 | /**********************************************************************/
557 | /* 这个函数在指定的端口上启动一个监听进程,如果端口是0,就动态分配一个
558 | * 并把值赋给变量port
559 | *
560 | * Parameters: pointer to variable containing the port to connect on
561 | * Returns: the socket */
562 | /**********************************************************************/
563 | int startup(u_short *port)
564 | {
565 | int httpd = 0;
566 | struct sockaddr_in name;
567 |
568 | //建立TCP SOCKET,PF_INET等同于AF_INET
569 | httpd = socket(PF_INET, SOCK_STREAM, 0);
570 | if (httpd == -1)
571 | error_die("socket");
572 |
573 | //在修改源码后重启启动总是提示bind: Address already in use,使用tcpreuse解决
574 | int reuse = 1;
575 | if (setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) {
576 | error_die("setsockopet");
577 | }
578 |
579 | memset(&name, 0, sizeof(name));
580 | name.sin_family = AF_INET;
581 |
582 | //,将*port 转换成以网络字节序表示的16位整数
583 | //这里port已被我修改为8008,如果为0的话内核会在bind时自动分配一个端口号
584 | name.sin_port = htons(*port);
585 | name.sin_addr.s_addr = htonl(INADDR_ANY);
586 |
587 | if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
588 | error_die("bind");
589 |
590 | //这里是原来的代码逻辑,如果port是0的话通过bind后内核会随机分配一个端口号给当前的socket连接,
591 | //获取这个端口号给port变量
592 | if (*port == 0) /* if dynamically allocating a port */
593 | {
594 | int namelen = sizeof(name);
595 | if (getsockname(httpd, (struct sockaddr *)&name, (socklen_t *)&namelen) == -1)
596 | error_die("getsockname");
597 | *port = ntohs(name.sin_port);
598 | }
599 |
600 |
601 | //启动监听,backlog为5
602 | if (listen(httpd, 5) < 0)
603 | error_die("listen");
604 | return (httpd);
605 | }
606 |
607 | /**********************************************************************/
608 | /* 通知connfd请求的web方法没有实现 */
609 | /**********************************************************************/
610 | void unimplemented(int connfd)
611 | {
612 | char buf[1024];
613 |
614 | sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");
615 | send(connfd, buf, strlen(buf), 0);
616 | sprintf(buf, SERVER_STRING);
617 | send(connfd, buf, strlen(buf), 0);
618 | sprintf(buf, "Content-Type: text/html\r\n");
619 | send(connfd, buf, strlen(buf), 0);
620 | sprintf(buf, "\r\n");
621 | send(connfd, buf, strlen(buf), 0);
622 | sprintf(buf, "Method Not Implemented\r\n");
623 | send(connfd, buf, strlen(buf), 0);
624 | sprintf(buf, "\r\n");
625 | send(connfd, buf, strlen(buf), 0);
626 | sprintf(buf, "HTTP request method not supported.\r\n");
627 | send(connfd, buf, strlen(buf), 0);
628 | sprintf(buf, "\r\n");
629 | send(connfd, buf, strlen(buf), 0);
630 | }
631 |
632 | /**********************************************************************/
633 |
634 | int main(void)
635 | {
636 | int listenfd = -1;
637 | u_short port = 8008;
638 | int connfd = -1;
639 |
640 | struct sockaddr_in client;
641 | int client_len = sizeof(client);
642 |
643 | //pthread_t newthread;
644 |
645 | //绑定监听端口
646 | listenfd = startup(&port);
647 | printf("httpd running on port %d\n", port);
648 |
649 | while (1)
650 | {
651 | //loop waiting for client connection
652 | connfd = accept(listenfd, (struct sockaddr *)&client, (socklen_t *)&client_len);
653 | if (connfd == -1) {
654 | error_die("accept");
655 | }
656 |
657 | //处理请求
658 | accept_request(connfd);
659 |
660 | /*if (pthread_create(&newthread , NULL, accept_request, connfd_sock) != 0)
661 | perror("pthread_create");*/
662 | }
663 |
664 | //关闭监听描述符,注意在这之前需要关闭close(connfd)
665 | close(listenfd);
666 |
667 | return (0);
668 | }
669 |
--------------------------------------------------------------------------------
/simpleclient.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include //自己加的,exit函数需要
8 |
9 | int main(int argc, char *argv[])
10 | {
11 | int sockfd;
12 | int len;
13 | struct sockaddr_in address;
14 | int result;
15 | char ch = 'A';
16 |
17 | //申请一个流 socket
18 | sockfd = socket(AF_INET, SOCK_STREAM, 0);
19 | //填充地址结构,指定服务器的 IP 和 端口
20 | address.sin_family = AF_INET;
21 | //inet_addr 可以参考 man inet_addr
22 | //可以用现代的inet_pton()替代inet_addr(), example 中有参考例子
23 | address.sin_addr.s_addr = inet_addr("127.0.0.1");
24 | address.sin_port = htons(8008);
25 | len = sizeof(address);
26 |
27 | //下面的语句可以输出连接的 IP 地址
28 | //但是inet_ntoa()是过时的方法,应该改用 inet_ntop(可参考 example)。但很多代码仍然遗留着inet_ntoa.
29 | //printf("%s\n", inet_ntoa( address.sin_addr));
30 |
31 | result = connect(sockfd, (struct sockaddr *)&address, len);
32 |
33 | if (result == -1)
34 | {
35 | perror("oops: client1");
36 | exit(1);
37 | }
38 |
39 | //往服务端写一个字节
40 | write(sockfd, &ch, 1);
41 | //从服务端读一个字符
42 | read(sockfd, &ch, 1);
43 | printf("char from server = %c\n", ch);
44 | close(sockfd);
45 | exit(0);
46 | }
47 |
--------------------------------------------------------------------------------