English version is here
- chenxuan
- 使用简单,入侵性小,全平台支持, 不要求强行安装到系统的include目录中, 推荐直接作为一个submodule 引用或者直接使用静态库
- 使用现代的C++构建, 使用方式和函数和 Go 的 gin 框架类似 , 学习和入门成本小, 可适合作为初学者进行源码学习
- 轻量化框架, 非常小, 源码不到5000行, 避免了大型网络框架的臃肿
- 需要编译器支持 C++17
- 下载 release 库(Releases · chenxuan520/cppnet) , 并且解压(地点可以随意, 可以放到系统的 include 也可以不放 下文假设是解压到当前目录), 需要编译器支持C++17
- 这里如果是需要ssl (需要安装 openssl ),就下载 ssl 的版本, 否则下载默认即可
- 编写代码, 这一步可以根据需要编写代码, 下面是两个简单的demo , 分别是 没有ssl 和有ssl的两个demo, 具体的函数介绍可以参考文档内容
#include "./cppnet/include/cppnet/http/server/http_server.hpp"
#include "./cppnet/include/cppnet/utils/const.hpp"
#include <iostream>
using namespace cppnet;
int main() {
HttpServer server;
auto rc = server.Init({"127.0.0.1", 8080});
if (rc != kSuccess) {
std::cout << "init server wrong " << server.err_msg() << std::endl;
return rc;
}
server.set_logger(std::make_shared<StdLogger>());
server.GET("/", [](HttpContext &ctx) {
ctx.resp().Success(HttpHeader::ContentType::kTextHtml,
"<h1>Hello, World!</h1>");
});
rc = server.Run();
if (rc != kSuccess) {
std::cout << "run server wrong " << server.err_msg() << std::endl;
return rc;
}
return 0;
}
// ssl 版本
#include "./cppnet/include/cppnet/http/server/http_server.hpp"
#include "./cppnet/include/cppnet/utils/const.hpp"
#include <iostream>
#include <memory>
using namespace cppnet;
int main() {
HttpServer server;
std::shared_ptr<SSLContext> ssl_ctx = std::make_shared<SSLContext>();
auto rc = ssl_ctx->InitSvrFile("./ssl/cacert.pem", "./ssl/privkey.pem");
if (rc != kSuccess) {
std::cout << "init ssl context wrong " << ssl_ctx->err_msg() << std::endl;
return rc;
}
rc = server.InitSSL({"127.0.0.1", 8080}, ssl_ctx);
if (rc != kSuccess) {
std::cout << "init server wrong " << server.err_msg() << std::endl;
return rc;
}
server.set_logger(std::make_shared<StdLogger>());
server.GET("/", [](HttpContext &ctx) {
ctx.resp().Success(HttpHeader::ContentType::kTextHtml,
"<h1>Hello, World!</h1>");
});
rc = server.Run();
if (rc != kSuccess) {
std::cout << "run server wrong " << server.err_msg() << std::endl;
return rc;
}
return 0;
}
- 编写编译文件
- 如果是使用 CMake 编译, 需要在CMakeLists 添加如下内容 , 实际上就是加上了链接库
# 是否使用ssl都需要添加 link_directories(./cppnet/lib) link_libraries(-lcppnet) # **如果使用的是 ssl版本并且需要使用ssl的功能,才需要添加下面的** add_definitions(-DCPPNET_OPENSSL) link_libraries(-lssl -lcrypto)
- 如果是使用 Makefile 编译, 需要在编译选项中添加
-L./cppnet/lib
和-lcppnet
, 下面是一个 Makefile 编译 demo, 当然如果是ssl的编译也是同理添加 lib 库 和 添加 CPPNET_OPENSSL 宏
all: libserver-makefile libserver-makefile: g++ -O2 -Wall -std=c++17 ./main.cpp -o libserver-makefile -lcppnet -L./cppnet/lib # ssl 版本的是 g++ -O2 -Wall -std=c++17 ./main.cpp -o libserver-makefile -DCPPNET_OPENSSL -lcppnet -lssl -lcrypto -L./cppnet/lib
- 执行编译, 获得编译的文件就成功了
- 使用
git clone https://github.com/chenxuan520/cppnet --recurse-submodules
拉取源代码- 默认是编译出 ssh版本的, 如果需要无ssl版本的需要手动修改cmake文件
- 运行
cd src;./build.sh
生成的静态库在lib中- 需要 cmake
- 需要编译器支持 C++17
- 使用 github action的ubuntu-latest进行自动化测试, 机器配置参考
- 4核 16G 内存 14G 硬盘
- 根据每次 commit 的代码自动运行更新测试结果, 参考 bench 文件夹下脚本, 图片中也会有 hash 值
- 使用 vegeta 进行压力测试, 分别测试在不同QPS下
- 平均响应时间
- p99 响应时间
- 正确率
- 等待时间
- 使用 cppnet 框架 和 go gin 框架进行对比, 使用两者实现同样的功能
- 更多demo参考 demo 文件夹和 test 文件夹
Address addr{"127.0.0.1", 8080};
TcpServer server{addr};
// init server
auto rc = server.Init();
if (rc != kSuccess) {
std::cout << "init server wrong " << server.err_msg() << std::endl;
return rc;
}
// init event function
auto event_func = [&](TcpServer::Event event, TcpServer &server, Socket fd) {
if (event == TcpServer::kEventAccept) {
// accept
std::cout << "accept " << fd.fd() << std::endl;
} else if (event == TcpServer::kEventRead) {
// read
string buf;
auto ser_rc = fd.Read(buf, msg.size());
std::cout << "read " << buf << std::endl;
} else if (event == TcpServer::kEventLeave) {
// leave
std::cout << "leave " << fd.fd() << std::endl;
} else {
// error event
std::cout << "dismiss " << fd.fd() << std::endl;
}
};
// register event function
server.Register(event_func);
Socket sock;
auto rc = sock.Init();
if (rc != kSuccess) {
std::cout << "init socket wrong " << sock.err_msg() << std::endl;
return rc;
}
rc = sock.Connect(addr);
if (rc != kSuccess) {
std::cout << "connect wrong " << sock.err_msg() << std::endl;
return rc;
}
// write
string msg = "hello world";
rc = sock.Write(msg);
if (rc != kSuccess) {
std::cout << "write wrong " << sock.err_msg() << std::endl;
return rc;
}
// read
string buf;
rc = sock.Read(buf, msg.size());
if (rc <= 0) {
std::cout << "read wrong " << sock.err_msg() << std::endl;
return rc;
}
std::cout << "read " << buf << std::endl;
// close
rc = sock.Close();
if (rc != kSuccess) {
std::cout << "close wrong " << sock.err_msg() << std::endl;
return rc;
}
HttpServer server;
auto rc = server.Init({"127.0.0.1", 8080});
if (rc != kSuccess) {
std::cout << "init server wrong " << server.err_msg() << std::endl;
return rc;
}
server.set_logger(std::make_shared<StdLogger>());
server.GET("/", [](HttpContext &ctx) {
ctx.resp().Success(HttpHeader::ContentType::kTextHtml,
"<h1>Hello, World!</h1>");
});
rc = server.Run();
if (rc != kSuccess) {
std::cout << "run server wrong " << server.err_msg() << std::endl;
return rc;
}
HttpServer server;
std::shared_ptr<SSLContext> ssl_ctx = std::make_shared<SSLContext>();
auto rc = ssl_ctx->InitSvrFile("./ssl/cacert.pem", "./ssl/privkey.pem");
if (rc != kSuccess) {
std::cout << "init ssl context wrong " << ssl_ctx->err_msg() << std::endl;
return rc;
}
rc = server.InitSSL({"127.0.0.1", 8080}, ssl_ctx);
if (rc != kSuccess) {
std::cout << "init server wrong " << server.err_msg() << std::endl;
return rc;
}
server.set_logger(std::make_shared<StdLogger>());
server.GET("/", [](HttpContext &ctx) {
ctx.resp().Success(HttpHeader::ContentType::kTextHtml,
"<h1>Hello, World!</h1>");
});
rc = server.Run();
if (rc != kSuccess) {
std::cout << "run server wrong " << server.err_msg() << std::endl;
return rc;
}
HttpClient client;
Address addr;
addr.InitWithDomain("www.androidftp.top", 80);
auto rc = client.Init(addr);
// 如果是https 443 端口 ssl
// auto rc = client.InitSSL(addr);
if (rc != kSuccess) {
std::cout << "init client wrong " << client.err_msg() << std::endl;
return rc;
}
HttpReq req;
req.GET("/");
HttpResp resp;
rc = client.Send(req, resp);
if (rc != kSuccess) {
std::cout << "send request wrong " << client.err_msg() << std::endl;
return rc;
}
std::string resp_str;
rc = resp.Build(resp_str);
if (rc != kSuccess) {
std::cout << "build response wrong " << resp.err_msg() << std::endl;
return rc;
}
- 使用 AddSoc 监听timesocket, 等待触发即可
TcpServer server{addr};
auto rc = server.Init();
if (rc != kSuccess) {
std::cout << "init server wrong " << server.err_msg() << std::endl;
return rc;
}
TimerSocket timerfd(0, 1e5);
if (rc != kSuccess) {
std::cout << "init timerfd wrong " << timerfd.err_msg() << std::endl;
return rc;
}
rc = server.AddSoc(timerfd);
- 仓库结构如下
- cppnet 放置所以的源码以及编译文件
- lib 放置生成的静态库以及静态库构建的 CMakeLists 文件
- test 是测试用例
- third_party 是第三方仓库, 目前只依赖 cpptest 用于test 文件夹的单元测试
- build.sh 生成 lib 静态库以及 单元测试的二进制文件
- docs 是接口文档
- demo 是参考的事例
- bench 是性能测试
.
├── LICENSE
├── README.en.md
├── README.md
├── bench
│ ├── build.sh
│ └── init.sh
├── demo
│ ├── asset
│ ├── build.sh
│ ├── mnist
│ ├── pack
│ ├── simple-http-server
│ ├── use-lib-server
│ ├── use-lib-server-ssl
│ ├── util
│ └── web-server
├── docs
│ ├── cppnet
│ └── update.sh
└── src
├── CMakeLists.txt
├── build.sh
├── cppnet
├── lib
├── release.sh
├── test
├── third_party
└── win_release.sh
- 源码主要放置在
src/cppnet
cppnet
├── http
│ ├── client
│ │ ├── http_client.cpp
│ │ └── http_client.hpp
│ ├── header
│ │ ├── http_header.cpp
│ │ └── http_header.hpp
│ ├── req
│ │ ├── http_req.cpp
│ │ ├── http_req.hpp
│ │ ├── method
│ │ │ ├── http_method.cpp
│ │ │ └── http_method.hpp
│ │ └── route
│ │ ├── http_route.cpp
│ │ └── http_route.hpp
│ ├── resp
│ │ ├── http_resp.cpp
│ │ ├── http_resp.hpp
│ │ └── status_code
│ │ ├── http_status_code.cpp
│ │ └── http_status_code.hpp
│ ├── server
│ │ ├── filter
│ │ │ ├── http_filter.hpp
│ │ │ ├── http_host_filter.hpp
│ │ │ └── http_method_filter.hpp
│ │ ├── http_server.cpp
│ │ └── http_server.hpp
│ └── version
│ ├── http_version.cpp
│ └── http_version.hpp
├── log
│ ├── file_logger.cpp
│ ├── file_logger.hpp
│ ├── logger.hpp
│ ├── multi_logger.cpp
│ ├── multi_logger.hpp
│ └── std_logger.hpp
├── server
│ ├── io_multiplexing
│ │ ├── epoll.cpp
│ │ ├── epoll.hpp
│ │ ├── io_multiplexing_base.hpp
│ │ ├── io_multiplexing_factory.cpp
│ │ ├── io_multiplexing_factory.hpp
│ │ ├── kqueue.cpp
│ │ ├── kqueue.hpp
│ │ ├── select.cpp
│ │ └── select.hpp
│ ├── tcp_server.cpp
│ └── tcp_server.hpp
├── socket
│ ├── address.cpp
│ ├── address.hpp
│ ├── socket.cpp
│ └── socket.hpp
├── ssl
│ ├── ssl_context.cpp
│ ├── ssl_context.hpp
│ ├── ssl_socket.cpp
│ └── ssl_socket.hpp
├── timer
│ ├── timer.cpp
│ └── timer.hpp
└── utils
├── const.hpp
├── file.cpp
├── file.hpp
├── host.cpp
├── host.hpp
├── string.cpp
├── string.hpp
├── threadpool.cpp
├── threadpool.hpp
├── trie.cpp
├── trie.hpp
├── version.cpp
└── version.hpp
19 directories, 61 files
- 补充完整 readme
- 支持设置LT和ET模式下的事件触发方式
- 支持设置连接的超时时间
- 补全select
- 支持UDP协议(添加测试)
- 支持Http协议(彻底迁移 cppweb -> cppnet)
- 支持 cicd 生成 lib 包
- 添加 release pkg的demo 和cmakelist
- httpclient 完成
- httpserver test
- 添加更多log
- route 宽泛匹配
- post 参数设置
- 触发模式控制修改
- 抽象出epoll层
- 支持SSL
- accept 改造
- 完善多线程
- 添加压力测试
- 提升trie