当前位置: 首页 > news >正文

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服务器 代码地址&#xff1a;WebServer_GitHub_Addr README 摘要 本实验通过C语言&#xff0c;实现了一个基于阻塞队列线程池的多线程Web服务器。该服务器支持通过http协议发送报文&#xff0c;跨主机抓取服务器上特定资源。与…...

【C++】运算符重载(日期类的实现)

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

【Linux】线程分离 | 线程库 | C++调用线程 | 线程局部存储

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

c++ ffmpeg 浅谈YUV444、YUV422、YUV420(2)

本期将会给大家介绍YUV相关基础知识&#xff0c;同时也介绍威创网络分布式系统的卓越色彩处理技术。 1.什么是YUV色彩空间 2.YUV采样格式 3.YUV不同采样格式对图像画质的影响分析 一、什么是YUV色彩空间? YUV是视频、图片、相机等应用中常常使用的一类图像格式&#xff0c;是…...

Redis在Windows下安装配置教程

Redis是一个开源的高性能键值对存储数据库&#xff0c;常用于缓存、消息队列等场景。本文将介绍如何在Windows系统下安装配置Redis。 1. 下载地址 Redis官方网站提供了Windows版本的安装包下载地址&#xff0c;网址为&#xff1a;https://github.com/tporadowski/redis/relea…...

数据库服务器

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

VS输出路径和生成事件

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

从 WebKit 看浏览器内核架构

浏览器常见的浏览器内核有&#xff1a;Blink、WebKit、Gecko、Trident 等&#xff0c;目前 WebKit 内核占据了非常大的的市场&#xff0c;包括 Chrome、Safari、安卓浏览器等市面上的主流浏览器&#xff0c;都使用了 WebKit 内核。 从 WebKit 看浏览器内核架构 既然 WebKit 这么…...

使用原生的 JavaScript,不依赖于任何特定的库与 ROSBridge进行通信

使用原生的 JavaScript&#xff0c;不依赖于任何特定的库与 ROSBridge进行通信 创建与 ROS 的连接&#xff1a; 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是用于验收测试和回归测试的通用测试自动化框架。它使用易于理解的表格数据语法&#xff0c;非常友好的实现了关键字驱动和数据驱动模式。它的测试功能可以通过使用Python或Java实现的测试库进行扩展&#xff0c;用户可以使用…...

chatgpt赋能python:Python中日期转换:从字符串到日期对象

Python中日期转换&#xff1a;从字符串到日期对象 作为一个经验丰富的Python工程师&#xff0c;日期转换在我的日常编码工作中经常遇到。Python提供了一些内置函数和模块&#xff0c;可以将字符串转换为日期对象或将日期对象格式化为特定的字符串。本篇文章将带您深入了解Pyth…...

k8s 1.27新特性in-place使用方法:避坑指南(官方文档有坑,已提issue)

背景 按照官方文档试用新版的in-place特性时&#xff0c;一字不差地执行了&#xff0c;但是却出现了执行失败的情况&#xff1a; 执行kubectl -n qos-example patch pod qos-demo-5 --patch {"spec":{"containers":[{"name":"qos-demo-ct…...

网络传输(传输介质、通信方式、交换方式)

目录 一、传输介质1.双绞线2.网线安装3.光纤4.无线信道 二、通信方式、交换方式1.通信方式2.同步方式3.交换方式 一、传输介质 1.双绞线 双绞线&#xff1a;将多根铜线按规则缠绕在一起&#xff0c;能够减少干扰&#xff1b;分为无屏蔽双绞线UTP和屏蔽双绞线STP&#xff0c;都…...

【Unity】Time.deltaTime有什么用?看完你就明白

大多数刚开始使用 Unity 的人(包括我),都会对Time.deltaTime感到迷惑。 看完本文,你就会明白Time.deltaTime的定义及作用。 1、deltaTime是什么? 根据定义,Time.deltaTime是每一帧之间的时间间隔(以秒为单位)。 这有助于我们使游戏与帧数无关,也就是说,无论 fps 是…...

vue实现用户动态权限登录

一、使用vueelementUI搭登录框架&#xff0c;主要就是1、2、3、4 配置&#xff1a; ①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模型修改为自定义节点

参考一 首先&#xff0c;需要将ONNX模型中的节点修改为自定义节点。要实现这一点&#xff0c;您需要了解自定义节点的定义和如何在ONNX中使用它们。ONNX定义了一个自定义运算符的接口&#xff0c;您可以使用该接口定义自己的运算符&#xff0c;并将其编译为ONNX模型可以识别的…...

内存对齐原则

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

Java SPI 一 之SPI(Service Provider Interface)进阶 AutoService

​ 一、SPI&#xff08;Service Provider Interface&#xff09; 1.1 介绍 SPI&#xff08;Service Provider Interface&#xff09;&#xff0c;是JDK内置的一种 服务提供发现机制(为某个接口寻找服务实现的机制)&#xff0c;可以用来启用框架扩展和替换组件&#xff0c;其…...

C++ list类成员函数介绍

目录 &#x1f914;list模板介绍&#xff1a; &#x1f914;特点&#xff1a; &#x1f914;list内存结构图解&#xff1a; &#x1f914; list的成员函数&#xff1a; &#x1f60a;list构造函数&#xff1a; &#x1f50d;代码示例&#xff1a; &#x1f50d;运行结果&…...

远程工作事故树:一次误删库引发的跨国追责

远程协作下的“脆弱”系统深夜&#xff0c;伦敦办公室的数据库工程师在连续工作十二小时后&#xff0c;敲下了一条他以为指向“测试环境”的删除命令。与此同时&#xff0c;上海的测试团队正在为次日的上线进行最后一轮回归验证。六小时后&#xff0c;当阳光照进浦东的办公室&a…...

开源轻量模型新星:Qwen1.5-0.5B-Chat部署趋势分析

开源轻量模型新星&#xff1a;Qwen1.5-0.5B-Chat部署趋势分析 1. 项目概述 Qwen1.5-0.5B-Chat是阿里通义千问开源系列中的轻量级智能对话模型&#xff0c;基于ModelScope&#xff08;魔塔社区&#xff09;生态构建。这个仅有5亿参数的模型在保持良好对话能力的同时&#xff0…...

Qwen3-1.7B能做什么?实测写邮件、生成故事、智能聊天

Qwen3-1.7B能做什么&#xff1f;实测写邮件、生成故事、智能聊天 1. 认识Qwen3-1.7B Qwen3&#xff08;千问3&#xff09;是阿里巴巴集团开源的新一代通义千问大语言模型系列中的一员&#xff0c;1.7B版本虽然参数量不大&#xff0c;但在日常应用中表现出色。这个17亿参数的模…...

深入理解请求限流算法的实现细节

在技术领域&#xff0c;我们常常被那些闪耀的、可见的成果所吸引。今天&#xff0c;这个焦点无疑是大语言模型技术。它们的流畅对话、惊人的创造力&#xff0c;让我们得以一窥未来的轮廓。然而&#xff0c;作为在企业一线构建、部署和维护复杂系统的实践者&#xff0c;我们深知…...

提升Telegraf性能:未使用方法接收器的代码优化实战指南

提升Telegraf性能&#xff1a;未使用方法接收器的代码优化实战指南 在Go语言开发中&#xff0c;方法接收器&#xff08;Method Receiver&#xff09;是连接函数与结构体的重要桥梁&#xff0c;但过度使用或不当使用会导致性能损耗和代码冗余。Telegraf作为插件驱动的指标收集代…...

Grove-I2C颜色传感器驱动开发与RGB色彩识别实践

1. Grove-I2C颜色传感器技术解析与嵌入式驱动开发实践 1.1 模块硬件架构与传感原理 Grove-I2C颜色传感器模块基于TAOS&#xff08;现为ams OSRAM&#xff09;TCS3414CS高精度数字颜色传感器芯片设计&#xff0c;其核心传感单元由16个微型光电二极管阵列构成&#xff0c;呈82物…...

3 个高级思路,让你的 AI 绘画 / 视频从此充满想象力

前言 如今 AI 视频与绘画工具的画质越来越卷&#xff0c;清晰度、光影、细节几乎都已触达天花板。但真正能让人记住、能脱颖而出的作品&#xff0c;靠的从来不是画质&#xff0c;而是想象力。 当所有人都在追求 “大片感” 时&#xff0c;你只需要换一种思路 ——用创意打破平…...

嵌入式系统栈溢出问题分析与防护实践

1. 栈溢出问题现象与初步分析最近在调试一个嵌入式系统时&#xff0c;遇到了一个非常典型的栈溢出问题。现象很简单&#xff1a;一个局部变量status的值莫名其妙地从0x01变成了其他值。最诡异的是&#xff0c;在两次打印status之间&#xff0c;代码并没有直接修改这个变量。简化…...

3个高效管理技巧让Windows右键菜单秒变清爽

3个高效管理技巧让Windows右键菜单秒变清爽 【免费下载链接】ContextMenuManager &#x1f5b1;️ 纯粹的Windows右键菜单管理程序 项目地址: https://gitcode.com/gh_mirrors/co/ContextMenuManager Windows右键菜单是日常操作的重要入口&#xff0c;但随着软件安装增多…...

飞书机器人自动化:OpenClaw调用Qwen3-4B实现会议纪要生成

飞书机器人自动化&#xff1a;OpenClaw调用Qwen3-4B实现会议纪要生成 1. 为什么选择OpenClawQwen3-4B做会议纪要 上个月我经历了连续三天的跨部门会议&#xff0c;每天手动整理会议纪要到深夜的痛苦让我开始寻找自动化解决方案。试过几款SaaS工具后&#xff0c;发现要么需要上…...