TinyHttpd源码感悟

来源:互联网 发布:诺基亚s60v5软件下载 编辑:程序博客网 时间:2024/05/16 05:33

花了好一会儿的时间去阅读了TinyHttpd的源码,读完了确实对HTTPhttp服务器有了一些不一样的见解。

HTTP

之前一直在想,浏览器发起一个HTTP请求的时候,到底在发送些什么。定义了那么多有的没有的东西,对于服务器来说,他是怎么根据HTTP头去知道的。现在总算是知道了。
对于HTTP协议而言,其本质上就是一系列的文本,浏览器在进行HTTP请求的时候,实际上就是在发生一系列的文本信息。服务器在拿到了HTTP请求之后,会对该请求进行分析,提取出有用的信息,进行处理之后再与客户端进行通信互动。

HTTP服务器

读完了该TinyHttpd的代码后,确实对于HTTP服务器的运行有了一个实质性的了解。记录一些阅读后的感悟。
该服务器采用纯C编写,全部代码不到500行。确实让人感到佩服。之前在学习TCP\IP编程的时候,学到了socket的各种用法,多进程的实现与坑等等。当时还在想就利用这些简单的函数来来回回是怎么实现网络编程的。现在看来,回归到本质,一切都很清晰了。在互联网上传输的数据,(除却媒体信息)。实质上都是一些文本信息,甚至媒体信息我们也可以当成文本信息来处理。

main

在该代码中,main函数先建立一个服务器socket,然后利用循环不断的接收信息。当有连接建立的时候,调用pthread_create建立一个线程来处理连接。此外,该服务器socket启动的时候,如果不指定端口,则会随机监听一个。

accept_request

此时进入该线程所执行的函数中。第一件事就是读取HTTP报文的第一行请求数据,从中提取出该HTTP报文的请求是啥。
如果是POST请求或是带有参数的GET请求,会将cgi标志位记为1。之后可以调用cgi对请求数据进行处理,否则直接发送一个静态文件给客户端。
这里需要注意两个数组urlpath
url数组保存着网址的具体位置

    while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))    {        url[i] = buf[j];        i++;        j++;    }
sprintf(path, "htdocs%s", url);

通过上述的代码获取url信息和将url信息填入网页具体位置。刚开始并不是很理解这两部分的代码。后来看了下几个网站的请求之后算明白了。
http的请求报文中。第一行的含义如下

Method Request-URI HTTP-Version CRLF

其中Request-URI是除了网站网址之后的一系列东西。即对于127.0.0.1/index.htmlRequest-URI保存的是/index.html。简单理解为Request-URI保存着网址的客户端请求访问的具体网页文件。
对于cgi标记位为1的请求,将会执行execute_cgi,返回计算结果。

execute_cgi

该函数用于处理客户端的各类请求。

问题

其中有个地方不是特别理解。对于GET请求,为何要把头信息都读取进来,不可以直接无视掉吗?

    if (strcasecmp(method, "GET") == 0)        //如果是GET请求        //读取并且丢弃头信息        while ((numchars > 0) && strcmp("\n", buf))            numchars = get_line(client, buf, sizeof(buf));

对于这段循环读取完所有的请求头的代码,我个人认为是清空缓存区,防止缓存区不够。或是清理掉该部分的垃圾数据。

感悟

在该函数内,会产生两个管道:cgi_outputcgi_input。这两个管道的操控也是我花了最长时间的地方。
GitHub上找到两张图,非常完美的解释这两个管道的工作方式。

初始状态

初试状态

最终状态
最终状态

然后在代码中调用dup2(cgi_output[1], 1);将标准输出全部重定向到cgi_output[1]中。
所以,现在一共有三个指针指向cgi_output这个管道入口!!!
这点非常重要。分别是以下三个指针

  • 子进程的cgi_output[1]
  • 父进程的cgi_output[1]
  • 标准输出

同样的,对于cgi_input[1],最终也是会有三个指针指向它!

重点

上面提到了一共有三个变量指向cgi_output[1]cgi_input[1]。这里需要重点理解的是。对于一条管道,父进程和子进程会同时指向它的入口和出口!父进程和子进程的文件描述符是互不影响的!!!这个部分也是我一开始纠结了很久没意识到的地方

通过上面的解析,应该能够理解最终状态的管道是如何建立的了。
所以现在数据的流向就比较清晰了。

  • 父进程读取了请求头的数据,将数据直接写入cgi_input[1]中。
  • 子进程调用cgi脚本
  • cgi脚本中的标准输入来源于cgi_input[1],处理完直接print到标准输出
  • 到标准输出中的数据会被父进程从cgi_output[0]中获取
  • 直接送到客户端

总结

该服务器的实现非常非常的简单明了,其中的难点在于对可执行脚本的数据流向处理,这种处理方式确实是第一次见到,所以纠结了好一会儿。不过看懂了之后发现异常清晰。
该源码采用七夜的注释版