Linux下C++轻量级Web服务器,助力初学者快速实践网络编程,搭建属于自己的服务器.原TinyWebServer源代码下载地址
其实在对TinyWebServer修改之后是否要开源出来其实是非常纠结的,倒不是说代码开不开源问题,而是TinyWebServer原作者的目的就是想要做一个尽量全而轻量的Web服务器,如果添加上传文件以及下载文件功能之后,会让代码变得更庞大,同时对于初学者或者我们自己在看的时候非常不利,这也违背了TinyWebServer原作者写这个项目的初心。本来是想和原作者沟通是否可以合并到分支里面,但是想了一下还是不要了,这样确实会让代码变得更臃肿和不友好,决定还是另起一个项目,这样大家可以根据自己的需求来学习这个项目。在改进,美化界面以及添加的功能的过程中我是借助了AI的,甚至你用AI可能搜出来的代码和我放在上面的一样。我主要的改进包括使用最小堆将原作者的双向链表给替换了,但是在进行压测的时候其实QPS都差不多;其次是将所有的界面给美化了,这部分需要自己有HTML,CSS,JAVAScript的基础(AI);最后是添加了上传文件和下载文件的功能。
- 使用 线程池 + 非阻塞socket + epoll(ET和LT均实现) + 事件处理(Reactor和模拟Proactor均实现) 的并发模型
- 使用状态机解析HTTP请求报文,支持解析GET和POST请求
- 访问服务器数据库实现web端用户注册、登录功能,可以请求服务器图片和视频文件
- 实现同步/异步日志系统,记录服务器运行状态
- 经Webbench压力测试可以实现上万的并发连接数据交换
- [√] 最小堆替换原作者实现的双向链表
- [√] 实现了上传文件功能
- [√] 实现了下载文件功能
- [√] 美化了所有界面
- [√] 服务器生成session id和保存cookie状态,过期时间设置为30分钟
- 浏览器调用摄像头
- [√] 图像分类系统
- [√] 目标检测系统
- [√] 优化文件分块上传模块(分离模块化)
- [√] SSL/TLS协议应用
- [√] 支持实时性能监控
- [√] 支持多种数据压缩格式
- [×] WebSocket支持
实现堆的过程中主要包含了向上调整堆,向下调整堆,添加节点,删除堆顶节点以及删除指定节点.这里的节点是指定时器,是根据定时器时间大小来进行构建最小堆的,并且保存节点采用的是vector数组以及哈希map来记录当前节点的索引(建议大家直接在网上搜索看资料或者视频更清楚)。
- 向上调整堆:将当前节点对应时间和父节点对应时间进行比较,如果小于父节点的时间就交换,同时哈希map记录的索引也需要交换,因为使用vector数组保存的节点。
- 向下调整堆:将当前节点和其左右子节点的时间进行对比,选择最小的孩子时间节点进行交换,哈希map记录的索引也需要进行交换。
- 添加节点:直接在vector数组最后添加节点,同时哈希map记录索引,最后调用向上调整函数调整堆即可。
- 删除堆顶节点:如果直接删除堆顶节点比较复杂,因此将堆顶节点和最后一个节点进行交换,删除最后一个节点,同时调用向下调整函数即可恢复最小堆结构。
- 删除指定节点:如果直接删除指定节点比较麻烦,和删除堆顶节点一样,将其和最后一个节点进行交换,并删除最后一个节点,同时从删除当前节点位置调用向下和向上调整函数即可恢复最小堆结构。
上传文件时需要注意的问题比较多,具体实现大家可以看源代码,主要问题如下:
- 原作者实现的代码对于请求body部分大小是有限的,如果直接对其缓冲区大小进行调整,将报错std::bad_alloc,因此对于稍微小的文件可以一次请求成功,但是对于稍微大一点的,比如1KB就会上传失败。
- 为了实现上传稍微大一点的文件,这里采用比较粗暴的形式,既然一次上传不完所有文件内容,那么可以分批次请求上传文件内容,然后将所有上传的内容进行合并得到完整的文件内容,这是目前的实现方式,同时也会将分块保存的文件给删除掉。
- 除了后端逻辑实现部分之外,还需要注意前端部分的实现,哪怕借助AI也需要非常小心和大量的调整。
- 前端部分除了实现upload.html页面之外,还需要考虑分块上传文件的过程,这部分涉及HTML,CSS以及JavaScript知识点,大家可以去了解一下,我也边写同时借助AI来了解。
下载文件的实现相比上传文件要简洁的多,当然并不代表简单,如下问题:
- 这部分首先需要实现download.html前端部分的网页,实现的过程中需要考虑怎么将服务器部分要下载的文件显示到前端,那么就需要我们在点击进入下载页面的时候,向服务器发送请求,服务器会把可以下载的文件列表通过响应的方式给浏览器(客户端),浏览器接收之后就可以将内容显示出来,当然这部分还是涉及大量的HTML,CSS以及JavaScript的知识点,但是并不妨碍我们理解后端的逻辑。
- 点击要下载的文件时,浏览器向服务端发送请求,服务端将要下载的文件通过响应的方式发送浏览器,注意这里有一个字段Content-Disposition要非常小心,Content-Disposition是 HTTP 响应头中的一个重要字段,主要用于控制客户端(如浏览器)如何处理服务器返回的内容,特别是在文件下载场景。
- Content-Disposition添加的时序位置也是有要求的,如果添加的时机不对,也会导致失败(主要是自己的经验不足,导致踩坑):
// HTTP响应的标准格式
HTTP/1.1 200 OK // 状态行
Content-Type: text/html // 响应头
Content-Length: 1234 // 响应头
Content-Disposition: attachment // 响应头
// 空行
<html>...</html> // 响应体
关于操作视频演示动画,请看:https://blog.csdn.net/Keep_Trying_Go/article/details/150215367
Cookie 是存储在客户端(浏览器)的小型文本数据(通常有大小限制,如4KB) Session 是存储在服务端的用户状态信息(大小理论上只受服务器内存限制) 它们通常配合使用,形成这样的工作流程:
- 用户首次访问 → 服务端创建Session并生成唯一Session ID
- 服务端通过Set-Cookie将Session ID发送给浏览器
- 浏览器后续请求自动携带这个Cookie(Session ID)
- 服务端通过Session ID查找对应的Session数据
图像分类系统实现流程的思路如下:
- 第一步:就是浏览器发送要分类的图像(发送文件采用的是分块上传文件)
- 第二步:接收来自浏览器的图像,然后进行常规的图像分类处理(包括打开图像,加载模型,图像预处理,以及输入模型检测,记录推理时间等)
- 第三步:将检测的分类结果以及推理时间放入header部分发送给浏览器,浏览器接收并渲染到页面中
目标检测系统实现流程的思路如下:
- 第一步:就是浏览器发送要检测的图像(发送文件采用的是分块上传文件),相比于图像分类,还需要在请求中发送当前设置的IOU阈值以及置信度阈值
- 第二步:接收来自浏览器的图像,然后进行常规的图像检测处理(包括打开图像,加载模型,图像预处理,以及输入模型检测,记录推理时间等)
- 第三步:将检测的结果发送给浏览器,浏览器接收并渲染到页面中
注意:目标检测系统相比于图像分类来说会更加的复杂,同时要处理的点也更多,这里有一个问题值得探讨,就是服务端是把检测的结果图像(已经将坐标框绘制到图像上)返回给浏览器呢?还是服务端直接将坐标框和检测置信度结果返回给浏览器呢?这两种方式各有优势和逆势,大家可以好好思考一下,本项目采用的是前一种方式。
- 如果是把检测结果图像发送给浏览器的话,响应报文中的数据量更大,对于数据的传输负担更大,但是这样的话就不需要前端来帮我们处理坐标框的事情了,并且在前端绘制坐标框的话,还需要考虑当前图像布局大小以及位置关系等问题,而是直接由服务端统一处理之后发送给浏览器,实现起来更容易理解
- 如果是把检测的坐标框和置信度发送给浏览器的话,那么响应报文数据量更少,有利于传输,但是坐标框和置信度就需要前端来帮我们处理了,考虑的东西也不少,特别是坐标框的绘制一定不能错。
语义分割系统实现流程的思路如下:
- 第一步:就是浏览器发送要分割的图像(发送文件采用的是分块上传文件),这里一定要注意使用的模型输入图像大小,比如我们这里举的例子是fcn resnet50输入的图像大小为512 x 512,,关于这一点大家可以看一下相关的前端代码实现部分。
- 第二步:接收来自浏览器的图像,然后进行常规的图像分割处理(包括打开图像,加载模型,图像预处理,以及输入模型检测,记录推理时间等)
- 第三步:将检测的分割结果图像以及推理时间发送给浏览器,浏览器接收并渲染到页面中。
注意:
- 关于Python + pytorch的转换为onnx模型的大家可以看下面总结的“参考链接”中关于语义分割模型在QT6.6.0中的使用,在对应CSDN博客中有给出代码下载地址。
- 关于OpenCV版本和torch版本之间的对应关系如下:
| OpenCV版本 | ONNX opset支持 | PyTorch兼容版本 |
|---|---|---|
| 4.5.x | opset≤11 | PyTorch≤1.7 |
| 4.7+ | opset≤15 | PyTorch≤1.13 |
我们这里使用的是4.5.5的OpenCV版本,使用的torch版本为1.11.0,torchvision为0.11.0
sudo apt update && sudo apt upgrade -y
安装编译器和构建工具
sudo apt install -y build-essential cmake git pkg-config libgtk-3-dev
安装图像和视频库
sudo apt install -y libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libxvidcore-dev libx264-dev
sudo apt install -y libjpeg-dev libpng-dev libtiff-dev gfortran openexr libatlas-base-dev
安装可选依赖(强烈推荐)
sudo apt install -y libtbb2 libtbb-dev libdc1394-22-dev libopenexr-dev
opencv官网: https://github.com/opencv https://opencv.org/releases/page/2/
OpenCV4.5.5下载:https://github.com/opencv/opencv/tree/4.5.5
OpenCV-Contrib4.5.5(contrib 包含额外的模块)下载:https://github.com/opencv/opencv_contrib/tree/4.5.5
注:下载之后将其上传指定的服务器(位置随便),然后进行解压unzip 文件名.zip
第一步:CMake配置构建
cmake -D CMAKE_BUILD_TYPE=RELEASE \
-D CMAKE_INSTALL_PREFIX=/usr/local \
-D INSTALL_C_EXAMPLES=ON \
-D INSTALL_PYTHON_EXAMPLES=ON \
-D OPENCV_GENERATE_PKGCONFIG=ON \
-D OPENCV_EXTRA_MODULES_PATH=/home/ubuntu/Documents/KTG/myPro/myProject/myTinyWebServer-v2/opencv_build/opencv_contrib/modules \
-D BUILD_EXAMPLES=ON \
-D WITH_GTK=ON \
-D WITH_FFMPEG=ON \
-D BUILD_opencv_python3=ON \
-D BUILD_opencv_python2=OFF \
..
第二步:编译
开始编译,使用所有可用的CPU核心:
make -j$(nproc)
第三步:安装
编译完成后,安装到系统目录 (需要sudo权限):
sudo make install
最后,更新动态链接库缓存:
sudo ldconfig
验证OpenCV是否安装成功:
pkg-config --modversion opencv4
# 如果成功,应该输出 4.5.5
Linux下编写C++程序导入opencv编译并执行的几种方式(Linux/C++/OpenCV)
OpenSSL 是一个功能完备的、商业级的、开源的工具包,实现了 SSL(Secure Sockets Layer) 和 TLS(Transport Layer Security) 协议。它提供了一个强大的通用密码学库,用于保护网络通信的安全。 关于更多介绍请看我之前的一个代码链接私钥和证书的生成以及给出几个实际抓包案例分析
编程接口(API)OpenSSL 通常书写步骤:
-
初始化库:SSL_library_init()
-
创建上下文(CTX):SSL_CTX_new(),用于存储全局设置和证书。
-
绑定 socket:将网络 socket 与 SSL 结构关联。
-
执行 SSL/TLS 握手:SSL_connect()(客户端) 或 SSL_accept()(服务器)。
-
安全通信:使用 SSL_read()和 SSL_write()替代普通的 read()和 write()。
-
清理:关闭连接并释放资源。
私钥和证书的生成以及给出几个实际抓包案例分析(建议把这个链接里面的内容看完以及案例实际操作一遍之后再来看本项目使用的SSL/TLS协议)
chmod +x generate_ssl.sh #可执行
./generate_ssl.sh # 执行脚本生成证书
结论:从上面的抓包结果来看,如果直接基于HTTP明文传输的话,抓到的上传文件信息都是直接可读的;而对于使用了HTTPS密文传输之后,传输的所有内容都是不可读的。
zlib旨在成为一个免费的、通用的、在法律上无障碍的——即不受任何专利限制的——无损数据压缩库,适用于几乎任何计算机硬件和操作系统。zlib的数据格式本身具有跨平台可移植性。与Unix系统compress(1)工具和GIF图像格式使用的LZW压缩方法不同,zlib当前采用的压缩方法基本上从不会使数据体积增大(LZW在极端情况下可能使文件大小翻倍甚至增至三倍)。zlib的内存占用也独立于输入数据,并且必要时可以通过降低压缩率来减少内存消耗(来自官方)。
方式一:
sudo apt update
sudo apt install zlib1g-dev # 安装开发包,包含头文件和静态库
方式二:
# 从官网下载源码 http://zlib.net/zlib-1.3.1.tar.gz
tar -xvf zlib-1.3.tar.gz # 解压
cd zlib-1.3
# 配置、编译和安装
./configure
make
sudo make install
# 默认安装路径是 /usr/local/lib,头文件在 /usr/local/include
# 系统可能会优先搜索 /usr/lib,如果需要让系统找到新安装的版本,可以运行:
sudo ldconfig
测试用例
g++ zlib_demo.cpp -o zlib_demo -lz
./zlib_demo
Brotli是Google开发的开源数据压缩算法,结合LZ77和Huffman编码,专为HTTP压缩优化,提供比gzip更高压缩比,显著提升网页加载速度,被主流浏览器和服务器广泛支持。
方式一:
# 更新包列表
sudo apt update
# 安装 Brotli 开发库
sudo apt install libbrotli-dev
# 验证安装
pkg-config --modversion libbrotlienc
方式二:
# 安装编译依赖
sudo apt install build-essential cmake git
# 下载 Brotli 源码
git clone https://github.com/google/brotli.git
cd brotli
# 创建构建目录
mkdir out && cd out
# 配置和编译
cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr/local ..
make -j$(nproc)
# 安装
sudo make install
# 更新动态库缓存
sudo ldconfig
// test_brotli.cpp
#include <brotli/encode.h>
#include <iostream>
#include <vector>
int main() {
const char* input = "Hello, Brotli compression test!";
size_t input_size = strlen(input);
// 估算最大输出大小
size_t max_output_size = BrotliEncoderMaxCompressedSize(input_size);
std::vector<uint8_t> output(max_output_size);
size_t encoded_size = max_output_size;
BROTLI_BOOL result = BrotliEncoderCompress(
BROTLI_DEFAULT_QUALITY,
BROTLI_DEFAULT_WINDOW,
BROTLI_MODE_TEXT,
input_size,
reinterpret_cast<const uint8_t*>(input),
&encoded_size,
output.data());
if (result == BROTLI_TRUE) {
std::cout << "Original size: " << input_size << " bytes\n";
std::cout << "Compressed size: " << encoded_size << " bytes\n";
std::cout << "Compression ratio: "
<< (encoded_size * 100.0 / input_size) << "%\n";
return 0;
} else {
std::cerr << "Compression failed!\n";
return 1;
}
}
原理: 父进程fork若干个子进程,每个子进程在用户要求时间或默认的时间内对目标web循环发出实际访问请求,父子进程通过管道进行通信,子进程通过管道写端向父进程传递在若干次请求访问完毕后记录到的总信息,父进程通过管道读端读取子进程发来的相关信息,子进程在时间到后结束,父进程在所有子进程退出后统计并给用户显示最后的测试结果,然后退出。
wget http://home.tiscali.cz/~cz210552/distfiles/webbench-1.5.tar.gz
解压:tar zxvf webbench-1.5.tar.gz
切换目录:cd webbench-1.5
编译:make
安装:sudo make install
注意:目前1.5版本的webbench支持http协议,不支持https协议。

webbench -c 10500 -t 5 http://10.16.110.157:9006/
模式1 proactor + LT + LT:./server -m 0 -c 1 -a 0
Speed=4317024 pages/min, 8058445 bytes/sec. Requests: 359752 susceed, 0 failed.
模式2 proactor + LT + ET:./server -m 1 -c 1 -a 0
Speed=4208976 pages/min, 7856733 bytes/sec. Requests: 350748 susceed, 0 failed.
模式3 proactor + ET + LT:./server -m 2 -c 1 -a 0
Speed=4085856 pages/min, 7636944 bytes/sec. Requests: 340485 susceed, 3 failed.
模式4 proactor + ET + ET:./server -m 3 -c 1 -a 0
Speed=3805128 pages/min, 7113030 bytes/sec. Requests: 317091 susceed, 3 failed.
模式5 reactor + LT + ET:./server -m 1 -c 1 -a 1
Speed=2533716 pages/min, 4729760 bytes/sec. Requests: 211143 susceed, 0 failed.
和原作者测试的结果比起来结果还变差了,我感觉改进的最小堆思路应该是没有什么问题,可能是没有做什么优化,我在实现最小那部分的时候对于构建堆,删除元素以及堆的调整都是采用原始堆的思想,完全没有做什么性能上的优化,但是可以给大家提供一个思路去学习。
-
TinyWebServer-v2服务器增加上传和下载文件功能,最小堆代替双链表,界面美化以及服务器生成session id,浏览器保存cookie以及图像分类实现(C/C++)
-
深度学习之模型部署入门 (一),案例实战值得一看(PyTorch,ONNX,ONNX Runtime,Flask,Android Studio,Gradio,Streamlit)
-
深度学习之模型部署入门 (二),案例实战值得一看(PyTorch,Android Studio,optimize_for_mobile)
-
深度学习之图像分割模型部署入门 (三),案例实战值得一看(PyTorch,Android Studio,ONNX,optimize_for_mobile)
-
深度学习之图像和目标检测模型基于pnnx工具转ncnn部署于Android入门 (四),案例实战值得一看(PyTorch,Android Studio,NCNN,PNNX)
-
深度学习之图像和目标检测模型基于pnnx工具转ncnn部署于Android入门 (四),案例实战值得一看(PyTorch,Android Studio,NCNN,PNNX)
-
TinyWebServer-v2服务器新增SSL/TLS协议和Content-Encoding字段指定压缩格式,生成私钥和自签证书以及数据压缩,保证数据在传输的过程中是加密和提高传输的效率
-
linux上使用tcpdump工具抓包(基于TCP协议的客户端向服务端发送信息,以及使用SSL/TLS协议之后客户端向服务端发送信息)和wireshark工具分析抓包(linux/C/C++)
近期版本迭代较快,以下内容多以旧版本(raw_version)代码为蓝本进行详解.
更简洁,更优雅的CPP11实现:Webserver
Linux高性能服务器编程,游双著.
感谢qinguoyi提供的TinyWebServer,让我学习到了很多知识点,也希望我给出的能给大家带来收益。




















