我们都知道,学一门语言,只是单独看了就不写的话是很容易出现眼高手低的,所以,今天摩杜云要给大家分享的内容,就是如何用C语言写一个web服务器的基础功能,希望大家看完有所收获。
服务器架构
目标架构
以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请求中客户端与服务器之间的交互:
如图:服务器创建要进行:
1.调用socket()创建一个连接;int socket(int domain, int type, int protocol);
2.调用bind()给套接字命名,绑定端口;int bind( int socket, const struct sockaddr *address, size_t address_len);
3.调用listen()监听此套接字;int listen(int socket, int backlog);
4.调用accept()接受客户端的连接;int accept(int socket, struct sockaddr *address, size_t *address_len);
5.调用recv()接收客户端的信息;int recv(int s, void *buf, int len, unsigned int flags);
6.调用send()将响应信息发送给客户端;int send(int s, const void * msg, int len, unsigned int falgs);
socket间的接收和发送信息在 C 中有几个系列:write() / read() 、send() / recv() 、sendto() / recvfrom()、 sendmsg() / recvmsg(),可以自行选用。
另外函数参数释义和要点,都被我注释在代码中了,感兴趣的可以拉下来看一下,这些在网上也多有介绍,这里不再赘述。
服务器与PHP cli交互
然后是C进程和php进程的交互,考虑到简单易用,目前在C进程中直接执行php脚本:
一开始使用system()函数: int system(const char *command);
system函数会fork一个子进程,在子进程中以cli方式执行php脚本,并将错误码或返回值返回。由于其结果类型不可控,编译时会报一个warning。而且它将结果返回给父进程时,还会在标准输出中打印结果,在服务器执行时会抛出异常。
于是找到了另一个方法popen, FILE * popen(const char * command, const char * type);:
popen同样会fork一个子进程来执行command ,然后建立管道连到子进程的标准输出设备或标准输入设备,然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。
其type参数便是控制连接到子进程的标准输入还是标准输出。我们想要子进程的标准输出,于是传入type参数为字符 “r” (read)。同理,如果想写入子进程标准输入的话,可以传值 “w”(write)。
另外在接收缓冲区内容的时候也出现了一点小意外:由于使用的fgets()方法会以换行符 为一段的结尾,在接收php进程输出时遇到换行会结束,这里使用了一个中间字符串数组line来接收每一行的信息,将每一行的信息拼装到结果中。
代码如下:
报文数据处理
socket处于应用层和传输层之间的虚拟层,由于设置服务器socket协议类型为TCP,那么TCP的握手挥手、数据读取等步骤对于我们都是透明的。我们拿到的数据即HTTP报文,关于HTTP报文结构和其字段解释的文章非常多,这里也不再多提。
首先使用C的strtok()方法,获取到HTTP头的第一行,获取到其http方法和path信息,将这些信息处理后,再使用sprintf()方法拼合HTTP响应报文,主要替换了响应内容长度和响应内容。
以上就是关于“如何用用C写一个web服务器的基础功能”的详细内容介绍,希望大家看望之后有所帮助,如若有不清楚的地方或者想了解更多知道可以关注摩杜云,领先的云计算及人工智能服务提供商,十几年行业经验,专业可靠!另外,如果大家觉得这篇文章不错的话,可以分享给更多的人看到。
转载:https://blog.csdn.net/Chihider/article/details/117334898