tinyhttpd源码详解 转载自 技术菌的blog

tinyhttpd是一轻量级的web 服务器,最近几天终于抽出时间来研究研究了。其源码百度即可下载,500多行,确实是学习linux编程的好材料。很多网友都写了关于tinyhttpd的博文,但是我还是觉得不够深入,严格说是写得不够深入,往往就是把500多行代码一扔,分析下主要过程,画个流程图就完事了。我怎么觉得还有很多东西可以挖一挖呢,也许还可再调整一下代码,虽然目前也不清楚可调整多少,待我细细道来。

我分析的过程就按主要路线走,也就是这样一个主干道流程:服务器创建socket并监听某一端口->浏览器输入url发出请求->服务器收到请求,创建线程处理请求,主线程继续等待->新线程读取http请求,并解析相关字段,读取文件内容或者执行CGI程序并返回给浏览器->关闭客户端套接字,新线程退出

咱们先来看看main函数

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. int main(void)  
  2. {  
  3.  int server_sock = -1;  
  4.  u_short port = 0;  
  5.  int client_sock = -1;  
  6.  struct sockaddr_in client_name;  
  7.  int client_name_len = sizeof(client_name);  
  8.  pthread_t newthread;  
  9.   
  10.  server_sock = startup(&port);  
  11.  printf("httpd running on port %d\n", port);  
  12.   
  13.  while (1)  
  14.  {  
  15.   client_sock = accept(server_sock,  
  16.                        (struct sockaddr *)&client_name,  
  17.                        &client_name_len);  
  18.   if (client_sock == -1)  
  19.    error_die("accept");  
  20.  if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)  
  21.    perror("pthread_create");  
  22.  }  
  23.   
  24.  close(server_sock);  
  25.   
  26.  return(0);  
  27. }  

这段代码,只要是稍微了解linux的网络编程就很好懂,创建服务端socket,绑定、监听、等待客户端连接。只不过作者把这些步骤都放在了一个叫startup的函数里。那来看startup

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. int startup(u_short *port)  
  2. {  
  3.  int httpd = 0;  
  4.  struct sockaddr_in name;  
  5.   
  6.  httpd = socket(PF_INET, SOCK_STREAM, 0);  
  7.  if (httpd == -1)  
  8.   error_die("socket");  
  9.  memset(&name, 0, sizeof(name));//也可以用bzero  
  10.  name.sin_family = AF_INET;  
  11.  name.sin_port = htons(*port);  
  12.  name.sin_addr.s_addr = htonl(INADDR_ANY);//任何网络接口  
  13.  if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)  
  14.   error_die("bind");  
  15.  if (*port == 0)  /* if dynamically allocating a port */  
  16.  {  
  17.   int namelen = sizeof(name);  
  18.   if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)  
  19.    error_die("getsockname");  
  20.   *port = ntohs(name.sin_port);//系统动态分配一个端口号  
  21.  }  
  22.  if (listen(httpd, 5) < 0)  
  23.   error_die("listen");  
  24.  return(httpd);//返回服务套接字描述符  
  25. }  
很常见的步骤,就不多说了。

此后,服务端就accept等待连接,作者其实没有关心客户端来自哪里,那accept的第二、第三参数完全可以为NULL。接着就创建线程把客户端套接字作为参数传过去了,由新线程处理请求,这是服务器编程的常用手段,提高并发性。注意这里的线程函数并不完全合法,至少在linux上就不符合线程函数的原型定义,编译时编译器也只是警告而未报错。

接下来重点就在线程函数accept_request上了

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. void accept_request(int client)  
  2. {  
  3.  char buf[1024];  
  4.  int numchars;  
  5.  char method[255];  
  6.  char url[255];  
  7.  char path[512];  
  8.  size_t i, j;  
  9.  struct stat st;  
  10.  int cgi = 0;      /* becomes true if server decides this is a CGI 
  11.                     * program */  
  12.  char *query_string = NULL;  
  13.   
  14.  numchars = get_line(client, buf, sizeof(buf));  
  15.  i = 0; j = 0;  
  16.  while (!ISspace(buf[j]) && (i < sizeof(method) - 1))  
  17.  {  
  18.   method[i] = buf[j];  
  19.   i++; j++;  
  20.  }  
  21.  method[i] = '\0';  
  22.   
  23.  if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))  
  24.  {  
  25.   unimplemented(client);  
  26.   return;  
  27.  }  
  28.   
  29.  if (strcasecmp(method, "POST") == 0)  
  30.   cgi = 1;  
  31.   
  32.  i = 0;  
  33.  while (ISspace(buf[j]) && (j < sizeof(buf)))  
  34.   j++;  
  35.  while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))  
  36.  {  
  37.   url[i] = buf[j];  
  38.   i++; j++;  
  39.  }  
  40.  url[i] = '\0';  
  41.   
  42.  if (strcasecmp(method, "GET") == 0)  
  43.  {  
  44.   query_string = url;  
  45.   while ((*query_string != '?') && (*query_string != '\0'))  
  46.    query_string++;  
  47.   if (*query_string == '?')  
  48.   {  
  49.    cgi = 1;  
  50.    *query_string = '\0';  
  51.    query_string++;  
  52.   }  
  53.  }  
  54.   
  55.  sprintf(path, "htdocs%s", url);  
  56.  if (path[strlen(path) - 1] == '/')  
  57.   strcat(path, "index.html");  
  58.  if (stat(path, &st) == -1) {  
  59.   while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */  
  60.    numchars = get_line(client, buf, sizeof(buf));  
  61.   not_found(client);  
  62.  }  
  63.  else  
  64.  {  
  65.   if ((st.st_mode & S_IFMT) == S_IFDIR)  
  66.    strcat(path, "/index.html");  
  67.   if ((st.st_mode & S_IXUSR) ||  
  68.       (st.st_mode & S_IXGRP) ||  
  69.       (st.st_mode & S_IXOTH)    )  
  70.    cgi = 1;  
  71.   if (!cgi)  
  72.    serve_file(client, path);  
  73.   else  
  74.    execute_cgi(client, path, method, query_string);  
  75.  }  
  76.   
  77.  close(client);  
  78. }  

首先很关键一点要理解get_line的意思。我们要知道当在浏览器中输入url后enter之后,它发给服务器是文本型的字符串,遵循http请求格式,类似下面的:

GET / HTTP/1.1

HOST:www.abc.com

Content-type:text/html

...

get_line干的事就是读取一行,并且不管原来是以\n还是\r\n结束,均转化为以\n再加\0字符结束。其实现如下:

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. int get_line(int sock, char *buf, int size)  
  2. {  
  3.  int i = 0;  
  4.  char c = '\0';  
  5.  int n;  
  6.   
  7.  while ((i < size - 1) && (c != '\n'))  
  8.  {  
  9.   n = recv(sock, &c, 1, 0);//从sock中一次读一个字符,循环读  
  10.   if (n > 0)  
  11.   {  
  12.    if (c == '\r'//如果读到回车,一般紧接着字符就是\n  
  13.    {  
  14.     n = recv(sock, &c, 1, MSG_PEEK);  
  15.     if ((n > 0) && (c == '\n'))  
  16.      recv(sock, &c, 1, 0);//这时再读,c还是\n,循环跳出  
  17.     else  
  18.      c = '\n';  
  19.    }  
  20.    buf[i] = c;  
  21.    i++;  
  22.   }  
  23.   else  
  24.    c = '\n';  
  25.  }  
  26.  buf[i] = '\0';  
  27.    
  28.  return(i);//返回读取的字符数  
  29. }  

get_line完后,就是开始解析第一行,判断是GET方法还是POST方法,目前只支持这两种。如果是POST,还是把cgi置1,表明要运行CGI程序;如果是GET方法且附带以?开头的参数时,也认为是执行CGI程序

还是获取要访问的url,可以是很常见的/,/index.html等等。该程序默认为根目录是在htdocs下的,且默认文件是index.html。另外还判断了给定文件是否有可执权限,如果有,则认为是CGI程序。最后根据变量cgi的值来进行相应选择:读取静态文件或者执行CGI程序返回结果。

我们首先看看最简单的静态文件情况,调用函数serve_file

[cpp]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. void serve_file(int client, const char *filename)  
  2. {  
  3.  FILE *resource = NULL;  
  4.  int numchars = 1;  
  5.  char buf[1024];  
  6.   
  7.  buf[0] = 'A'; buf[1] = '\0';  
  8.  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */  
  9.   numchars = get_line(client, buf, sizeof(buf));//必须要读完客户端发来的头部,否则后来的send不能正常显示在浏览器中。  
  10.   
  11.  resource = fopen(filename, "r");  
  12.  if (resource == NULL)  
  13.   not_found(client);  
  14.  else  
  15.  {  
  16.   headers(client, filename);  
  17.   cat(client, resource);  
  18.  }  
  19.  fclose(resource);  
  20. }  

将文件名作为参数,首先读完客户端的头部,然后打开创建文件流。为了模拟http响应,首先向客户端发送头部,头部信息至少包含以下几点:

http/1.0 200 ok

server:

content-type:

\r\n(一个空白行,标识头部结束)

最后发送数据体部分,即文件内容,在cat方法中,fgets每读入一行,就send,直到末尾。headers和cat函数就不在这里列出了。下面,我们来看看一个具体测试例子,紧接着在gdb中调试

我在根目录下的htdocs下建立一个新文件index2.html,内容如下:

<a href="http://10.108.222.96:54205/test.sh">Display Date</a>

我在这里放了一个链接,href部分是关于cgi的,先不管,就只看文本部分能否显示在浏览器中。

首先编译之后直接运行./httpd,程序打印"httpd running on port 53079"

我们在浏览器中访问index2.html文件,如下图所示:


文本能正确显示了。那如何在gdb中调试观察呢?

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. xiaoqiang@ljq-Lenovo:~/chenshi/tinyhttpd-0.1.0$ gdb attach 7029  【通过ps查看httpd进程的PID,然后gdb attach之】  
  2. Attaching to process 7029  
  3. Reading symbols from /home/xiaoqiang/chenshi/tinyhttpd-0.1.0/httpd...done.  
  4. Reading symbols from /lib/i386-linux-gnu/libpthread.so.0...(no debugging symbols found)...done.  
  5. [Thread debugging using libthread_db enabled]  
  6. Using host libthread_db library "/lib/i386-linux-gnu/libthread_db.so.1".  
  7. Loaded symbols for /lib/i386-linux-gnu/libpthread.so.0  
  8. Reading symbols from /lib/i386-linux-gnu/libc.so.6...(no debugging symbols found)...done.  
  9. Loaded symbols for /lib/i386-linux-gnu/libc.so.6  
  10. Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.  
  11. Loaded symbols for /lib/ld-linux.so.2  
  12. 0xb7750424 in __kernel_vsyscall ()  
  13. (gdb) bt  
  14. #0  0xb7750424 in __kernel_vsyscall ()  
  15. #1  0xb772dc08 in accept () from /lib/i386-linux-gnu/libpthread.so.0  
  16. #2  0x0804a8d6 in main () at httpd.c:516  
  17. (gdb) l accept_request  
  18. warning: Source file is more recent than executable.  
  19. 47  /* A request has caused a call to accept() on the server port to  
  20. 48   * return.  Process the request appropriately.  
  21. 49   * Parameters: the socket connected to the client */  
  22. 50  /**********************************************************************/  
  23. 51  void accept_request(int client)  
  24. 52  {  
  25. 53   char buf[1024];  
  26. 54   int numchars;  
  27. 55   char method[255];  
  28. 56   char url[255];  
  29. (gdb) l  
  30. 57   char path[512];  
  31. 58   size_t i, j;  
  32. 59   struct stat st;  
  33. 60   int cgi = 0;      /* becomes true if server decides this is a CGI  
  34. 61                      * program */  
  35. 62   char *query_string = NULL;  
  36. 63    
  37. 64   numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行  
  38. 65   i = 0; j = 0;  
  39. 66   while (!ISspace(buf[j]) && (i < sizeof(method) - 1))  
  40. (gdb) b 64 【在64行设置断点,观察读到的是什么】  
  41. Breakpoint 1 at 0x8048b3f: file httpd.c, line 64.  
  42. (gdb) c  
  43. Continuing. 【直到在浏览器中发起了请求,后面的才会打印出来】  
  44. [New Thread 0xb63feb40 (LWP 7655)]  
  45. [Switching to Thread 0xb63feb40 (LWP 7655)]  
  46.   
  47. Breakpoint 1, accept_request (client=4) at httpd.c:64  
  48. 64   numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行  
  49. (gdb) n  
  50. 65   i = 0; j = 0;  
  51. (gdb) p buf 【打印读到的一行】  
  52. $1 = "GET /index2.html HTTP/1.1\n", '\000' <repeats 997 times> 【果真是HTTP GET请求的第一行】  
  53. (gdb) l  
  54. 60   int cgi = 0;      /* becomes true if server decides this is a CGI  
  55. 61                      * program */  
  56. 62   char *query_string = NULL;  
  57. 63    
  58. 64   numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行  
  59. 65   i = 0; j = 0;  
  60. 66   while (!ISspace(buf[j]) && (i < sizeof(method) - 1))  
  61. 67   {  
  62. 68    method[i] = buf[j];  
  63. 69    i++; j++;  
  64. (gdb) l  
  65. 70   }  
  66. 71   method[i] = '\0';//获取到了HTTP方法  
  67. 72    
  68. 73   if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))  
  69. 74   {  
  70. 75       //忽略大小写比较  
  71. 76    unimplemented(client);  
  72. 77    return;//尚未支持的请求方法,线程返回  
  73. 78   }  
  74. 79    
  75. (gdb) l serve_file 【其它的细节调试就不在这里演示了,直接跳到serve_file里】  
  76. 412  * Parameters: a pointer to a file structure produced from the socket  
  77. 413  *              file descriptor  
  78. 414  *             the name of the file to serve */  
  79. 415 /**********************************************************************/  
  80. 416 void serve_file(int client, const char *filename)  
  81. 417 {  
  82. 418  FILE *resource = NULL;  
  83. 419  int numchars = 1;  
  84. 420  char buf[1024];  
  85. 421   
  86. (gdb) l  
  87. 422  buf[0] = 'A'; buf[1] = '\0';  
  88. 423  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */  
  89. 424   numchars = get_line(client, buf, sizeof(buf));  
  90. 425   
  91. 426  resource = fopen(filename, "r");  
  92. 427  if (resource == NULL)  
  93. 428   not_found(client);  
  94. 429  else  
  95. 430  {  
  96. 431   headers(client, filename);  
  97. (gdb) b 426 【在426行设置断点】  
  98. Breakpoint 2 at 0x804a247: file httpd.c, line 426.  
  99. (gdb) c  
  100. Continuing.  
  101.   
  102. Breakpoint 2, serve_file (client=4, filename=0xb63fdf4e "htdocs/index2.html") at httpd.c:426  
  103. 426  resource = fopen(filename, "r");  
  104. (gdb) p filename  
  105. $2 = 0xb63fdf4e "htdocs/index2.html"  
  106. (gdb) n  
  107. 427  if (resource == NULL)  
  108. (gdb) n  
  109. 431   headers(client, filename);  
  110. (gdb) n  
  111. 432   cat(client, resource);  
  112. (gdb) s 【进入cat里面看看】  
  113. cat (client=4, resource=0xb6c00468) at httpd.c:170  
  114. 170 {  
  115. (gdb) l  
  116. 165  * easier just to do something like pipe, fork, and exec("cat").  
  117. 166  * Parameters: the client socket descriptor  
  118. 167  *             FILE pointer for the file to cat */  
  119. 168 /**********************************************************************/  
  120. 169 void cat(int client, FILE *resource)  
  121. 170 {  
  122. 171  char buf[1024];  
  123. 172   
  124. 173  fgets(buf, sizeof(buf), resource);  
  125. 174  while (!feof(resource))  
  126. (gdb) n  
  127. 173  fgets(buf, sizeof(buf), resource);  
  128. (gdb) n  
  129. 174  while (!feof(resource))  
  130. (gdb) p buf 【讲到了index2.html的一行,然后send】  
  131. $3 = "<a href=\"http://10.108.222.96:54205/test.sh\">Display Date</a>\n", '\000' <repeats 306 times>, "\"\225^\267\000\000\000\000 \312q\267\000\320t\267 \000\000\000 \312q\267\304Re\267 \000\000\000El^\267\001\000\000\000\000\320t\267 \000\000\000\364\277q\267\360\331?\266V\003_\267\364\277q\267 \000\000\000 \312q\267\000\320t\267\000\000\000\000$k^\267 \312q\267\000\320t\267 ", '\000' <repeats 15 times>, "A\252\004\b\364\277q\267 \000\000\000\377\377\377\377\000\000\000\000\236\201^\267 ", '\000' <repeats 23 times>, " \312q\267U\205^\267 \312q\267\000\320t\267 ", '\000' <repeats 19 times>"\364, \277q\267\001\000\000\000R\252\004\b\000\000\000\000\343v^\267"...  
  132. (gdb) n  
  133. 176   send(client, buf, strlen(buf), 0);  
  134. (gdb) n  
  135. 177   fgets(buf, sizeof(buf), resource);  
  136. (gdb) n  
  137. 174  while (!feof(resource))  
  138. (gdb) n  
  139. 179 }  
  140. (gdb) n  
  141. serve_file (client=4, filename=0xb63fdf4e "htdocs/index2.html") at httpd.c:434  
  142. 434  fclose(resource);  
  143. (gdb) bt  
  144. #0  serve_file (client=4, filename=0xb63fdf4e "htdocs/index2.html") at httpd.c:434  
  145. #1  0x08048f83 in accept_request (client=4) at httpd.c:130  
  146. #2  0xb7726d4c in start_thread () from /lib/i386-linux-gnu/libpthread.so.0  
  147. #3  0xb7665b8e in clone () from /lib/i386-linux-gnu/libc.so.6  
  148. (gdb) n  
  149. 435 }  
  150. (gdb) s  
  151. accept_request (client=4) at httpd.c:139  
  152. 139  close(client); <span style="background-color: rgb(255, 255, 255);">【直到运行在这里,浏览器的请求才会真正停止,意味着标签栏那个不断旋转的标志就停了】</span>  
  153. (gdb) s  
  154. 140 }  
  155. (gdb) s  
  156. 0xb7726d4c in start_thread () from /lib/i386-linux-gnu/libpthread.so.0  
  157. (gdb) s  
  158. Single stepping until exit from function start_thread,  
  159. which has no line number information.  
  160. [New Thread 0xb5bfdb40 (LWP 7656)]  
  161. [Switching to Thread 0xb5bfdb40 (LWP 7656)]  
  162.   
  163. Breakpoint 1, accept_request (client=4) at httpd.c:64  
  164. 64   numchars = get_line(client, buf, sizeof(buf));//从套接字中读取一行  
  165. (gdb) n  
  166. [Thread 0xb63feb40 (LWP 7655) exited]  
  167. 65   i = 0; j = 0;  
  168. (gdb) p buf  
  169. $4 = "GET /favicon.ico HTTP/1.1\n", '\000' <repeats 997 times> 【再读一行时,竟读到favicon.ico,目前没弄明白这怎么回事】  
  170. (gdb)   

前面已说过,tinyhttpd目前就支持两种请求形式,纯get请求或者带?的get和直接POST请求。了解到源码htdocs目录下的cgi都是perl写的,不知读者你懂不懂,反正博主我不懂,所以就改一改,改成自己的需求,用shell写。正如index2.html所示:

<a href="http://10.108.222.96:54205/test.sh">Display Date</a>

test.sh脚本如下:

#!/bin/sh 
#echo "Content-type:text/html"
echo
echo "<html><head><meta charset="utf-8"><title>MyTitle</title></head><body>"
time=`date`
echo "<p>Server Time:$time"
echo "</body></html>"

即包括服务器响应给客户的字符数据,顺便把服务器时间传过去。注意要加test.sh添加执行权限,才会被视为执行cgi程序,且href中的端口号要改为你具体的端口号,这里只是个示例。来看当在浏览器中点击“Display Date”时,服务器作出的响应:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. (gdb) l execute_cgi 【为了节省空间,以下内容我删除了无关内容】  
  2. warning: Source file is more recent than executable.  
  3. 214  * Parameters: client socket descriptor  
  4. 215  *             path to the CGI script */  
  5. 216 /**********************************************************************/  
  6. 217 void execute_cgi(int client, const char *path,  
  7. 218                  const char *method, const char *query_string)  
  8. 219 {  
  9. 220  char buf[1024];  
  10. 229   
  11. 230  buf[0] = 'A'; buf[1] = '\0';  
  12. 231  if (strcasecmp(method, "GET") == 0)  
  13. (gdb) b 231 【在execute_cgi处设置断点】  
  14. Breakpoint 1 at 0x8049555: file httpd.c, line 231.  
  15. (gdb) c  
  16. Continuing. 【当在浏览器发起请求时,serve_file被调用,但此时断点在execute_cgi处,所以此处没有反应直到鼠标点击链接】  
  17. [New Thread 0xb7567b40 (LWP 7708)]  
  18. [Thread 0xb7567b40 (LWP 7708) exited]  
  19. [New Thread 0xb6bffb40 (LWP 7709)]  
  20. [Thread 0xb6bffb40 (LWP 7709) exited]  
  21. [New Thread 0xb63feb40 (LWP 7710)]  
  22. [Switching to Thread 0xb63feb40 (LWP 7710)]  
  23.   
  24. Breakpoint 1, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",   
  25.     query_string=0xb63fe255 "") at httpd.c:231  
  26. 231  if (strcasecmp(method, "GET") == 0)  
  27. (gdb) info args 【查看此函数调用参数值】  
  28. client = 4  
  29. path = 0xb63fdf4e "htdocs/test.sh" 【文件为test.sh脚本】  
  30. method = 0xb63fe14e "GET"  
  31. query_string = 0xb63fe255 ""  
  32. 257   
  33. 258  if (pipe(cgi_output) < 0) {  
  34. 259   cannot_execute(client);  
  35. 260   return;  
  36. 261  }  
  37. 262  if (pipe(cgi_input) < 0) {  
  38. 263   cannot_execute(client);  
  39. 264   return;  
  40. 265  }  
  41. 266   
  42. (gdb) b 258 【在创建管道处设置断点】  
  43. Breakpoint 2 at 0x804973e: file httpd.c, line 258.  
  44. (gdb) c  
  45. Continuing.  
  46.   
  47. Breakpoint 2, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",   
  48.     query_string=0xb63fe255 "") at httpd.c:258  
  49. 258  if (pipe(cgi_output) < 0) {  
  50. (gdb) n  
  51. 262  if (pipe(cgi_input) < 0) {  
  52. (gdb) n  
  53. 267  if ( (pid = fork()) < 0 ) {  
  54. (gdb) l  
  55. 262  if (pipe(cgi_input) < 0) {  
  56. 263   cannot_execute(client);  
  57. 264   return;  
  58. 265  }  
  59. 266   
  60. 267  if ( (pid = fork()) < 0 ) {  
  61. 268   cannot_execute(client);  
  62. 269   return;  
  63. 270  }  
  64. 271  if (pid == 0)  /* child: CGI script */  
  65. (gdb) l  
  66. 272  {  
  67. 273   char meth_env[255];  
  68. 274   char query_env[255];  
  69. 275   char length_env[255];  
  70. 276   
  71. 277   dup2(cgi_output[1], 1);  
  72. 278   dup2(cgi_input[0], 0);  
  73. 279   close(cgi_output[0]);  
  74. 280   close(cgi_input[1]);  
  75. 281   sprintf(meth_env, "REQUEST_METHOD=%s", method);  
  76. (gdb) l  
  77. 282   putenv(meth_env);  
  78. 283   if (strcasecmp(method, "GET") == 0) { 【我的测试例子虽说是get请求,但不需要设置什么环境变量】  
  79. 284    sprintf(query_env, "QUERY_STRING=%s", query_string);  
  80. 285    putenv(query_env);  
  81. 286   }  
  82. 287   else {   /* POST */  
  83. 288    sprintf(length_env, "CONTENT_LENGTH=%d", content_length);  
  84. 289    putenv(length_env);  
  85. 290   }  
  86. 291   execl(path, path, NULL); 【子进程执行test.sh】  
  87. (gdb) l  
  88. 292   exit(0);  
  89. 293  }  
  90. 294    
  91. 295  else {    /* parent */  
  92. 296   close(cgi_output[1]);  
  93. 297   close(cgi_input[0]);  
  94. 298   if (strcasecmp(method, "POST") == 0)  
  95. 299    for (i = 0; i < content_length; i++) {  
  96. 300     recv(client, &c, 1, 0);  
  97. 301     write(cgi_input[1], &c, 1);  
  98. (gdb) b 298 【由于子进程执行test.sh,父进程发送响应给浏览器,所以先进入父进程,看发的是什么】  
  99. Breakpoint 3 at 0x80498ec: file httpd.c, line 298.  
  100. (gdb) c  
  101. Continuing.  
  102.   
  103. Breakpoint 3, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",   
  104.     query_string=0xb63fe255 "") at httpd.c:298  
  105. 298   if (strcasecmp(method, "POST") == 0)  
  106. (gdb) n  
  107. 304   while (read(cgi_output[0], &c, 1) > 0)  
  108. (gdb) l  
  109. 299    for (i = 0; i < content_length; i++) { 【如果是POST,则还要继续从cgi_input中读取数据体,它被导入到标准输入,从而经由管道进入cgi_output[1]】  
  110. 300     recv(client, &c, 1, 0);  
  111. 301     write(cgi_input[1], &c, 1);  
  112. 302    }  
  113. 303    
  114. 304   while (read(cgi_output[0], &c, 1) > 0)  
  115. 305    send(client, &c, 1, 0);  
  116. 306   
  117. 307   close(cgi_output[0]);  
  118. 308   close(cgi_input[1]);  
  119. (gdb) s 【单步从cgi_output[0]中读】  
  120. 305    send(client, &c, 1, 0);  
  121. (gdb) p c  
  122. $1 = 10 '\n'  
  123. (gdb) s  
  124. 305    send(client, &c, 1, 0);  
  125. (gdb) p c 【以下部分刚好读到的是test脚本的"<html"】  
  126. $2 = 60 '<'  
  127. (gdb) s  
  128. 305    send(client, &c, 1, 0);  
  129. (gdb) p c  
  130. $3 = 104 'h'  
  131. (gdb) s  
  132. 305    send(client, &c, 1, 0);  
  133. (gdb) p c  
  134. $4 = 116 't'  
  135. (gdb) s  
  136. 305    send(client, &c, 1, 0);  
  137. (gdb) p c  
  138. $5 = 109 'm'  
  139. (gdb) s  
  140. 305    send(client, &c, 1, 0);  
  141. (gdb) p c  
  142. $6 = 108 'l'  
  143. (gdb) l  
  144. 300     recv(client, &c, 1, 0);  
  145. 301     write(cgi_input[1], &c, 1);  
  146. 302    }  
  147. 303    
  148. 304   while (read(cgi_output[0], &c, 1) > 0)  
  149. 305    send(client, &c, 1, 0);  
  150. 306   
  151. 307   close(cgi_output[0]);  
  152. 308   close(cgi_input[1]);  
  153. 309   waitpid(pid, &status, 0);  
  154. (gdb) b 307  
  155. Breakpoint 4 at 0x80499be: file httpd.c, line 307.  
  156. (gdb) c  
  157. Continuing.  
  158.   
  159. Breakpoint 4, execute_cgi (client=4, path=0xb63fdf4e "htdocs/test.sh", method=0xb63fe14e "GET",   
  160.     query_string=0xb63fe255 "") at httpd.c:307  
  161. 307   close(cgi_output[0]);  
  162. (gdb) n  
  163. 308   close(cgi_input[1]);  
  164. (gdb) n  
  165. 309   waitpid(pid, &status, 0);  
  166. (gdb) n  
  167. 311 }  
  168. (gdb) p status  
  169. $7 = 0  
  170. (gdb) n  
  171. accept_request (client=4) at httpd.c:139  
  172. 139  close(client); <span style="background-color: rgb(255, 255, 255);">【直到这里,浏览器才显示了返回结果】</span>  
  173. (gdb) n  
  174. 140 }  
  175. (gdb)   
结果显示:

当然我在这里只是演示了其中的一种情况,至于情况如get请求带?查询的,POST请求带数据体的,只有靠读者自己去尝试了,博主暂时抛砖引玉于此。

呃,感觉讲解至此结束了呢。貌似还有一点点细节博主还得继续研究下,总之通过这个例子确实对Linux编程了解了更多了,感谢开源,哈哈!


参考链接

1 http://blog.csdn.net/jcjc918/article/details/42129311

2 http://blog.sina.com.cn/s/blog_a5191b5c0102v9yr.html

3 CGI介绍:http://www.jdon.com/idea/cgi.htm

4 http://www.scholat.com/vpost.html?pid=7337

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值