C++服务器项目概述

项目框架

该项目代码:https://github.com/xiaoweixiao/HTTP

http协议总结:https://blog.csdn.net/Vickers_xiaowei/article/details/86683121
项目拓展网页效果:https://blog.csdn.net/Vickers_xiaowei/article/details/88839444

思维导图
在这里插入图片描述
服务器启动会生成一个Server对象,这个对象中包含了用于和客户端交互的socket和处理任务的线程池。当Accept到客户端的TCP连接后,创建任务对象,将任务对象添加给线程池的任务队列。线程池的线程从任务队列取来任务进行任务处理。任务处理部分首先解析http请求,对于CGI部分,创建子进程,让子进程替换成执行CGI程序,父进程负责客户端socket和子进程CGI程序的数据交互。处理完CGI程序之后,根据请求进行响应。

详细过程

  • 服务器启动,会new一个httpserver对象,这个httpserver对象监听套接字、线程池、端口三个私有成员。在对httpserver这个对象初始化时候,创建了套接字、绑定了INADDR_ANY地址(实现端口复用)、监听链接,同时创建了线程池对象,线程池对象有总共线程数、idle的线程数、任务队列、互斥锁、条件变量、bool类型的开关。每来一个连接,创建一个任务,将任务扔到线程池,让线程去处理。

  • 线程池部分:线程初始化主要是创建NUM个线程,然后让这些线程,线程分离后,线程去排队处理任务,线程处理任务前,要先把线程池加锁,因为线程池是线程的临界资源。然后查看任务队里中有没有任务,如果一直没有任务,就让线程一直idle,一旦任务队列有任务了,线程醒来自动获得锁资源,然后从任务队列获得任务之后,赶紧释放锁资源,开始处理任务,任务对象开始处理任务,任务对象有处理方法handle的函数指针,有数据socket。

  • 请求解析:处理方法函数handle是Entry类的成员函数,在handle函数处理中,分别对请求行、请求报头、空行、和正文进行解析。处理中会创建三个对象分别是Request、Connect、Response,在解析过程中记录请求方法、请求路径、参数、后缀、请求文件大小等信息字段更新到Request,并对请求方法、请求路径等信息是否合法做了判断,分别采用unorder_map对报头键值对和后缀对应的文件格式进行存储。对文件的可执行权限、是否为目录文件都进行判断记录。如果是CGI部分还需要接受正文内容。

  • 处理过程:处理过程分为非CGI和CGI两部分。对于非CGI部分,直接进行构建响应行、响应报头、加上空格,打开资源路径,将资源sendfile出去。对于CGI部分,创建子进程,让子进程去处理CGI。创建了两个管道用于子进程从父进程读数据,返回处理结果给用户。由于进程替换后,文件描述符依然有效,但是数据都已经被替换,所以在进程替换前,对子进程的两个管道端口进行重定向到0和1,而对于Content-length字段,采取了设置环境变量的方法让子进程可见。子进程处理了CGI程序,而父进程,将构建响应行、响应报头、空行、响应正文然后将结果发送。

  • 错误处理:响应对象中的响应码,对应有响应信息。对于错误、警告、提示信息进行对应处理。特别是状态码404的处理,构建了404错误响应。

细节问题

在这里插入图片描述

  1. 为什么需要实现端口复用?
  • 服务器只有一个,但是客户端有很多。HTTP服务器采用TCP协议进行通信,在一个客户端与服务器断开连接时候,传输层会有四次挥手断开链接之后的TIME_WAIT时间,一般这个TIME_WAIT大概是30秒,对于这个即将断开连接的客户端来说,区区30秒不足挂齿,而对于服务器来讲,这30秒弥足珍贵,因为服务器还有很多客户端,TIME_WAIT状态服务器端口被占用是不允许存在的情况,将导致其他客户端无法正常链接,因此端口复用是服务器必备的功能。
    在这里插入图片描述
  • 使用setsockopt函数实现端口复用。SO_REUSEADDR为1能够保证HTTP服务器可以快速重启,如果没有该选项,一旦服务器挂掉了,端口是无法立即重新启用,因为还有TIME_WAIT。
  • INADDR_ANY:访问任意的网卡
    在这里插入图片描述
  1. 当发生错误之后为什么还需要将请求报头全部接受?
    当出现错误时,客户端的请求报头没有全部接受,服务器会认为连接失败,在服务器响应给客户端的响应报文中,TCP报头中的RST字段会被置1,链接会被重置,这样链接就会陷入请求错误和链接重置的死循环中。
    正确的做法是当发送错误时候,将请求报头全部读取,然后给客户端构造链接错误的响应报文。
  2. 子进程进行进程替换之后,是否还能找到原有的CGI文件?
    文件系统的声明周期随进程,在进程的task_strcut中有一个file*的文件指针数组,子进程进行进程替换只是替换了进程的代码段和数据段,不会对进程所打开的文件有影响。
    在这里插入图片描述
  3. 在CGI部分为什么需要设置环境变量?
    在CGI程序中,必须知道需要从管道中读取的数据长度,因此需要将Content-Length字段保存至进程替换之后还依旧有效。
  4. 对子进程的标准输入和输出重定向的意义是什么?
    虽然在进程替换过程管道文件对于子进程依然有效,但是由于进程替换了代码段,由于管道文件的下标是局部变量,所以input和output不再有效。对子进程的标准输入和标准输出重定向之后,达到的效果是,子进程直接从管道读取端读取数据,结果直接写入管道的写入端,父进程将客户端的sock中的参数写入管道,直接从管道读取CGI程序结果,然后构建响应发送给客户端。
    在这里插入图片描述
  5. TCP粘包问题:就是要解决数据无边界的问题:数据定长,特殊字符间隔,tlv格式数据
    tag、len、value缩写tlv
struct
{
    uint8_t type;
    uint64_t len;
    char val[0];
};
  1. 网络断开/对端关闭连接,程序如何快速判断连接已经断开?
    传输层:tcp内部有自己的连接检测机制,检测是否正常。保活机制:发送“保持活动”包,维持连接。
    应用层:发送方判断:连续重复发送多次数据没有ACK之后,认为连接断开了,操作系统向进程发送SIGPIPE信号,进程使用默认处理方式(退出异常)
    接收方判断:recv返回0(对端关闭连接,连接断开)。recv的errno==EINTR,这个阻塞操作被信号打断。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值