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.mzph.cn/bicheng/63585.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux】开机进入grub/怎么办?

开机进入grub/怎么办&#xff1f; 1、利用ls命令查看磁盘 ls执行后提示&#xff1a; &#xff08;hd0&#xff09;&#xff08;hd0,msdo1&#xff09;&#xff08;hd0,msdo3&#xff09;&#xff08;hd0,msdo5&#xff09;(lvm-cd****-Home)(lvm-cd****-Root)2、利用cat查看f…

Java版-图论-拓扑排序与有向无环图

拓扑排序 拓扑排序说明 对一个有向无环图(Directed Acyclic Graph简称DAG)G进行拓扑排序,是将G中所有顶点排成一个线性序列,使得图中任意一对顶点u和v,若边<u,v>∈E(G),则u在线性序列中出现在v之前。通常,这样的线性序列称为满足拓扑次序(Topological Order)的序列…

前沿重器[56] | google search: 用emb模型做个性化语言prompt

前沿重器 栏目主要给大家分享各种大厂、顶会的论文和分享&#xff0c;从中抽取关键精华的部分和大家分享&#xff0c;和大家一起把握前沿技术。具体介绍&#xff1a;仓颉专项&#xff1a;飞机大炮我都会&#xff0c;利器心法我还有。&#xff08;算起来&#xff0c;专项启动已经…

【adb】iqoo系统精简垃圾内置应用

免责声明 这个得谨慎点&#xff0c;虽然我验证过两部手机和不同版本的系统&#xff0c;但是总会有特殊的存在、 本教程来自于互联网搜集整理&#xff0c; 按照本教程造成的用户设备硬件或数据损失&#xff0c;本人概不承担任何责任&#xff0c;如您不同意此协议&#xff0c;请不…

用最小的代价解决mybatis-plus关于批量保存的性能问题

1.问题说明 问题背景说明&#xff0c;在使用达梦数据库时&#xff0c;mybatis-plus的serviceImpl.saveBatch()方法或者updateBatchById()方法的时候&#xff0c;随着数据量、属性字段的增加&#xff0c;效率越发明显的慢。 serviceImpl.saveBatch(); serviceImpl.updateBatch…

使用 EasyExcel 提升 Excel 处理效率

目录 前言1. EasyExcel 的优点2. EasyExcel 的功能3. 在项目中使用 EasyExcel3.1 引入依赖3.2 实体类的定义与注解3.3 工具类方法的实现3.4 在 Controller 中使用 4. 总结5. 参考地址 前言 在日常开发中&#xff0c;Excel 文件的处理是不可避免的一项任务&#xff0c;特别是在…

Linux上的C语言编程实践

说明&#xff1a; 这是个人对该在Linux平台上的C语言学习网站笨办法学C上的每一个练习章节附加题的解析和回答 ex1: 在你的文本编辑器中打开ex1文件&#xff0c;随机修改或删除一部分&#xff0c;之后运行它看看发生了什么。 vim ex1.c打开 ex1.c 文件。假如我们删除 return 0…

Elasticsearch vs 向量数据库:寻找最佳混合检索方案

图片来自Shutterstock上的Bakhtiar Zein 多年来&#xff0c;以Elasticsearch为代表的基于全文检索的搜索方案&#xff0c;一直是搜索和推荐引擎等信息检索系统的默认选择。但传统的全文搜索只能提供基于关键字匹配的精确结果&#xff0c;例如找到包含特殊名词“Python3.9”的文…

SpringCloudAlibaba学习路线:全面掌握微服务核心组件

大家好&#xff0c;我是袁庭新。 星友给我留言说&#xff1a;“新哥&#xff0c;我最近准备开始学Spring Cloud Alibaba技术栈&#xff0c;计划冲刺明年的春招&#xff0c;想全面掌握微服务核心组件。但不知从何学起&#xff0c;没有一个有效的学习路线&#xff0c;我需要学习…

Java阶段三06

第3章-第6节 一、知识点 理解MVC三层模型、理解什么是SpringMVC、理解SpringMVC的工作流程、了解springMVC和Struts2的区别、学会使用SpringMVC封装不同请求、接收参数 二、目标 理解MVC三层模型 理解什么是SpringMVC 理解SpringMVC的工作流程 学会使用SpringMVC封装请求…

租赁系统|租赁小程序|租赁小程序成品

租赁系统是现代企业管理中不可缺少的数字化工具&#xff0c;它通过高效的信息整合与流程管理&#xff0c;为企业带来极大的便利和效益。一个完善的租赁系统开发应具备以下必备功能&#xff1a; 一、用户管理 用户管理模块负责系统的访问控制&#xff0c;包括用户注册、登录验证…

product/admin/list?page=0size=10field=jancodevalue=4562249292272

文章目录 1、ProductController2、AdminCommonService3、ProductApiService4、ProductCommonService5、ProductSqlService https://api.crossbiog.com/product/admin/list?page0&size10&fieldjancode&value45622492922721、ProductController GetMapping("ad…

java+ssm+mysql美妆论坛

项目介绍&#xff1a; 使用javassmmysql开发的美妆论坛&#xff0c;系统包含超级管理员&#xff0c;系统管理员、用户角色&#xff0c;功能如下&#xff1a; 用户&#xff1a;主要是前台功能使用&#xff0c;包括注册、登录&#xff1b;查看论坛板块和板块下帖子&#xff1b;…

Java-21 深入浅出 MyBatis - 手写ORM框架2 手写Resources、MappedStatment、XMLBuilder等

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

专业135+总分400+华中科技大学824信号与系统考研经验华科电子信息与通信工程,真题,大纲,参考书。

考研成功逆袭985&#xff0c;上岸华科电子信息&#xff0c;初试专业课824信号与系统135&#xff0c;总分400&#xff0c;成绩还是很满意&#xff0c;但是也有很多遗憾&#xff0c;总结一下自己的复习&#xff0c;对于大家复习给些参考借鉴&#xff0c;对自己考研画个句号&#…

ElementUI:el-tabs 切换之前判断是否满足条件

<div class"table-card"><div class"card-steps-class"><el-tabsv-model"activeTabsIndex":before-leave"beforeHandleTabsClick"><el-tab-pane name"1" label"基础设置"><span slot&…

java中的数组(2)

大家好&#xff0c;我们今天继续来看java中数组这方面的知识点&#xff0c;那么话不多说&#xff0c;我们直接开始。 一.数组的使用 1.数组中元素访问 数组在内存中是一段连续的空间,空间的编号都是从0开始的,依次递增,数组可以通过下标访问其任意位置的元素. 也可以进行修改…

#渗透测试#红蓝对抗#SRC漏洞挖掘# Yakit(6)进阶模式-Web Fuzzer(下)

免责声明 本教程仅为合法的教学目的而准备&#xff0c;严禁用于任何形式的违法犯罪活动及其他商业行为&#xff0c;在使用本教程前&#xff0c;您应确保该行为符合当地的法律法规&#xff0c;继续阅读即表示您需自行承担所有操作的后果&#xff0c;如有异议&#xff0c;请立即停…

Python 爬虫 (1)基础 | XHR

一、XHR 1、概念 XHR&#xff0c;全称XMLHttpRequest&#xff0c;是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。它允许网页的JavaScript代码与服务器进行异步通信&#xff0c;即在发送请求后&#xff0c;浏览器不会阻塞用户的后续操作&#xff0…

二进制部署Prometheus+grafana+alertmanager+node_exporter

Prometheus 是一个开源的监控和告警工具包&#xff0c;旨在提供高可靠性和可扩展性。它最初由 SoundCloud 开发&#xff0c;现已成为云原生计算基金会&#xff08;CNCF&#xff09;的一部分。以下是 Prometheus 的一些关键特性和概念&#xff1a; 1. **时间序列数据库**&#…