C++入门项目:Linux下C++轻量级Web服务器 项目详解(小白篇)

拿到一个项目首先先跑通,然后再慢慢来看代码,关于怎么将这个项目跑通,上一篇已经讲过,感兴趣的小伙伴可以移步下面的链接,或者其他博主的教程。

C++入门项目:Linux下C++轻量级Web服务器 跑通|运行|测试(小白进)_c++web服务器-CSDN博客

 跑通后 ,我们先来看看这个服务器是怎么实现的。要知道怎么实现,首先就要知道服务器的工作流程是怎么样的 ,背后的原理是什么,这样更容易理解整个项目。

web服务器的工作流程

大致流程就是服务器会一直监听web端,如果有客户端发起请求,则创建线程处理用户的链接,与客户端(web端)建立连接,然后根据web端的需求来响应。那么具体的实现细节分解为几个部分来实现,可分为以下5个部分来实现(不是下面这个图,是下面的5个标题)

 整个服务器的框架如下图所示:(来自于原作者)

 

 上面的框架图就是该服务器项目的整体框架:总共就是3部分I/O处理逻辑单元处理存储处理。(一定要用面向对象的思想来理解为什么这么划分)。I/O处理就是服务器监听客户端,建立连接、读写网络数据(主线程负责监听),逻辑处理单元负责处理接收到的请求,执行业务逻辑,包括数据处理、计算、决策等操作(也就是工作线程负责处理连接请求,如:日志的输出、处理非活动链接、处理http请求(Http请求通常用的比较多的是get和post,不同的需求使用的请求不一样)。存储部分就是数据库存储客户端的信息数据等以及日志记录服务器的运行状态(例如链接了多少客户端等)。各个模块之间通过消息队列进行通信。知道大概结构后,再来一个一个细究。

1、web端和服务器端建立连接

参照项目原作者的号:两猿社(最新版Web服务器项目详解 - 00 项目概述

这里涉及到I/O多路复用、边缘触发模式(ET)、proactor模式、线程池、get请求、post请求等知识点

  • 采用epoll的边缘触发模式同时监听多个文件描述符,采用同步I/O模拟proactor模式处理事件,主线程负责监听客户端是否发起请求

  • 当web端发起http请求时,主线程接收请求报文,然后将任务插入请求队列,由工作线程通过竞争从请求队列中获取任务

  • 通过http类中的主从状态机对请求报文进行分析,根据请求报文对客户端进行http响应,然后由主线程给客户端发送响应报文。

 5种I/O模型:

  • 阻塞IO:调用者调用了某个函数,等待这个函数返回,期间什么也不做,不停的去检查这个函数有没有返回,必须等这个函数返回才能进行下一步动作

  • 非阻塞IO:非阻塞等待,每隔一段时间就去检测IO事件是否就绪。没有就绪就可以做其他事。非阻塞I/O执行系统调用总是立即返回,不管时间是否已经发生,若时间没有发生,则返回-1,此时可以根据errno区分这两种情况,对于accept,recv和send,事件未发生时,errno通常被设置成eagain

  • 信号驱动IO:linux用套接口进行信号驱动IO,安装一个信号处理函数,进程继续运行并不阻塞,当IO时间就绪,进程收到SIGIO信号。然后处理IO事件。

  • IO复用:linux用select/poll函数实现IO复用模型,这两个函数也会使进程阻塞,但是和阻塞IO所不同的是这两个函数可以同时阻塞多个IO操作。而且可以同时对多个读操作、写操作的IO函数进行检测。知道有数据可读或可写时,才真正调用IO操作函数

  • 异步IO:linux中,可以调用aio_read函数告诉内核描述字缓冲区指针和缓冲区的大小、文件偏移及通知的方式,然后立即返回,当内核将数据拷贝到缓冲区后,再通知应用程序。

 I/O多路复用:I/O多路复用(异步阻塞IO)总共涉及到3中模式(select、poll、epoll),本项目中采用的epoll

 select、poll、epoll的区别;

⽂件描述符集合的存储位置
对于 select 和 poll 来说,所有⽂件描述符都是在⽤户态被加⼊其⽂件描述符集合的,每次调⽤都需要将整个集合拷⻉到内核态;epoll 则将整个⽂件描述符集合维护在内核态,每次添加⽂件描述符的时候都需要执⾏⼀个系统调⽤。系统调⽤的开销是很⼤的,⽽且在有很多短期活跃连接的情况下,由于这些⼤量的系统调⽤开销,epoll 可能会慢于 select 和 poll。

⽂件描述符集合的表示⽅法
select 使⽤线性表描述⽂件描述符集合,⽂件描述符有上限(1024);poll使⽤链表来描述;epoll底层通过红⿊树来描述,并且维护⼀个就绪列表,将事件表中已经就绪的事件添加到这⾥,在使epoll_wait调⽤时,仅观察这个list中有没有数据即可。
遍历⽅式
select 和 poll 的最⼤开销来⾃内核判断是否有⽂件描述符就绪这⼀过程:每次执⾏ select 或 poll 调⽤时,它们会采⽤遍历的⽅式,遍历整个⽂件描述符集合去判断各个⽂件描述符是否有活动;epoll 则不需要去以这种⽅式检查,当有活动产⽣时,会⾃动触发 epoll 回调函数通知epoll⽂件描述符,然后内核将这些就绪的⽂件描述符放到就绪列表中等待epoll_wait调⽤后被处理。
触发模式
select和poll都只能⼯作在相对低效的LT模式下,⽽epoll同时⽀持LT和ET模式
适⽤场景

当监测的fd数量较⼩,且各个fd都很活跃的情况下,建议使⽤select和poll;当监听的fd数量较多,且单位时间仅部分fd活跃的情况下,使⽤epoll会明显提升性能。

参照:TInyWebServer面试题_tinywebserver面经-CSDN博客

边缘触发模式(ET):

LT:⽔平触发模式,只要内核缓冲区有数据就⼀直通知,只要socket处于可读状态或可写状态,就会⼀直返回sockfd;是默认的⼯作模式,⽀持阻塞IO和⾮阻塞IO
ET:边沿触发模式,只有状态发⽣变化才通知并且这个状态只会通知⼀次,只有当socket由不可写到可写或由不可读到可读,才会返回其sockfd;只⽀持⾮阻塞IO

 事件处理模式:

proactor模式(半同步proactor):主线程处理连接和读写,返回就绪事件,并将处理好的数据从内核态拷贝到用户态

reactor\proactor模型的区别

  • Reactor 是⾮阻塞同步⽹络模式,主线程(I/O处理单元)只负责监听文件描述符上是否有事件发生,有的话立即通知工作线程(逻辑单元 ),读写数据、接受新连接及处理客户请求均在工作线程中完成。通常由同步I/O实现。
  • Proactor 是异步⽹络模式 主线程和内核负责处理读写数据、接受新连接等I/O操作,工作线程仅负责业务逻辑,如处理客户请求。通常由异步I/O实现。

线程池: 线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。

线程池的定义:

template<typename T>
class threadpool{
public://构造函数 初始化参数threadpool( connection_pool *connpool,int thread_number=8, int max_requests=100000);//析构函数~threadpool();//向请求队列中插入任务请求bool append(T* request);private://工作线程运行的函数//它不断从工作队列中取出任务并执行之static void *worker(void *arg);void run();private://线程数
int m_thread_number;//队列中允许最大的请求数
int m_max_requests;//描述线程池的数组,大小为m_thread_number
pthread_t *m_threads;// 请求队列
std::list<T *> m_threads;//保护队列请求的互斥锁
locker m_queuelocker;//是否有任务需要处理 信号量
sem m_queuestat;//是否结束线程
bool m_stop;//数据库连接池
connection_pool *m_connPool;};

各成员函数的定义: 

//构造函数中创建线程池
template<typename T>
threadpool<T>::threadpool(connection_pool *connPool, int thread_number, int max_requests) : m_thread_number(thread_number), m_max_requests(max_requests), m_stop(false), m_threads(NULL),m_connPool(connPool){if(thread_number<=0||max_requests<=0)throw std::exception();//线程id初始化m_threads=new pthread_t[m_thread_number];if(!m_threads)throw std::exception();for(int i=0;i<thread_number;++i){//循环创建线程,并将工作线程按要求进行运行if(pthread_create(m_threads+i,NULL,worker,this)!=0){delete [] m_threads;throw std::exception();}//将线程进行分离后,不用单独对工作线程进行回收if(pthread_detach(m_threads[i])){delete[] m_threads;throw std::exception();}}
}//向任务队列添加任务
template<typename T>
bool threadpool<T>::append(T* request){ m_queuelocker.lock();//根据硬件,预先设置请求队列的最大值if(m_workqueue.size()>m_max_requests){m_queuelocker.unlock();return false;}//添加任务m_workqueue.push_back(request);m_queuelocker.unlock();//信号量提醒有任务要处理m_queuestat.post();return true;
}//线程处理
template<typename T>
void* threadpool<T>::worker(void* arg){//将参数强转为线程池类,调用成员方法threadpool* pool=(threadpool*)arg;pool->run();return pool;
}//run执行任务
template<typename T>
void threadpool<T>::run()
{while(!m_stop){    //信号量等待m_queuestat.wait();//被唤醒后先加互斥锁m_queuelocker.lock();if(m_workqueue.empty()){m_queuelocker.unlock();continue;}//从请求队列中取出第一个任务//将任务从请求队列删除T* request=m_workqueue.front();m_workqueue.pop_front();m_queuelocker.unlock();if(!request)continue;//从连接池中取出一个数据库连接request->mysql = m_connPool->GetConnection();//process(模板类中的方法,这里是http类)进行处理request->process();//将数据库连接放回连接池m_connPool->ReleaseConnection(request->mysql);}
}

  那么在客户端和服务器之间进行请求--响应时,有两种最常用到的请求方式是:

  • GET
  • POST

get请求:最常用于向服务器查询某些信息,例如查询操作、搜索操作、读操作等

post请求:通常用于向服务器发送应该被保存的数据,例如本项目中的登录注册请求

 HTTP请求报文由请求行(request line)、请求头部(header)空行请求数据四个部分组成。

  • 请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本。
    GET说明请求类型为GET,/562f25980001b1b106000338.jpg(URL)为要访问的资源,该行的最后一部分说明使用的是HTTP1.1版本。

  • 请求头部,紧接着请求行(即第一行)之后的部分,用来说明服务器要使用的附加信息。

  • HOST,给出请求资源所在服务器的域名。

  • User-Agent,HTTP客户端程序的信息,该信息由你发出请求使用的浏览器来定义,并且在每个请求中自动发送等。

  • Accept,说明用户代理可处理的媒体类型。

  • Accept-Encoding,说明用户代理支持的内容编码。

  • Accept-Language,说明用户代理能够处理的自然语言集。

  • Content-Type,说明实现主体的媒体类型。

  • Content-Length,说明实现主体的大小。

  • Connection,连接管理,可以是Keep-Alive或close。

  • 空行,请求头部后面的空行是必须的即使第四部分的请求数据为空,也必须有空行。

  • 请求数据也叫主体,可以添加任意的其他数据。 

服务器 响应报文:

HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

  • 状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
    第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为OK。

  • 消息报头,用来说明客户端要使用的一些附加信息。
    第二行和第三行为消息报头,Date:生成响应的日期和时间;Content-Type:指定了MIME类型的HTML(text/html),编码类型是UTF-8。

  • 空行,消息报头后面的空行是必须的。

  • 响应正文,服务器返回给客户端的文本信息。空行后面的html部分为响应正文。

HTTP有5种类型的状态码,具体的:

  • 1xx:指示信息--表示请求已接收,继续处理。

  • 2xx:成功--表示请求正常处理完毕。

    • 200 OK:客户端请求被正常处理。

    • 206 Partial content:客户端进行了范围请求。

  • 3xx:重定向--要完成请求必须进行更进一步的操作。

    • 301 Moved Permanently:永久重定向,该资源已被永久移动到新位置,将来任何对该资源的访问都要使用本响应返回的若干个URI之一。

    • 302 Found:临时重定向,请求的资源现在临时从不同的URI中获得。

  • 4xx:客户端错误--请求有语法错误,服务器无法处理请求。

    • 400 Bad Request:请求报文存在语法错误。

    • 403 Forbidden:请求被服务器拒绝。

    • 404 Not Found:请求不存在,服务器上找不到请求的资源。

  • 5xx:服务器端错误--服务器处理请求出错。

    • 500 Internal Server Error:服务器在执行请求时出现错误。

 2、连接数据库

  • 单例模式创建数据库连接池,避免频繁建立连接,用于后续web端登录和注册校验访问服务器数据库

 单例模式: 单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象。

3、实现Web端的登录和注册

  • web访问的欢迎界面为GET请求,登录和注册界面是POST请求

  • 欢迎界面有新用户(0)和已有账号(1)两个选项,若选择新用户,会跳转注册(3)界面,注册成功或选择已有账号,跳转登录(2)界面,注册或登录失败会提示失败,成功和失败为0,1

4、记录服务器的运行状态(日志记录设计)

  • 同步的方式下,工作线程直接写入日志文件

  • 异步会另外创建一个写线程,工作线程将要写的内容push进请求队列,通过写线程写入文件

  • 日志文件支持按日期分类,和超过最大行数自动创建新文件

5、处理非连接活动(定时器设计)

  • 由于非活跃连接占用了连接资源,严重影响服务器的性能,通过实现一个服务器定时器,处理这种非活跃连接,释放连接资源。

  • 利用alarm函数周期性地触发SIGALRM信号,该信号的信号处理函数利用管道通知主循环执行定时器链表上的定时任务.

先写到写到这,后面有时间再写!! 可以去原作者的公主号:两猿社

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/35825.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

RuoYi集成Drools,并实现数据库获取规则

Drools是一个开源的业务规则管理系统&#xff08;BRMS&#xff09;和规则引擎&#xff0c;它允许开发者以接近自然语言的形式定义业务规则&#xff0c;并将这些规则应用到应用程序中&#xff0c;实现业务逻辑的自动化和决策过程的优化。Drools基于Java语言开发&#xff0c;使用…

什么是API接口?如何调用API接口?

一、什么是 API 接口 定义 API&#xff08;Application Programming Interface&#xff09;即应用程序编程接口。它是一组定义好的规则和协议&#xff0c;允许不同的软件应用程序之间进行通信和交互。可以把 API 想象成是餐厅的服务员&#xff0c;当顾客&#xff08;一个软件应…

BurpSuite-6(验证码识别)

声明&#xff1a;学习视频来自b站up主 泷羽sec&#xff0c;如涉及侵权马上删除文章 感谢泷羽sec 团队的教学 视频地址&#xff1a;burp(6)暴力破解与验证码识别绕过_哔哩哔哩_bilibili 一、下载 github地址&#xff1a;GitHub - f0ng/captcha-killer-modified: captcha-killer…

【JavaEE】多线程(7)

一、JUC的常见类 JUC→java.util.concurrent&#xff0c;放了和多线程相关的组件 1.1 Callable 接口 看以下从计算从1加到1000的代码&#xff1a; public class Demo {public static int sum;public static void main(String[] args) throws InterruptedException {Thread …

Go 程序编译的步骤

Go程序编译的步骤 词法分析&#xff08;Lexical Analysis&#xff09; Go编译器首先对源代码进行词法分析&#xff0c;将代码拆分成标记&#xff08;tokens&#xff09;&#xff0c;这些标记是编程语言的最小单位&#xff0c;如关键字、变量名、操作符等。 作用&#xff1a;生…

SD-WAN服务商应该怎么挑选?

随着企业对高效、灵活网络的需求不断增长&#xff0c;SD-WAN技术逐渐成为市场热点。然而&#xff0c;面对众多服务商的多样化方案&#xff0c;如何选择最适合自己企业的服务商是一个重要的问题。SD-WAN服务商之间在技术、功能和服务等方面存在差异&#xff0c;企业需要根据自身…

三相LCL并网逆变器--仿真验证

根据前面的博客的讲解&#xff0c;确定主电路参数如下 名称/单位 值 名称/单位 值 额定功率P/kW 20 开关频率fsw/kHz 10 母线电压Udc/V 720 逆变器侧电感L1/mH 1.8 额定电流Ireated/A 30 并网测电感L2/mH 0.4 PCC电压/Us/V 220 滤波电容C/uF 4.7 电网频率f…

基于SpringBoot+Vue的服装生产管理系统-无偿分享 (附源码+LW+调试)

目录 1. 项目技术 2. 功能菜单 3. 部分功能截图 4. 研究背景 5. 设计原则 6. 可行性分析 6.1 技术可行性 6.2 经济可行性 6.3 操作可行性 7. 系统设计 7.1 系统流程和逻辑 7.2 系统结构 8. 数据库设计 8.1 数据库ER图 &#xff08;1&#xff09;管理员实体属性图…

告别充电焦虑:移动充电机器人的革命性解决方案

移动充电机器人作为新能源汽车领域的黑科技&#xff0c;正逐渐崭露头角。它的出现为电动汽车充电带来了全新的解决方案&#xff0c;解决了传统充电方式的诸多痛点。 新能源汽车具有诸多优点&#xff0c;如科技含量高、噪音小、使用成本低等&#xff0c;但 “续航焦虑”“充电焦…

【启明智显分享】ESP32-P4方案4.3寸触摸屏来袭!支持MIPI CSI摄像头接口

家人们&#xff0c;你们一直在等的ESP32-P4方案4.3寸触摸屏它来了&#xff01; 启明智显全新推出的ESP32-P4 4.3寸IPS触摸屏&#xff0c;搭载强大的双核400MHz RISC-V处理器&#xff0c;配备32MB PSRAM和16MB FLASH&#xff0c;分辨率800*480&#xff0c;性能和视觉体验再次突…

国内管理咨询公司哪家落地辅导做的好?

在当今快速变化的市场环境中&#xff0c;企业面临着前所未有的竞争压力与转型挑战。为了在这场没有硝烟的战争中脱颖而出&#xff0c;许多企业开始寻求外部专业力量的帮助&#xff0c;以期通过科学的管理咨询实现战略升级和业绩突破。而在众多的管理咨询公司中&#xff0c;思博…

【力扣】824.山羊拉丁文

问题描述 思路解析 首先把字符串通过空格分隔开来&#xff0c;用一个字符串数组来接收。因为要经常加上字符&#xff0c;所以使用 StringBuilder &#xff0c;最后再进行转化然后检验首字母是否为元音&#xff0c;因为元音字母加上其大小写比较多&#xff0c;所以用一个字符串…

二叉树OJ题

带值的多层递归 对二叉树的递归性质做一个更好的补充。 提到二叉树的递归&#xff0c;我们首相想到的就是二叉树的深度优先遍历&#xff08;根遍历&#xff09;。对于求二叉树结点的个数&#xff0c;同样可以用递归来实现&#xff08;带值的多层递归&#xff09;。 1、二叉树的…

算法刷题Day11: BM33 二叉树的镜像

点击题目链接 思路 转换为子问题&#xff1a;左右子树相反转。遍历手法&#xff1a;后序遍历 代码 class Solution:def Transverse(self,root: TreeNode):if root None:return rootnewleft self.Transverse(root.left)newright self.Transverse(root.right)# 对root节点…

leetcode104.二叉树的最大深度

给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上的节点数。 示例 1&#xff1a; 输入&#xff1a;root [3,9,20,null,null,15,7] 输出&#xff1a;3示例 2&#xff1a; 输入&#xff1a;root [1,null,2] 输出…

一体式远程IO(三格电子)

一、功能概述 1.1 设备结构 本产品是三格电子研发生产的一体式远程 IO 。通信有 Profinet 、EtherCAT、 EtherNet/IP 三种。IO 接口有&#xff1a;32 路数字量输入、32 路数字量输出 NPN、32 路数字量输出 PNP、16 路数字量输入 16 路数字量输出 NPN、16 路数字量输入 16 路数…

零碳新墅居 | 重新定义零碳美学,阳光新能源打开高端品智生活新可能

当下人们对于居住生活的期待&#xff0c;不再仅限于实用与舒适&#xff0c;更追求绿色、低碳、美观与智能的结合。在这一趋势下&#xff0c;零碳墅居生活正成为引领高端生活风尚的新范式。 11月初&#xff0c;PChouse太平洋家居网携手阳光家庭能源官宣成立的“零碳新墅居设计智…

库存看板在现代企业管理中的作用:如何通过看板系统提升库存流动性与效率?

库存管理是现代企业管理中的重要环节&#xff0c;尤其对于制造业、零售业及电商平台等行业&#xff0c;如何高效、精准地管理库存&#xff0c;避免过度库存积压或库存不足的情况&#xff0c;直接影响到公司的运营成本、资金周转、客户满意度等多个方面。而在众多库存管理方法中…

免押租赁系统助力资源共享新模式开创便捷租赁体验

内容概要 免押租赁系统&#xff0c;听起来是不是很酷&#xff1f;这个新模式不仅仅是为了让你少花点钱&#xff0c;它的到来简直就是个革命&#xff01;以前&#xff0c;租东西时首先想到的就是那个令人心痛的押金&#xff0c;对吧&#xff1f;但现在&#xff0c;免押租赁系统…