Web服务器实现|基于阻塞队列线程池的Http服务器|线程控制|Http协议
基于阻塞队列生产者消费者模型线程池的多线程Web服务器
代码地址:WebServer_GitHub_Addr
README
摘要
本实验通过C++语言,实现了一个基于阻塞队列线程池的多线程Web服务器。该服务器支持通过http协议发送报文,跨主机抓取服务器上特定资源。与此同时,该Web服务器后台通过C++语言,通过原生系统线程调用
pthread.h
,实现了一个基于阻塞队列的线程池,该线程池支持手动设置线程池中线程数量。与此同时,该线程池通过维护任务队列,每次Web服务器获取请求时,后台服务器就会将特定请求对应的特定任务加载到线程池中,等待线程池中线程的调用。由于在本项目中,每次的远端抓取都是短链接,因此在理论上,该Web服务器可以接收无数个请求。按照实验要求,本Web服务器可以接受并解析 HTTP 请求,然后从服务器的文件系统中读取被 HTTP 请求的文件,并根据该文件是否存在而向客户端发送正确的响应消息。
在服务端终端中,服务器能够按照不同等级打印日志信息,并且在收到请求时打印报文信息。
执行效果
定义代码下./wwwroot
目录为服务器资源的根目录,定义./wwwroot/index.html
为服务器主页。
- 当客户端请求资源存在时,服务器将返回对应资源。
- 当客户端请求路径是
/
根目录时,服务器默认返回主页。 - 当客户端申请路径不存在时,服务端返回
./wwwroot/error/404.html
作为返回资源。
服务器开机
浏览器远端连接
服务器是腾讯云的远端服务器,浏览器是本地浏览器。这是一个跨局域网的跨主机测试。
客户端申请不存在的资源
客户端发送请求时,后台服务器所获取到的报文
多个客户端发起请求
该服务器理论上支持无数个短链接进行请求
环境准备
系统: Linux version 3.10.0-1160.11.1.el7.x86_64
编译器版本: gcc version 4.8.5 20150623 (Red Hat 4.8.5-44) (GCC)
资源文件/源文件准备目录结构
MyThreadPool
目录维护手写的线程池源代码./wwwroot/
目录维护服务器资源,其中./wwwroot/
是客户端访问的根目录- 其他文件均为HttpServer源代码
RUN
服务默认端口号:8080
tips: 如果运行过程中二进制程序HttpServer没有可执行权限 chmod 755 HttpServer
生成可执行:make clean;make
启动服务器:./HttpServer 8080
在浏览器中输入:ip:port
即可访问服务器主页
或输入:ip:port/index.html
也可以访问服务器主页
输入不存在的路径: 43:xxx:xxx:xxx:8080/a.txt
代码详解
Web服务器实现架构
第一层封装:对套接字的基本操作进行封装 Sock.hpp
/* 具体接口实现可以见源代码 */
/* Sock.hpp */
class Sock
{
private:const static int gbacklog = 20;
public:Sock() {}~Sock() {}/* 以下接口内部调用的都是套接字的底层调用 */int Socket(); // 创建获取套接字 -- 返回值: 监听套接字void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0"); // 绑定void Listen(int sock); // 设置监听状态int Accept(int listensock, std::string *ip, uint16_t *port); // 接收连接 -- 返回值: 服务套接字bool Connect(int sock, const std::string &server_ip, const uint16_t &server_port);
};
第二层封装:对Http服务器的封装 HttpServer.hpp
- 通过维护
Task
类,可以把线程所需要执行的动作进行封装。通过Task
类的operator()()
重载,线程可以直接执行内部绑定好的方法。 - 通过维护
HttpServer
类,当初始化并执行HttpServer::Start()
后,启动线程池,Accept成功后,把相对应的任务加载到线程池中,等待线程池中线程的调用。
#ifndef __Yufc_HttpServer
#define __Yufc_HttpServer#include "Sock.hpp"
#include <functional>
#include "MyThreadPool/threadPool.hpp"
using func_t = std::function<void(int)>;
/* 任务类 */
struct Task
{
public:func_t func__;int sock__;
public:Task() {}Task(func_t func, int sock) : func__(func), sock__(sock) {}void operator()(){func__(sock__);}
};
/* http 服务器类 */
class HttpServer
{
private:int __listen_sock;uint16_t __port;Sock __sock;func_t __func;yufc_thread_pool::ThreadPool<Task> *__thread_pool = yufc_thread_pool::ThreadPool<Task>::getThreadPool();
public:HttpServer(const uint16_t &port, func_t func) : __port(port), __func(func);~HttpServer();void Start();
};
#endif
Http服务器的启动 HttpServer.cc
执行HttpServer.cc
后,main()
创建一个服务器对象指针,并调用其中的Start()
启动服务器。
void HandlerHttpRequest(int sockfd);
函数维护每个线程所要执行的任务,其中包括以下内容。
- 读取Http请求
- 解析Http报文
- 创建一个http响应
- 发送响应至客户端
/* 一般http都要有自己的web根目录 */
#define ROOT "./wwwroot"
/* 如果客户端只请求了一个/ ,一般返回默认首页 */
#define HOME_PAGE "index.html"
void HandlerHttpRequest(int sockfd); // 具体实现可见源代码
int main(int argc, char **argv)
{if (argc != 2){Usage(argv[0]);exit(0);}std::unique_ptr<HttpServer> httpserver(new HttpServer(atoi(argv[1]), HandlerHttpRequest));httpserver->Start();return 0;
}
ulity.hpp
提供Util
类,里面提供static void cutString(const std::string &s, const std::string &sep, std::vector<std::string> *out)
接口。在解析http报文时,可以把传入的字符串s
按照间隔为sep
的方式进行切割,并把结果放到out
里面。
Usage.hpp
提供Usage
函数,作为HttpServer的使用手册。
Log.hpp
提供void logMessage(int level, const char *format, ...)
函数,负责打印服务器日志信息。
日志等级有:
DEBUG 调试
NORMAL 正常运行
WARNING 警告 – 进程继续运行
ERROR 非致命错误 – 进程继续运行
FATAL 致命错误 – 进程终止
线程池实现架构
thread.hpp
- 对原生线程进行了简单封装
typedef void*(*func_t_)(void*); // 函数指针
class ThreadData
{
public:void* __args;std::string __name;
};
class Thread
{
private:std::string __name; // 线程名字pthread_t __tid; // 线程tidfunc_t_ __func; // 线程要调用的函数ThreadData __tdata; // 线程数据
public:Thread(int num, func_t_ callback, void* args); // num-自定义的线程编号 callback-线程要执行的任务 args-callback的参数~Thread();void start();void join();std::string name(); // 返回线程名字
};
lockGuard.hpp
- 用RAII的锁的封装风格对互斥锁进行封装
//封装一个锁
class Mutex
{
private:pthread_mutex_t *__pmtx;
public:Mutex(pthread_mutex_t *mtx):__pmtx(mtx){}~Mutex(){}
public:void lock() // 加锁{pthread_mutex_lock(__pmtx);}void unlock() // 解锁{pthread_mutex_unlock(__pmtx);}
};
class lockGuard
{
public:lockGuard(pthread_mutex_t *mtx):__mtx(mtx){__mtx.lock();}~lockGuard(){__mtx.unlock();}
private:Mutex __mtx;
};
threadPool.hpp
线程池整体架构
#define _DEBUG_MODE_ false
const int g_thread_num = 3; // 默认3个线程
/* 具体实现可见源代码 */
namespace yufc_thread_pool
{template <class T>class ThreadPool{private:std::vector<Thread *> __threads; // 线程们int __num; // 线程数量std::queue<T> __task_queue; // 任务队列pthread_mutex_t __lock; // 互斥锁pthread_cond_t __cond; // 条件变量static ThreadPool<T> *thread_ptr; // 单例模式static pthread_mutex_t __mutexForPool; // 保护getThreadPool对互斥锁private://构造放成私有的 -- 让线程池成为单例模式ThreadPool(int thread_num = g_thread_num);ThreadPool(const ThreadPool<T>& other) = delete;const ThreadPool<T>& operator=(const ThreadPool<T>& other) = delete;public:~ThreadPool();public:static ThreadPool<T>* getThreadPool(int num = g_thread_num); // 懒汉模式--获取线程池void run(); // 启动线程池void pushTask(const T &task); // 向线程池添加任务static void *routine(void *args); // 线程要做的事public:// 需要一批,外部成员访问内部属性的接口提供给static的routine,不然routine里面没法加锁// 下面这些接口,都是没有加锁的,因为我们认为,这些函数被调用的时候,都是在安全的上下文中被调用的// 因为这些函数调用之前,已经加锁了,调用完,lockGuard自动解锁pthread_mutex_t *getMutex();void waitCond(); // 等待条件变量就绪bool isEmpty(); // 判断任务队列是否为空T getTask(); // 获取一个任务};template <typename T>ThreadPool<T> *ThreadPool<T>::thread_ptr = nullptr;template <typename T>pthread_mutex_t ThreadPool<T>::__mutexForPool = PTHREAD_MUTEX_INITIALIZER;//static/全局可以这样初始化,这把锁是用来保护getThreadPool的
}
实现的一些具体细节
Http报文解析
因为我们收到的都是http请求,因此,对http请求报文进行解析,可以获得客户端信息。
以下是一段完整的http报文
GET /index.html HTTP/1.1
Host: 43.xxx.xxx.xxx:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
因为我们需要提取客户需要访问的资源,因此我们需要提取客户输入的资源路径。
http报文的第一行的第二个字段,则表示客户端需要提取资源的路径。
与此同时,http报文每行的间隔是特殊字符串\r\n
。
因此在HttpServer.cc
中的HandlerHttpRequest
函数中,我们首先把http报文中的每一行分出来,并把第一行的第二个字符串提取出来,就能得到资源的路径。
单例模式的线程池
在本次实验中,我们希望一个进程只能产生一个线程池,因此我们把线程池设置成单例模式。这是一个懒汉方式的单例模式。
**懒汉:**第一次需要这个对象的时候构建该对象。
**饿汉:**在main()
执行之前构建该对象。
-
把构造函数私有化
-
通过
static ThreadPool<T>* getThreadPool(int num = g_thread_num)
来获取线程池在这个接口中,如果执行流是第一次执行该函数,则该函数会构造一个线程池对象并返回它的指针。如果执行流不是第一次执行该函数,则该函数则会返回
this
,即自己。
另外,为了保证线程池运行时的线程安全,在线程池中多个操作中添加了互斥锁对操作进行保护。
实验结果分析和思考
改进部分:
- 实现多执行流,支持多个客户端进行连接。
- 封装线程池,使得服务器可以并行地对http请求进行响应
不足部分:
- 给客户端返回对报文是静态网页,暂时没有实现可以支持多个客户端之间共同交互的功能
- 回应报文比较粗糙,可以进一步美化。
- 可以进一步优化服务器,把服务器进程设置成守护进程,让它长期执行并提供服务。
- 没有模拟丢包的情况
相关文章:

Web服务器实现|基于阻塞队列线程池的Http服务器|线程控制|Http协议
基于阻塞队列生产者消费者模型线程池的多线程Web服务器 代码地址:WebServer_GitHub_Addr README 摘要 本实验通过C语言,实现了一个基于阻塞队列线程池的多线程Web服务器。该服务器支持通过http协议发送报文,跨主机抓取服务器上特定资源。与…...

【C++】运算符重载(日期类的实现)
【C】运算符重载(日期类的实现) 前言运算符重载operator全局和类中 日期类的实现成员变量的确定构造函数拷贝构造 运算符重载部分的重载思路实现GETmonthdayoperator 的重载思路实现 -的与-的重载实现 各个比较运算符的重载实现 前置与后置实现 …...

【Linux】线程分离 | 线程库 | C++调用线程 | 线程局部存储
文章目录 1. 线程分离1. 为什么要线程分离?2. 具体使用3. 为什么有时候分离在调用join 会正常运行? 2. 如何理解线程库?如何理解 先描述 在组织? 3. C中使用多线程4. 线程局部存储局部变量全局变量 1. 线程分离 1. 为什么要线程分…...

c++ ffmpeg 浅谈YUV444、YUV422、YUV420(2)
本期将会给大家介绍YUV相关基础知识,同时也介绍威创网络分布式系统的卓越色彩处理技术。 1.什么是YUV色彩空间 2.YUV采样格式 3.YUV不同采样格式对图像画质的影响分析 一、什么是YUV色彩空间? YUV是视频、图片、相机等应用中常常使用的一类图像格式,是…...
Redis在Windows下安装配置教程
Redis是一个开源的高性能键值对存储数据库,常用于缓存、消息队列等场景。本文将介绍如何在Windows系统下安装配置Redis。 1. 下载地址 Redis官方网站提供了Windows版本的安装包下载地址,网址为:https://github.com/tporadowski/redis/relea…...

数据库服务器
数据库服务器,联系Web服务器与DBMS的中间件是负责处理所有的应用程序服务器,包括在web服务器和后台的应用程序或数据库之间的事务处理和数据访问。 基本信息 中文名 数据库服务器 外文名 database server 功能 数据库服务器建立在数据库系统基础上&a…...

VS输出路径和生成事件
在生成时,常常希望输出文件夹整洁,因此需要设置dll或exe输出位置,同时也希望对一些文件做一些特殊操作 VS的 UI 常用缩写 “./”:代表目前所在的目录。 " . ./"代表上一层目录。 “/”:代表根目录。 生成…...

从 WebKit 看浏览器内核架构
浏览器常见的浏览器内核有:Blink、WebKit、Gecko、Trident 等,目前 WebKit 内核占据了非常大的的市场,包括 Chrome、Safari、安卓浏览器等市面上的主流浏览器,都使用了 WebKit 内核。 从 WebKit 看浏览器内核架构 既然 WebKit 这么…...
使用原生的 JavaScript,不依赖于任何特定的库与 ROSBridge进行通信
使用原生的 JavaScript,不依赖于任何特定的库与 ROSBridge进行通信 创建与 ROS 的连接: var rosbridge_url "ws://localhost:9090"; var ws new WebSocket(rosbridge_url);ws.onopen function(event) {console.log(Connected to rosbri…...

MATLAB第十章_图像处理算法
目录 图像处理算法 图像处理基础 图像处理函数 默认显示方式 添加颜色条 显示多帧图像 显示动画 三维材质图像 图像的直方图 灰度变换 均衡直方图 图像处理应用 图像增强 图像重建 图像变换 图像压缩 图像分割 图像边缘检测 图像识别 图像处理算法 图像处理…...

RobotFramework接口测试方案
1. Robot FrameWork介绍 1.1 介绍 Robot Framework是用于验收测试和回归测试的通用测试自动化框架。它使用易于理解的表格数据语法,非常友好的实现了关键字驱动和数据驱动模式。它的测试功能可以通过使用Python或Java实现的测试库进行扩展,用户可以使用…...

chatgpt赋能python:Python中日期转换:从字符串到日期对象
Python中日期转换:从字符串到日期对象 作为一个经验丰富的Python工程师,日期转换在我的日常编码工作中经常遇到。Python提供了一些内置函数和模块,可以将字符串转换为日期对象或将日期对象格式化为特定的字符串。本篇文章将带您深入了解Pyth…...
k8s 1.27新特性in-place使用方法:避坑指南(官方文档有坑,已提issue)
背景 按照官方文档试用新版的in-place特性时,一字不差地执行了,但是却出现了执行失败的情况: 执行kubectl -n qos-example patch pod qos-demo-5 --patch {"spec":{"containers":[{"name":"qos-demo-ct…...

网络传输(传输介质、通信方式、交换方式)
目录 一、传输介质1.双绞线2.网线安装3.光纤4.无线信道 二、通信方式、交换方式1.通信方式2.同步方式3.交换方式 一、传输介质 1.双绞线 双绞线:将多根铜线按规则缠绕在一起,能够减少干扰;分为无屏蔽双绞线UTP和屏蔽双绞线STP,都…...
【Unity】Time.deltaTime有什么用?看完你就明白
大多数刚开始使用 Unity 的人(包括我),都会对Time.deltaTime感到迷惑。 看完本文,你就会明白Time.deltaTime的定义及作用。 1、deltaTime是什么? 根据定义,Time.deltaTime是每一帧之间的时间间隔(以秒为单位)。 这有助于我们使游戏与帧数无关,也就是说,无论 fps 是…...

vue实现用户动态权限登录
一、使用vueelementUI搭登录框架,主要就是1、2、3、4 配置: ①vue.config.js use strict const path require(path)function resolve(dir) {return path.join(__dirname, dir) }// All configuration item explanations can be find in https://cli.v…...
ONNX模型修改为自定义节点
参考一 首先,需要将ONNX模型中的节点修改为自定义节点。要实现这一点,您需要了解自定义节点的定义和如何在ONNX中使用它们。ONNX定义了一个自定义运算符的接口,您可以使用该接口定义自己的运算符,并将其编译为ONNX模型可以识别的…...

内存对齐原则
struct (1)结构体第一个数据成员放在offset为0的地方,后面每个成员相对于结构体首地址的偏移量(offset)都是成员大小(该变量类型所占字节)的整数倍,如有需要编译器会在成员之间加上填…...

Java SPI 一 之SPI(Service Provider Interface)进阶 AutoService
一、SPI(Service Provider Interface) 1.1 介绍 SPI(Service Provider Interface),是JDK内置的一种 服务提供发现机制(为某个接口寻找服务实现的机制),可以用来启用框架扩展和替换组件,其…...

C++ list类成员函数介绍
目录 🤔list模板介绍: 🤔特点: 🤔list内存结构图解: 🤔 list的成员函数: 😊list构造函数: 🔍代码示例: 🔍运行结果&…...
Java如何权衡是使用无序的数组还是有序的数组
在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...

SiFli 52把Imagie图片,Font字体资源放在指定位置,编译成指定img.bin和font.bin的问题
分区配置 (ptab.json) img 属性介绍: img 属性指定分区存放的 image 名称,指定的 image 名称必须是当前工程生成的 binary 。 如果 binary 有多个文件,则以 proj_name:binary_name 格式指定文件名, proj_name 为工程 名&…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
C++.OpenGL (14/64)多光源(Multiple Lights)
多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
LangFlow技术架构分析
🔧 LangFlow 的可视化技术栈 前端节点编辑器 底层框架:基于 (一个现代化的 React 节点绘图库) 功能: 拖拽式构建 LangGraph 状态机 实时连线定义节点依赖关系 可视化调试循环和分支逻辑 与 LangGraph 的深…...

Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...