【项目篇】从零手写高并发服务器(十):性能测试与项目总结

文章目录

从零手写高并发服务器(十):性能测试与项目总结

💬 开篇:代码写完了不算完,得测!本篇我们用压测工具对HTTP服务器进行高并发测试,验证服务器的性能表现,然后做一个完整的项目回顾总结。

👍 点赞、收藏与分享:这是整个系列的最后一篇,完整收官!

🚀 内容:压测工具安装 → 压测实战 → 结果分析 → 项目架构回顾 → 总结。


一、压测工具安装

我们使用 webbench 进行压力测试,它是一个经典的轻量级Web压测工具。

1.1 安装webbench

bash 复制代码
cd ~
sudo apt-get install -y ctags  # webbench依赖
git clone https://github.com/EZLippi/WebBench.git
cd WebBench
make
sudo make install

验证安装:

bash 复制代码
webbench --help

如果输出帮助信息就说明安装成功。

1.2 webbench用法

bash 复制代码
webbench -c 并发数 -t 持续时间(秒) URL

参数说明:

  • -c:并发客户端数量
  • -t:测试持续时间(秒)
  • 还可以加 -2 使用HTTP/1.1,-k 使用keep-alive

二、压测准备

2.1 优化编译选项

压测时要用Release模式编译,开启优化:

bash 复制代码
cd ~/TcpServer/test
g++ -std=c++17 -O2 http_server.cpp -o http_server -lpthread

-O2 开启编译器优化,性能会比 -g 调试模式好很多。

2.2 调整系统参数

高并发测试前需要调整文件描述符限制:

bash 复制代码
# 查看当前限制
ulimit -n

# 临时调大(当前终端有效)
ulimit -n 65535

如果需要永久修改:

bash 复制代码
sudo vim /etc/security/limits.conf
# 添加以下两行
# * soft nofile 65535
# * hard nofile 65535

2.3 启动服务器

bash 复制代码
cd ~/TcpServer/test
./http_server

注意:服务器要在一个终端运行,压测在另一个终端执行。


三、压测实战

3.1 第一轮:低并发测试

先用100个并发连接测试10秒,验证基本功能:

bash 复制代码
webbench -c 100 -t 10 http://127.0.0.1:8500/hello

输出示例:

bash 复制代码
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1


Runing info: 100 clients, running 10 sec.

Speed=194316 pages/min, 421018 bytes/sec.
Requests: 32386 susceed, 0 failed.

关注两个指标:

  • pages/min:每分钟处理的请求数
  • failed:失败请求数,应该为0

3.2 第二轮:中等并发测试

500个并发连接,测试10秒:

bash 复制代码
webbench -c 500 -t 10 http://127.0.0.1:8500/hello

输出示例:

cpp 复制代码
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1


Runing info: 500 clients, running 10 sec.

Speed=171834 pages/min, 372307 bytes/sec.
Requests: 28639 susceed, 0 failed.

3.3 第三轮:高并发测试

1000个并发连接,测试10秒:

bash 复制代码
webbench -c 1000 -t 10 http://127.0.0.1:8500/hello

输出示例:

bash 复制代码
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1


Runing info: 1000 clients, running 10 sec.

Speed=156330 pages/min, 338715 bytes/sec.
Requests: 26055 susceed, 0 failed.

3.4 第四轮:极限测试

5000个并发连接,测试10秒:

bash 复制代码
webbench -c 5000 -t 10 http://127.0.0.1:8500/hello

输出示例:

bash 复制代码
wsh@VM-16-2-ubuntu:~/TcpServer/test$ webbench -c 5000 -t 10 http://127.0.0.1:8500/hello
Webbench - Simple Web Benchmark 1.5
Copyright (c) Radim Kolar 1997-2004, GPL Open Source Software.

Request:
GET /hello HTTP/1.0
User-Agent: WebBench 1.5
Host: 127.0.0.1


Runing info: 5000 clients, running 10 sec.

Speed=1782 pages/min, 3861 bytes/sec.
Requests: 297 susceed, 0 failed.

3.5 不同线程数对比

修改 http_server.cpp 中的线程数,分别测试:

线程数 并发1000 说明
0(单线程) 基准值 所有连接都在主线程处理
1 对比 主线程监听+1个工作线程
2 对比 主线程监听+2个工作线程
3 对比 主线程监听+3个工作线程
4 对比 主线程监听+4个工作线程

一般来说,线程数设置为CPU核心数效果最好:

bash 复制代码
# 查看CPU核心数
nproc

说明:本节所有压测数据均来自同一台 2核4G 腾讯云服务器,且压测程序与被测服务运行在同一主机。 因此结果会受到 CPU资源争抢、调度开销等因素影响,数据仅用于演示压测方法与趋势,不代表服务器在独立压测环境下的真实性能上限。

受限于当前机器带宽与硬件资源,暂无法进行压测机与服务端分离的标准化测试。


四、压测结果分析

4.1 本机实测结果汇总

并发数 线程数 成功请求数(10秒) QPS(每秒请求数) 失败数
100 2 32386 3238.6 0
500 2 28639 2863.9 0
1000 2 26055 2605.5 0
5000 2 297 29.7 0

QPS计算方式:QPS = 成功请求数 / 压测时长(秒)

例如:32386 / 10 = 3238.6

4.2 结果分析

bash 复制代码
性能曲线(本次实测趋势):

  QPS
   ^
   |   ●
   |    \
   |     \
   |      \
   |       \
   |        \
   |         \
   |          ●
   +──────────────────→ 并发数
      100   500  1000  5000

  - 在本次同机测试中,并发升高后QPS没有继续增长
  - 5000并发时QPS明显下降,说明资源竞争/调度开销很大
  - failed为0,说明功能正确性正常,但吞吐受环境限制明显

影响性能的因素:

  • 同机压测资源争抢:压测端与服务端抢占CPU,导致结果偏低
  • CPU核心数:决定可并行处理能力(2核机器上限有限)
  • 线程数配置:线程过少利用不充分,过多会增加上下文切换
  • 文件描述符限制:影响可同时维持的连接数
  • 编译优化:-O2 对吞吐影响明显
  • epoll模型:在高连接场景下优于select/poll,但仍受硬件上限约束

4.3 可能遇到的问题

问题1:大量failed请求

bash 复制代码
# 原因:文件描述符上限不足
ulimit -n 65535

问题2:每轮压测后服务异常,必须重启

bash 复制代码
# 可能原因:
# 1) 连接未及时释放(TIME_WAIT/CLOSE_WAIT堆积)
# 2) 某些状态或资源未正确回收
# 3) 异常信号处理不完整(如SIGPIPE)
#
# 建议排查:
ss -lntp | grep 8500
ss -ant | grep 8500 | awk '{print $1}' | sort | uniq -c

问题3:QPS偏低

bash 复制代码
# 原因1:编译没开优化
g++ -O2 ...

# 原因2:线程数不合理
# 建议线程数与CPU核心数接近(本机2核可先用2线程)

# 原因3:压测端与服务端同机,CPU争抢严重
# 更准确的方式是压测机与服务机分离

五、项目架构完整回顾

5.1 整体架构图

bash 复制代码
┌─────────────────────────────────────────────────────────────┐
│                        用户代码层                            │
│                                                             │
│   EchoServer / HttpServer应用                                │
│   注册回调 → 处理业务逻辑                                     │
├─────────────────────────────────────────────────────────────┤
│                      HTTP协议层                              │
│                                                             │
│   HttpServer    → 路由分发、静态资源服务                       │
│   HttpContext   → HTTP请求解析状态机                          │
│   HttpRequest   → 请求数据存储                               │
│   HttpResponse  → 响应数据组织                               │
│   Util          → URL编解码、文件操作、MIME类型                │
├─────────────────────────────────────────────────────────────┤
│                      TCP服务器层                              │
│                                                             │
│   TcpServer     → 整合所有模块,对外提供接口                   │
│   Acceptor      → 监听套接字管理,获取新连接                   │
│   Connection    → 通信连接管理(读写、缓冲区、状态)            │
├─────────────────────────────────────────────────────────────┤
│                      Reactor核心层                            │
│                                                             │
│   EventLoop     → 事件循环(epoll_wait + 任务队列 + 定时器)   │
│   Poller        → epoll封装                                  │
│   Channel       → 文件描述符事件管理                          │
│   TimerWheel    → 时间轮定时器                               │
│   LoopThread    → 线程与EventLoop绑定                        │
│   LoopThreadPool→ 线程池,轮询分配连接                        │
├─────────────────────────────────────────────────────────────┤
│                      基础组件层                               │
│                                                             │
│   Buffer        → 用户态缓冲区                               │
│   Socket        → 套接字操作封装                              │
│   Any           → 通用类型容器                               │
│   Log           → 日志宏                                     │
└─────────────────────────────────────────────────────────────┘

5.2 主从Reactor模型工作流程

bash 复制代码
                    主线程 (baseloop)
                         │
                    ┌────┴────┐
                    │ Acceptor │ ← 监听端口,等待新连接
                    └────┬────┘
                         │ accept() 获取新连接fd
                         │
                    ┌────┴────┐
                    │ TcpServer│ ← NewConnection()
                    └────┬────┘
                         │ pool.NextLoop() 轮询选择
                         │
            ┌────────────┼────────────┐
            ↓            ↓            ↓
     ┌──────────┐ ┌──────────┐ ┌──────────┐
     │SubLoop_1 │ │SubLoop_2 │ │SubLoop_3 │  ← 子线程EventLoop
     │          │ │          │ │          │
     │ Conn A   │ │ Conn B   │ │ Conn C   │  ← 每个连接绑定到一个子线程
     │ Conn D   │ │ Conn E   │ │ Conn F   │
     │ ...      │ │ ...      │ │ ...      │
     └──────────┘ └──────────┘ └──────────┘
          │            │            │
     epoll_wait   epoll_wait   epoll_wait
     处理读写事件  处理读写事件  处理读写事件

5.3 一次完整的HTTP请求处理流程

bash 复制代码
1. 客户端发起TCP连接
   └→ 主线程Acceptor的监听socket可读
      └→ accept()获取新fd
         └→ TcpServer::NewConnection()
            └→ 创建Connection对象
               └→ 轮询分配到子线程EventLoop
                  └→ Connection::Established()
                     └→ Channel::EnableRead() 开始监听可读事件

2. 客户端发送HTTP请求数据
   └→ 子线程EventLoop中epoll_wait返回可读事件
      └→ Channel::HandleEvent()
         └→ Connection::HandleRead()
            └→ Socket::NonBlockRecv() 读取数据到Buffer
               └→ HttpServer::OnMessage() 回调
                  └→ HttpContext::RecvHttpRequest() 解析请求
                     ├→ 解析请求行(方法、路径、版本)
                     ├→ 解析头部(一行一行)
                     └→ 接收正文(根据Content-Length)

3. 请求解析完毕
   └→ HttpServer::Route() 路由匹配
      ├→ 静态资源?→ FileHandler() 读文件返回
      └→ 功能请求?→ Dispatcher() 查路由表,调用handler

4. 生成响应
   └→ HttpServer::WriteResponse()
      └→ 组装响应行+头部+空行+正文
         └→ Connection::Send()
            └→ 数据写入out_buffer
               └→ Channel::EnableWrite()
                  └→ epoll监控可写事件
                     └→ Connection::HandleWrite()
                        └→ Socket::NonBlockSend() 发送数据

5. 连接管理
   ├→ keep-alive → 保持连接,等待下一个请求
   └→ close → Connection::Shutdown() → Release() → 关闭连接

5.4 关键设计思想

1. One Thread One Loop

每个线程拥有一个独立的EventLoop,线程内的所有操作都在这个EventLoop中完成,避免了多线程竞争。

2. 非阻塞IO + epoll

所有socket都设置为非阻塞模式,配合epoll的事件驱动,实现高效的IO多路复用。

3. 任务队列实现跨线程调用

当线程A需要操作线程B的资源时,不直接操作,而是把任务放到线程B的任务队列中,由线程B自己执行,避免加锁。

bash 复制代码
线程A                          线程B(EventLoop)
  │                                │
  │  RunInLoop(task)               │
  │  ──→ 放入任务队列 ──→          │
  │  ──→ eventfd通知 ──→           │
  │                          epoll_wait返回
  │                          执行任务队列中的task

4. 时间轮定时器

用环形数组实现O(1)的定时任务添加和删除,利用shared_ptr的引用计数实现任务的自动延迟和取消。

5. Buffer缓冲区

用户态缓冲区解决了TCP粘包问题,支持动态扩容,读写位置分离。

6. shared_ptr管理Connection生命周期

Connection使用shared_ptr管理,配合enable_shared_from_this,确保在异步回调中Connection对象不会被提前释放。


六、完整文件清单

bash 复制代码
~/TcpServer/
├── source/
│   └── server.hpp              # 整个服务器框架(所有模块在一个头文件中)
├── test/
│   ├── buffer_test.cpp         # Buffer类测试
│   ├── socket_test.cpp         # Socket类测试
│   ├── channel_poller_test.cpp # Channel+Poller测试
│   ├── timer_eventloop_test.cpp# TimerWheel+EventLoop测试
│   ├── threadpool_test.cpp     # 线程池测试
│   ├── echo_server.cpp         # Echo服务器
│   ├── http_server.cpp         # HTTP服务器
│   └── client.cpp              # 测试客户端
├── wwwroot/
│   └── index.html              # 静态资源
└── README.md

七、提交最终代码

bash 复制代码
cd ~/TcpServer
git add .
git commit -m "完成性能测试,项目收官"
git push

八、整个系列博客目录回顾

篇数 标题 核心内容
第一篇 前置知识 C++11特性、正则表达式、Any类、通用型函数包装器
第二篇 环境搭建与项目规划 项目结构、Makefile、架构设计
第三篇 Buffer缓冲区 用户态缓冲区设计与实现
第四篇 Socket套接字封装 网络操作抽象、非阻塞IO
第五篇 Channel与Poller 事件管理、epoll封装
第六篇 EventLoop事件循环 Reactor核心、任务队列、eventfd
第七篇 定时器与线程池 TimerWheel时间轮、LoopThread、LoopThreadPool
第八篇 Connection、Acceptor、TcpServer 连接管理、监听管理、服务器组装、EchoServer
第九篇 HTTP协议支持 HttpRequest/Response/Context/Server
第十篇 性能测试与项目总结 压测、架构回顾、设计思想总结

九、写在最后

到这里,整个「从零手写高并发服务器」项目就全部完成了。我们从最基础的Buffer缓冲区开始,一步步搭建起了一个完整的高性能TCP/HTTP服务器框架:

  • 底层用epoll实现高效IO多路复用
  • 用One Thread One Loop模型实现多线程并发
  • 用时间轮实现高效的连接超时管理
  • 用主从Reactor模型实现负载均衡
  • 在TCP之上实现了完整的HTTP协议支持

这个项目涵盖了网络编程、多线程编程、设计模式、性能优化等多个方面的知识,是一个非常好的C++后端学习项目。

相关推荐
楚Y6同学2 小时前
为什么 C++ 要设计函数重载
开发语言·c++
steins_甲乙2 小时前
【无标题】
开发语言·c++
YMWM_2 小时前
服务器上的cursor同步本地插件
运维·服务器·chrome
C++ 老炮儿的技术栈2 小时前
Tcp客户端报错原因分析
linux·c语言·网络·c++·网络协议·tcp/ip
co_wait2 小时前
【C语言】Linux系统文件操作函数基本使用
linux·c语言·microsoft
xiaoye-duck2 小时前
《算法题讲解指南:优选算法-哈希表》--56.两数之和,57.判断是否互为字符重排
c++·算法·哈希表
xiaomo22492 小时前
javaee-网络原理(理论)
linux·服务器·网络
炘爚2 小时前
Linux 进程管理 GCC/GDB 编译调试
linux·运维·服务器
不想好好取名字2 小时前
Ubuntu apt启用dbg符号库
linux·运维·ubuntu