• 基于 Linux 的 web 服务器


    简介

    Linux 下的轻量级 Web 服务器,使用 C++11 开发,架构参考 Github 高星项目 TinyWebServer。参考链接:Tinyweb

    技术要点:

    1. 使用线程池实现高并发。
    2. epoll实现IO复用,采用模拟 Proactor 并发模型。
    3. 通过状态机解析 HTTP 报文请求。
    4. 采用异步日志系统记录服务器运行状态。
    5. 使用timerfd定时器接口主动对长时间无操作的连接断开。
    6. 允许提供自签名的证书和私 钥使用 openssl 提供 https 服务。

    使用 webbench 压测能够实现上万并发连接数据交换。

    基本架构:

    简单的分为三层:数据层、服务层、连接层。其中数据层负责与MySQL数据库建立连接,进行交互;服务层负责http报文的解析和构建;连接层负责socket监听服务以及连接操作,对于https服务,提供ctx会话环境的构建和ssl连接。

    文件结构:

    .
    ├── http 										//提供http报文解析和生成服务
    │   ├── httpconn.cpp					
    │   ├── httpconn.h							//集成httpRequestParser和httpResponse,并负责提供静态资源托管接口、get和post路由设置函数
    │   ├── httpRequestParser.cpp
    │   ├── httpRequestParser.h				//http报文解析
    │   ├── httpResponse.cpp
    │   ├── httpResponse.h				//http报文构建
    │   └── README.md
    ├── log										//异步日志,来自[Ring Log](git@github.com:LeechanX/Ring-Log.git),进行了一点修改
    │   ├── Makefile
    │   ├── README.md
    │   ├── rlog.cc
    │   ├── rlog.h
    │   └── rlog.o
    ├── make.sh
    ├── mysql								//数据库连接池,以及提供数据库操作函数
    │   ├── connect_pool.cpp
    │   └── connect_pool.h			//单例模式,使用GetConnection获取数据库连接指针,ReleaseConnection放回数据库连接池。提供ConnectRAII对象管理数据库连接,使用RAII保证数据连接会安全放回连接池。使用init函数初始化数据库连接池对象,并且保证init函数只会调用一次。
    ├── server.cpp
    ├── server.h							//连接层,负责监听socket连接,将连接放入线程池以及主动断开长时间未操作的连接,webserver类用来定义一个完整的web服务器对象。
    ├── threadPool						//线程池类,为连接层提供线程池,使用模板实现,便于扩展(可以提供除http外的其他服务)
    │   ├── README.md
    │   └── threadPool.h
    └── timer							//定时器类,为连接层提供定时断连服务。
        ├── README.md
        ├── timer_fd.cpp
        └── timer_fd.h
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    一、线程池模板类threadPool

    使用模板便于扩展,之后可以添加除了http之外的其他服务。
    线程同步采用了c++11提供的,线程库采用了c++11的thread

    技术栈

    1.public继承std::enable_shared_from_this>

    使用智能指针管理threadPool对象,当对象析构时,停止线程循环,执行线程回收工作。

    2.智能指针shared_ptr

    init()函数负责初始化线程池对象,构建对应数目的线程,并关联线程函数work(),传入线程池模板类对应的this的智能指针(采用shared_from_this),在work中就能调用threadPool对象的工作队列workList进行处理。

    3. 异常捕获try

    对于thread线程创建增加异常捕获,若线程创建失败,则不需要把线程添加进m_threadList,整个程序也不会因此崩溃,只是实际上创建的线程会少于预期。

    关键代码段

    //增加异常捕获,如果当前线程创建失败,则不需要添加进m_threadList
    try{
    		//把当前对象的智能指针传入work函数,因为work函数必须是静态函数,而静态函数没有对象的this指针,所以使用这种折中的方法
            std::shared_ptr<std::thread> t = std::make_shared<std::thread>(work, threadPool<T>::shared_from_this());
            temp.swap(t);
    }
    catch(const std::system_error &e){
        //报告错误
        LOG_FATAL("Create thread eroror : %s", e.what());
        continue;
    }
    ....
    //work函数
    template<typename T>
    void threadPool<T>::work(std::shared_ptr<threadPool<T>> request){
    	//使用threadPool对象的run方法,也就是业务循环段
        request->run();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    二、http服务,httphttp_requesthttp_response

    http类整合了:http报文读取:read_oncehttp报文发送:write_once()http请求报文解析和响应报文构建:process()

    • http类中的static std::map staticSource负责静态资源的托管,
    • 负责Get请求的路由设置:static std::map registerGet;
    • 负责POST请求的路由设置:static std::map registerPost;

    其中http报文解析使用了http_request类专门负责报文解析模块,http响应报文构建专门使用了http_response类负责响应报文构建。

    流程:

    1. 使用init(int epollfd, int sockfd, SSL* ssl)初始化,传入epoll、socket文件描述符,以及SSL*,当SSL*不为空时,认为执行https服务,空时执行http服务。
    2. 使用http::read_oncesocket中读取http请求报文,并对读取情况进行判定,read_once返回结果是READ_STAT对象
    • enum class READ_STAT{OK, BAD, CLOSE};分别代表读取成功、失败以及对方关闭连接。
    1. 成功读取http请求报文后使用process()函数,process()函数首先对报文进行解析,解析函数返回HTTP_CODE类型对象,根据不同的解析结果生成不同的响应报文,除了“报文完整”,其他都是错误信息,对于“报文完整”的情况,会跳跳转get_request()函数,分别执行GET请求和POST的事务,并构建响应报文。
    //解析报文
    HTTP_CODE code = this->request_parase.parser(this->m_message);
    ....
    //HTTP报文解析结果:报文不完整,报文完整,报文错误, 内部错误(兜底)
    enum class HTTP_CODE{OPEN_REQUEST = 0, GET_REQUEST, BAD_REQUEST, INTERNAL_ERROR};
    
    • 1
    • 2
    • 3
    • 4
    • 5

    三、定时器类timefdList

    timefdList类维护一个定时器列表std::list>> m_list,该m_list维护一个智能指针,指针指向vector,vector是一个长度为2的数组,分别存储socket文件描述符和timerfd_create创建的定时器对象文件描述符

    接口:

    • void add(std::shared_ptr):负责往定时器列表添加需要监听的定时器对象
    • void adjust(std::shared_ptr data, int n):调整对应计时器,超时时间后移
    • void delete_timer(std::shared_ptr data):删除对应的计时器。

    操作流程:

    • 当创建一个新的socket连接时,同时往定时器类中添加一个定时器对象,并在epoll中注册响应的定时器对象文件描述符的读操作。
    • 每次读写socket都会更新对应socket的定时器超时时间。
    • 当定时器文件描述符可读时,并不直接关闭对应的socket,而是给一个标记,等这次epoll_wait返回的事件都处理完毕,调用timefdList的tick方法处理所有超时连接。

    四、连接层,webserver类

    websever负责整合所有功能,主要其监听连接,并发模型使用模拟proactor

    接口:

    • bool webListen(std::string ip, int port);:监听对应的端口,成功返回true,失败返回false
    • void run(int);:开始运行,输入一个int类型参数,代表socket允许的无操作时间。
    • bool openhttps(const char* cacert, const char* key, const char* passwd);:开启https,输入ca文件,私钥文件和密码,正确初始化返回true,失败返回false。
    • void addStaticSource(const std::string url, const std::string fileName);:添加静态资源
    • void addGet(const std::string, void (func) (http_request&, http_response&, MYSQL));:添加Get请求路由
    • void addPost(const std::string, void (func) (http_request&, http_response&, MYSQL));:添加POST请求路由

    操作流程:

    执行首先使用webListen设置监听端口,如果开启https,则需要使用openhttps传入响应文件,addStaticSource、addGet、addPost添加静态资源和GET、POST路由。

    当执行run之后,服务器主循环开始,主循环epoll_wait等待epoll注册事件发生,对于接收到的事件分为4种类型:

    1. 新连接,服务器监听到新的socket连接请求,accept接收请求,若开启了https,则建立ssl连接。往epoll注册新连接的读事件,并创建一个定时器,epoll注册定时器读事件。
    2. socket读事件:更新定时器,读取socket中的信息,根据读取到的结果选择是否把http对象放入请求队列,让线程池解析报文内容并生成响应报文。
    3. socket写事件:更新定时器,往socket写响应报文。
    4. 定时器定时事件:并不会立即处理定时事件,而是把alarm_event标记置true,等到此次epoll事件处理完毕,再调用定时器列表的tick方法统一关闭超时的socket连接。
  • 相关阅读:
    排队分数 10
    达梦数据库[-70028]:Create SOCKET connection failure
    OpenWrt kernel install分析(1)
    Linux学习之HIS部署(4)
    Vue打包好的dist如何在本地运行
    2.2线性表的顺序表示-综合应用题(408统考真题10-14)
    C++11之lambda表达式理解与使用
    Spring Cloud的魔法世界
    C# 异步编程,有时候我们需要拿到异步任务计算体完成计算的数据,请使用task.AsyncState去获取。
    laravel框架 - 集合篇
  • 原文地址:https://blog.csdn.net/qq_42370809/article/details/126753879