_______. _______.___ ___ .______ ____ ____ _______ .______
/ | / | / / | _ / / | ____|| _
| (----` | (----` V / | |_) | / / | |__ | |_) |
> < | / / | __| | /
.----) | .----) | / . | | ----. / | |____ | | ----.
|_______/ |_______/ /__/ __ | _| `._____| __/ |_______|| _| `._____|
ssxrver 是一个运行于 Linux 平台下的高性能高并发网络库,使用 C++17 进行编写,支持 TCP 和UDP 协议。
请尽量匹配与我相同的开发环境,如果不需要 数据库模块 请对应修改 CMakeLists.txt .
cmake 安装
# debian/ubuntu
sudo apt-get install cmakeboost 库安装
wget http://sourceforge.net/projects/boost/files/boost/1.72.0/boost_1_72_0.tar.bz2
tar -xvf boost_1_72_0.tar.bz2
cd ./boost_1_72_0
./bootstrap.sh --prefix=/usr/local
sudo ./b2 install --with=all在 ssxrver 目录下运行 ./build.sh , 可以修改 build.sh 选择生成 Debug 版本还是 Release 版本(默认 Release 版本)
./build.sh编译成功会生成 build/ 目录,可执行文件在对应版本的目录下,比如当你选择 Release 版本,可执行文件就在 /build/Release/ssxrver.
模仿 conf/ssxrver.json.example 的格式去创建你的配置文件(注意配置文件中不能加注释,不能加注释,不能加注释),以下我对各个配置文件选项做一下解释,很多参数实际上我设定了默认值.如果不配置的话也不会有影响.
{
"port": 4507, # 端口号,不填的话默认4507
"address": "127.0.0.1", # 绑定的地址
"worker_processes": 4, # IO 线程数量,不填默认为 4 个
"worker_connections": -1, # 一个 IO 线程最多支持多少连接, -1 表示最多能创建多少就创建多少,不做限制
"task_processes": 0, # 任务线程,不填的话默认为 0
"cpu_affinity": "off", # cpu 亲和度 ,默认关闭
"http": { # http 模块
"max_body_size": 67108864, # 单个 http 包最大支持大小
"root_path": "/home/randylambert/sunshouxun/ssxrver/html/" # 文件访问根路径
},
"log": { # log 模块
"level": "INFO", # 输出等级,可填三种等级, DEBUG,INFO,WARN 不填默认为 INFO 等级
"ansync_started": "off", # 是否打开异步日志线程,不填默认关闭
"flush_second": 3, # 异步线程每隔多久持久化一次
"roll_size": 67108864, # 日志文件滚动大小
"path": "/home/randylambert/sunshouxun/ssxrver/logs/", # 日志文件存放路径
"base_name": "ssxrver" # 日志文件基础名
},
"mysql": { # 数据库模块
"mysql_started": "off", # 是否打开数据库模块,默认关闭
"address": "127.0.0.1",# 以下是对应数据库连接信息
"user": "root",
"password": "123456",
"database_name": "ttms",
"port": 0,
"unix_socket": null,
"client_flag": 0
},
"blocks_ip": ["122.0.0.2","198.1.2.33"] # 可屏蔽部分恶意 IP
}运行可执行文件.
./ssxrver -f /配置文件的路径
# 例如
./build/Release/ssxrver -f ./conf/ssxrver.json| 测试环境 | 数值 |
|---|---|
| 操作系统发型版本 | deepin v20.1 社区版(1030) |
| 内核版本 | 5.4.70-amd64-desktop (64位) |
| 编译器版本 | gcc 8.3 |
| boost库版本 | 1.72 |
| 处理器 | Intel(R) Core(TM) i7-8750H CPU @2.20GHz |
| L1 Cache 大小 | 32K |
| L2 Cache 大小 | 256K |
| L3 Cache 大小 | 9216K |
| 硬盘转速 | 1.8 TiB 机械硬盘 5400转 |
| 硬盘读写速度 | 370 MB in 3.03 seconds = 122.27 MB/sec |
| 内存 | 7.6GB |
| Swap分区 | 4.7GB |
| 逻辑核数 | 12核 |
为控制变量,测试前重启电脑,保证测试环境没有高 CPU 负载和 高 IO 负载的其他应用.
测试工具为 webbench1.5 ,去掉第一次热身数据,测试命令如下(100个客户端持续访问15秒).
./webbench -c 100 -t 15 http://127.0.0.1:8081/测试对象为 Apache/2.4.38 , nginx/1.14.2 , ssxrver.
注: 无论是使用 webbench 还是 ab ,这种压测工具测出来的数据只能做一个简单参考,压测是一个需要全方位多角度的测试,而不是简单的运行一条命令而已,甚至在压测时数据根本没有经过网络传输,只是在内核里转了一圈.
| 网络库 | Speed(pages/min) | Requests成功率 |
|---|---|---|
| ssxrver 返回在内存中生成的 response | 7107414 | 100% |
| ssxrver 返回静态文件 | 5114376 | 100% |
| Apache/2.4.28 | 2884072 | 100% |
| nginx/1.14.2 | 4728748 | 100% |
ssxrver 的测试结果还不错,但是奇怪的是,我本以为数据会更高的,因为在我早期开发的时候,当时我很多优化还并没有做,返回直接在内存中生成的 response 时,测出来最多有接近 8000000 pages/min (接近 8000000 pages/min 的测试结果没截图,留下来一个7550778). 而当时 nginx/1.14.2 最多也超过 5000000 pages/min , 不过无论是 ssxrver 还是 nginx/1.14.2 ,现在我怎么测也测不出来那么高的值了,我也不清楚是什么原因,导致最终结果能出现这么大的差距,(难道是我电脑老化了? ̄□ ̄||)
目前我个人如果有时间的话会修改 ssxrver 的 Buffer 模块和 Log 模块.
首先, Buffer 模块最简单的改法是将其改成循环buffer ,从而有效的减少 Buffer 将数据前移的次数,或者直接放弃这种 Buffer 实现,重新实现一种高性能 Buffer .
其次,目前的 Log 模块是模拟 C++ 的流形式写的,虽然在性能上肯定比直接用 C++ 的 iostream 要高,但是重载 << 符号形式的 Log 还是会出现 格式控制不方便的问题 和 函数调用链引起的性能问题 ,这两个问题都可以通过实现 printf 形式的 Log 来解决.
由于时间原因, ssxrver 并没有实现内存管理模块,写一个通用的高性能内存管理模块是几乎不可能的 (不如直接上 jemalloc 或者 tcmalloc),但是,通过分析网络库这种场景,写出一个在这种场景下性能更高的内存管理模块还是有一点机会的,如果之有时间,我会去看看 nginx 中的实现,学习一下.
在我查询资料的时候我得到了一个结论是在 C++ 17 中,可以使用 std::string_view 替换 const string& ,会有一定的效率提升,因此我尝试将我项目中全部使用 const string& 的地方更换为 std::string_view ,但是当我在最终使用 perf -top 去查看更改后的负载时,意外的发现了部分函数在我使用了 std::string_view 替换之后,负载居然提高了,我很疑惑为什么会出现这种情况,由于时间原因我暂时不去追究这个问题的具体成因了,有机会去看看底层实现去查一下具体原因.
在实现 http 解析模块时,第一版我采用的是直接匹配字符串的手写状态机,之后我替换为了 Ragel 实现的状态机,但是最近测试的时候我发现 http 解析函数的负载十分夸张,达到了 10% , 难道说使用 Ragel 之后反而导致了性能下降 ?(如果说解析 header 会出现如此高的系统负载,那么看来 HTTP/2.0 对性能的提高还是很可观的) 遗憾的是在之前我手写状态机的时候,我并没有测试对应解析函数的负载情况,现在我一下子我拿不出来两者的数据比较,有机会写一个 BenchMark 测测.
ssxrver 支持简单的 UDP 传输,但是我个人认为一个没有拥塞控制,流量控制,丢包重传功能的 UDP 框架基本可以说是没办法正常应用的,以后我有时间去学习学习 QUIC, KCP 这些协议,在补充补充 UDP 相关知识,相信更高效更灵活的 UDP 协议在未来的应用会越来越广泛的!
实际上,我其实我认为目前最好的网络框架应该是,端口复用地址复用加多线程(多进程)绑定同一地址和端口,内核自动去做 accept 负载均衡 , 同时在通过协程框架 + hook 阻塞系统调用,这个框架使用之后可以在保证高性能的前提之下,同时不用主线程分发连接,还不用陷入异步回调地狱.
初此之外,如果能使用上在 Linux kernel 5.1 版本之后加入的异步 IO 机制 io_uring , 相信服务器的性能会更上一层楼,不过我目前对 io_uring 的了解并不多,暂时还没有功力去设计一个基于 io_uring 的异步 IO 网络库.