如何用C写一个web服务器之基础功能

 更新时间:2021年05月27日 09:24:41   作者:枕边书  
C语言是一门很基础的语言,程序员们对它推崇备至,本文将带着大家来看一下,如何用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() 方法会以换行符\n为一段的结尾,在接收 php 进程输出时遇到换行会结束,这里使用了一个中间字符串数组line来接收每一行的信息,将每一行的信息拼装到结果中。

代码如下:

char * execPHP(char *args){
    // 这里不能用变长数组,需要给command留下足够长的空间,以存储args参数,不然拼接参数时会栈溢出
    char command[BUFF_SIZE] = "php /Users/mfhj-dz-001-441/CLionProjects/cproject/tinyServer/index.php ";
    FILE *fp;
    static char buff[BUFF_SIZE]; // 声明静态变量以返回变量指针地址
    char line[BUFF_SIZE];
    strcat(command, args);
    memset(buff, 0, BUFF_SIZE); // 静态变量会一直保留,这里初始化一下
    if((fp = popen(command, "r")) == NULL){
        strcpy(buff, "服务器内部错误");
    }else{
        // fgets会在获取到换行时停止,这里将每一行拼接起来
        while (fgets(line, BUFF_SIZE, fp) != NULL){
        strcat(buff, line);
        };
    }

    return buff;
}

报文数据处理

socket 处于应用层和传输层之间的虚拟层,由于设置服务器 socket 协议类型为 TCP,那么 TCP 的握手挥手、数据读取等步骤对于我们都是透明的。我们拿到的数据即 HTTP 报文,关于 HTTP 报文结构和其字段解释的文章非常多,这里也不再多提。

首先使用 C 的 strtok() 方法,获取到 HTTP 头的第一行,获取到其 http 方法和 path 信息,将这些信息处理后,再使用 sprintf() 方法拼合 HTTP 响应报文,主要替换了 响应内容长度和响应内容。

以上就是如何用C写一个web服务器之基础功能的详细内容,更多关于用C写一个web服务器之基础功能的资料请关注脚本之家其它相关文章!

相关文章

  • QT网络编程UDP下C/S架构广播通信(实例讲解)

    QT网络编程UDP下C/S架构广播通信(实例讲解)

    下面小编就为大家带来一篇QT网络编程UDP下C/S架构广播通信(实例讲解)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-07-07
  • C语言实现的阶乘,排列和组合实例

    C语言实现的阶乘,排列和组合实例

    这篇文章主要介绍了C语言实现的阶乘,排列和组合的方法,涉及C语言数学运算的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-07-07
  • c++ 数据结构map的使用详解

    c++ 数据结构map的使用详解

    这篇文章主要介绍了c++ 数据结构map的使用详解,帮助大家更好的理解和学习使用c++,感兴趣的朋友可以了解下
    2021-04-04
  • C++ 中const和复合类型

    C++ 中const和复合类型

    本文给大家讲述的是C++ 中比较难理解的const和复合类型,结合自己的一些经验,分享给大家,希望大家能够喜欢。
    2016-02-02
  • C++ Template 基础篇(一):函数模板详解

    C++ Template 基础篇(一):函数模板详解

    这篇文章主要介绍了C++ Template函数模板,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-04-04
  • C语言实现简易三子棋

    C语言实现简易三子棋

    这篇文章主要为大家详细介绍了C语言实现简易三子棋,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07
  • C++中的delete不会将操作数置0

    C++中的delete不会将操作数置0

    这篇文章主要介绍了C++中的delete不会将操作数置0的相关资料,需要的朋友可以参考下
    2016-05-05
  • C#和C++编程语言中的类浅析

    C#和C++编程语言中的类浅析

    在本篇文章里我们给大家分析了C#和C++编程语言中的类的相关知识点,正在学习的朋友们跟着操作下。
    2019-02-02
  • C++ Leetcode实现从英文中重建数字

    C++ Leetcode实现从英文中重建数字

    本文主要介绍了当给你一个字符串s,其中包含字母顺序打乱的用英文单词表示的若干数字(0-9)时,如何通过Leetcode按升序返回原始的数字。感兴趣的童鞋可以来看看
    2021-11-11
  • C++实现简单的扫雷游戏(控制台版)

    C++实现简单的扫雷游戏(控制台版)

    这篇文章主要为大家详细介绍了C++实现简单的扫雷游戏,控制台版的扫雷游戏希望大家喜欢,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-05-05

最新评论