标准C语言编写WebSever完成HTTP请求
服务器架构
目标架构
以 nginx 的思想来考虑本服务器架构,初步考虑如下图:
当然 php 进程也可以替换为其他的脚本语言,可以更改源码中的 command 变量实现。
服务器有一个 master 进程,其有多个子进程为 worker 进程,master 进程受理客户端的请求,然后分发给 worker 进程,worker 进程处理 http 头信息后将参数传递给 php 进程处理后,将结果返回到上层,再响应给客户端。
也考虑过使用 php-fpm 的 worker 进程池方式,那样的话 php-fpm 进程也要仿写了,目前还不熟悉其内部构造,如果可以简单化,自然向其靠拢。目前对 PHP 的 SAPI 接口不熟,了解一下再考虑。
当前状态
当前状态的服务器还极其简单,总结下来有以下地方待优化:
- 当前还是单进程,需要改成多进程,最终为 worker 进程池方式;
- 优化 socket IO 模型,考虑 epoll、事件驱动方式;
- 只支持 HTTP GET 请求方法,未进行太多的异常处理来定义 http 状态码;
- 与 php 进程的交互方式,考虑如 nginx 使用 unix domain socket 方式。
- 协议目前只考虑了 http,后续会考虑一些基于 TCP 的协议;
虽然简单,但服务器已经有基本的功能了:
它监听本地地址的 8080 端口,将接收到的 http 头中的 path 信息提出出来交给 php 进程,php 进程将参数信息处理后返回给服务器,服务器拼装 http 响应信息再将结果返回给客户端。
下面介绍各个功能的实现:
功能实现
socket系列方法
在介绍函数之间先用一张图来介绍一次 http 请求中客户端与服务器之间的交互:
如图:服务器创建要进行:
- 调用 socket() 创建一个连接;
int socket(int domain, int type, int protocol);
- 调用 bind() 给套接字命名,绑定端口;
int bind( int socket, const struct sockaddr *address, size_t address_len);
- 调用 listen() 监听此套接字;
int listen(int socket, int backlog);
- 调用 accept() 接受客户端的连接;
int accept(int socket, struct sockaddr *address, size_t *address_len);
- 调用 recv() 接收客户端的信息;
int recv(int s, void *buf, int len, unsigned int flags);
- 调用 send() 将响应信息发送给客户端;
int send(int s, const void * msg, int len, unsigned int falgs);
socket 间的接收和发送信息在 C 中有几个系列:write() / read() 、send() / recv() 、sendto() / recvfrom()、 sendmsg() / recvmsg()
,可以自行选用。
另外函数参数释义和要点,都被我注释在代码中了,感兴趣的可以拉下来看一下,这些在网上也多有介绍,这里不再赘述。
文件发送
在传统的文件传输里面(read/write方式),在实现上其实是比较复杂的,需要经过多次上下文的切换,我们看一下如下两行代码:
read(file, tmp_buf, len); write(socket, tmp_buf, len);
以上两行代码是传统的read/write方式进行文件到socket的传输。
当需要对一个文件进行传输的时候,其具体流程细节如下:
- 调用read函数,文件数据被copy到内核缓冲区
- read函数返回,文件数据从内核缓冲区copy到用户缓冲区
- write函数调用,将文件数据从用户缓冲区copy到内核与socket相关的缓冲区。
- 数据从socket缓冲区copy到相关协议引擎。
以上细节是传统read/write方式进行网络文件传输的方式,我们可以看到,在这个过程当中,文件数据实际上是经过了四次copy操作:
硬盘—>内核buf—>用户buf—>socket相关缓冲区—>协议引擎
而sendfile系统调用则提供了一种减少以上多次copy,提升文件传输性能的方法。Sendfile系统调用是在2.1版本内核时引进的:
sendfile(socket, file, len);
运行流程如下:
- sendfile系统调用,文件数据被copy至内核缓冲区
- 再从内核缓冲区copy至内核中socket相关的缓冲区
- 最后再socket相关的缓冲区copy到协议引擎
相较传统read/write方式,2.1版本内核引进的sendfile已经减少了内核缓冲区到user缓冲区,再由user缓冲区到socket相关 缓冲区的文件copy,而在内核版本2.4之后,文件描述符结果被改变,sendfile实现了更简单的方式,系统调用方式仍然一样,细节与2.1版本的 不同之处在于,当文件数据被复制到内核缓冲区时,不再将所有数据copy到socket相关的缓冲区,而是仅仅将记录数据位置和长度相关的数据保存到 socket相关的缓存,而实际数据将由DMA模块直接发送到协议引擎,再次减少了一次copy操作。
最后附上代码
http://download.****.net/download/wh1pbf38/10272953