一、cpp-httplib
1.1 介绍
cpp-httplib 是一个基于 C++11 标准的、单文件、跨平台的 HTTP/HTTPS 库,非常适合用于快速搭建 HTTP 客户端或服务端,⽀持同步和异步操作。
特点
-
易于集成 :它是一个 Header-only 库,使用时只需将
httplib.h头文件包含到项目中即可,无需复杂的编译和链接配置。 -
跨平台:支持 Windows、Linux、macOS 等多种操作系统。
-
支持同步和异步操作。
-
简单易用:API 设计简洁,易于学习和使用。
Http 请求格式:

Http 响应格式:

1.2 安装
bash
git clone https://github.com/yhirose/cpp-httplib.git
cd cpp-httplib
cp httplib.h /path/..
# 在代码中直接引用 #include "httplib.h"
1.3 头文件
cpp
#include "httplib.h"
1.4 核心接口
-
Request 对象:封装了客户端请求的所有信息(请求方法、请求的资源路径、请求头、请求的查询字符串、请求正文等)。
-
Response 对象:用于构建服务器响应(响应状态码、响应头、响应正文等)。
-
方法路由表 :为每个 HTTP 方法维护一个路由表(哈希表),
key是一个 正则表达式 或 字符串 ,value是你为这个请求的 URL 设置的 回调函数。-
为什么使用正则表达式? 因为正则表达式提供了强大的路由匹配能力,有一些方法的资源路径后可能会携带不同的路径参数,通过正则表达式可以精准匹配格式,用一个回调函数可以同时处理这些不同参数的请求。
-
捕获结果存入
req.matches[1]中。
-
-
监听 :
listen()方法启动服务器,持续接收和处理请求。
cpp
namespace httplib {
// Range: 表示一个字节范围,用于 Range 请求(断点续传)
// first: 起始位置,second: 结束位置,ssize_t 是有符号大小类型
using Range = std::pair<ssize_t, ssize_t>;
// Ranges: 多个范围段的集合,支持多段 Range 请求
using Ranges = std::vector<Range>;
// MultipartFormData: 表示 multipart/form-data 格式的表单数据
// 用于文件上传等场景
struct MultipartFormData {
std::string name; // 表单字段名
std::string content; // 字段内容(文件内容或文本)
std::string filename; // 文件名
std::string content_type; // 内容类型,如 "image/jpeg"
};
// MultipartFormDataItems: 多个表单数据项(支持多文件上传)
using MultipartFormDataItems = std::vector<MultipartFormData>;
// MultipartFormDataMap: 以字段名为键的表单数据映射
using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;
// Params: URL 查询参数和表单参数的存储结构
using Params = std::multimap<std::string, std::string>;
// Match: 正则表达式匹配结果,存储路径参数捕获组
using Match = std::smatch;
struct Request {
std::string method;
std::string path;
Headers headers;
std::string body;
Params params;
MultipartFormDataMap files;
Ranges ranges;
Match matches;
};
struct Response {
std::string version;
int status = -1;
std::string reason;
Headers headers;
std::string body;
// 设置响应体字符串内容和内容类型
void set_content(const std::string &s, const std::string &content_type);
// 设置响应头
void set_header(const std::string &key, const std::string &val);
};
class Server {
using Handler = std::function<void(const Request &, Response &)>;
// 设置静态文件挂载点
// mount_point: URL 路径前缀,如 "/"
// dir: 本地文件系统目录,如 "./wwwroot"
// 访问 /index.html 会返回 ./wwwroot/index.html
bool set_mount_point(const std::string &mount_point,
const std::string &dir,
Headers headers = Headers()
);
Server &Get(const std::string &pattern, Handler handler);
Server &Post(const std::string &pattern, Handler handler);
Server &Put(const std::string &pattern, Handler handler);
Server &Delete(const std::string &pattern, Handler handler);
bool listen(const std::string &host, int port);
};
class Client {
explicit Client(const std::string &host, int port);
Result Get(const std::string &path, const Headers &headers);
Result Get(const std::string &path, const Params ¶ms,
const Headers &headers, Progress progress = nullptr);
Result Post(const std::string &path, const std::string &body,
const std::string &content_type);
Result Post(const std::string &path, const Params ¶ms);
Result Post(const std::string &path, const Headers &headers,
const Params ¶ms);
Result Post(const std::string &path, const MultipartFormDataItems &items);
Result Put(const std::string &path, const std::string &body,
const std::string &content_type);
Result Delete(const std::string &path, const std::string &body,
const std::string &content_type);
};
class Result {
operator bool() const { return res_ != nullptr; }
const Response &value() const { return *res_; }
Response &value() { return *res_; }
const Response &operator*() const { return *res_; }
Response &operator*() { return *res_; }
const Response *operator->() const { return res_.get(); }
Response *operator->() { return res_.get(); }
}
}
1.5 使用案例
httpserver.cc
cpp
#include <iostream>
#include "httplib.h"
void HelloWorld(const httplib::Request & req, httplib::Response & resp) {
std::cout << req.method << std::endl;
std::cout << req.path << std::endl;
std::cout << req.body << std::endl;
//打印头部字段信息
for (auto it : req.headers) {
std::cout << it.first << " = " << it.second << std::endl;
}
//打印查询字符串
for (auto it : req.params) {
std::cout << it.first << " = " << it.second << std::endl;
}
std::string html_body = "<html><body><h1>Hello World</h1></body></html>";
// httplib 会自动添加 Content-Length 字段
resp.set_content(html_body, "text/html");
resp.status = 200;
}
int main() {
httplib::Server server;
server.Get("/hello" , HelloWorld);
server.Get(R"(/numbers/(\d+))" , [](const httplib::Request & req, httplib::Response & resp){
std::cout << req.method << std::endl;
std::cout << req.path << std::endl;
std::cout << req.body << std::endl;
//打印头部字段信息
for (auto it : req.headers) {
std::cout << it.first << " = " << it.second << std::endl;
}
//打印查询字符串
for (auto it : req.params) {
std::cout << it.first << " = " << it.second << std::endl;
}
// 打印捕捉参数
std::cout << req.matches[1] << std::endl;
std::string numb = req.matches[1];
std::string html_body = "<html><body><h1> " + numb + "</h1></body></html>";
resp.set_content(html_body, "text/html");
resp.status = 200;
});
server.listen("0.0.0.0" , 8080);
return 0;
}
Makefile
makefile
server: httpserver.cc
g++ -std=c++11 -o $@ $^
clean:
rm -f server
二、Websocket
2.1 介绍
WebSocket 是一种在单个 TCP 连接上实现全双工通信的协议。简单说,它让客户端和服务器可以随时互相发送消息,不再像传统 HTTP 那样必须一问一答。
原理:
WebSocket 协议本质上是⼀个基于 TCP 的协议。为了建⽴⼀个 WebSocket 连接,客⼾端浏览器⾸先要向服务器发起⼀个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了⼀些附加头信息,通过这个附加头信息完成握⼿过程并升级协议的过程。
客户端请求
http
GET /ws HTTP/1.1
Host: example.com
Connection: Upgrade // 希望升级协议
Upgrade: websocket // 升级协议格式
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 客户端与服务器通信的钥匙
Sec-WebSocket-Version: 13 // 版本
服务器响应
http
HTTP/1.1 101 Switching Protocols
Connection: Upgrade // 升级协议
Upgrade: websocket // 升级的协议格式
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 服务端与客户端通讯的钥匙
握手成功后,TCP 连接被复用,后续通信切换为 WebSocket 协议。
2.2 报文格式

-
FIN:WebSocket传输数据以消息为概念单位,⼀个消息有可能由⼀个或多个帧组成,FIN字段为1表⽰末尾帧。
-
RSV1~3:保留字段,只在扩展时使⽤,若未启⽤扩展则应置0,若收到不全为0的数据帧,且未协商扩展则⽴即终⽌连接。
-
opcode:标志当前数据帧的类型:
0x0:继续帧0x1:文本帧0x2:二进制帧0x3-7:保留,暂未使用0x8:关闭连接0x9:ping0xA:pong其余:保留,暂未使用
-
mask:表⽰Payload数据是否被编码,若为1则必有Mask-Key,⽤于解码Payload数据,仅客⼾端发送给服务端的消息需要设置。
-
Payload len:表示实际载荷数据的长度,假设Payload len = x
- x 为 0 ~ 126:数据的长度为x字节
- x 为 126:后续 2 个字节代表⼀个16位的⽆符号整数,该⽆符号整数的值为数据的⻓度
- x 为 127:后续8个字节代表⼀个64位的⽆符号整数(最⾼位为0),该⽆符号整数的值为数据的⻓度
-
Mask-key:如果 MASK=1,接收方需要对载荷数据进行 XOR 解密,解码规则,
payload_data[i] ^= masking_key[i % 4]。 -
Payload data:报⽂携带的载荷数据。
2.3 Websocketpp 介绍
WebSocketpp 是⼀个跨平台的开源(BSD许可证)头部专⽤ C++ 库,它允许将 WebSocket 客⼾端和服务器功能集成到 C++ 程序中。在最常⻅的配置中,全功能⽹络 I/O 由 boost 库中的 Asio ⽹络库提供。
2.4 Websocketpp 安装
bash
sudo apt-get install libboost-dev libboost-system-dev libwebsocketpp-dev
2.5 Websocketpp 头文件包含
cpp
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
2.6 Websocketpp 编译选项
makefile
g++ -std=c++11 echo_server.cpp -o echo_server -lboost_system -lpthread
2.7 Websocketpp 常用接口
cpp
namespace websocketpp {
typedef lib::weak_ptr<void> connection_hdl; // weak_ptr
template <typename config>
class endpoint : public config::socket_type {
typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
typedef typename connection_type::ptr connection_ptr; // shared_ptr
typedef typename connection_type::message_ptr message_ptr; // shared_ptr
// 回调函数
typedef lib::function<void(connection_hdl)> open_handler;
typedef lib::function<void(connection_hdl)> close_handler;
typedef lib::function<void(connection_hdl)> http_handler;
typedef lib::function<void(connection_hdl,message_ptr)> message_handler;
/* websocketpp::log::alevel::none 禁⽌打印所有⽇志*/
void set_access_channels(log::level channels); /*设置⽇志打印等级*/
void clear_access_channels(log::level channels); /*清除指定等级的⽇志*/
/*设置指定事件的回调函数*/
void set_open_handler(open_handler h); /*websocket握⼿成功回调处理函数*/
void set_close_handler(close_handler h); /*websocket连接关闭回调处理函数*/
void set_message_handler(message_handler h); /*websocket消息回调处理函数*/
void set_http_handler(http_handler h); /*http请求回调处理函数*/
/*发送数据接⼝*/
void send(connection_hdl hdl, std::string& payload, frame::opcode::value op);
void send(connection_hdl hdl, void* payload, size_t len, frame::opcode::value op);
/*关闭连接接⼝*/
void close(connection_hdl hdl, close::status::value code, std::string& reason);
/*获取connection_hdl 对应连接的connection_ptr*/
connection_ptr get_con_from_hdl(connection_hdl hdl);
/*websocketpp基于asio框架实现,init_asio⽤于初始化asio框架中的io_service调度器*/
void init_asio();
/*设置是否启⽤地址重⽤*/
void set_reuse_addr(bool value);
/*设置endpoint的绑定监听端⼝*/
void listen(uint16_t port);
/*对io_service对象的run接⼝封装,⽤于启动服务器*/
std::size_t run();
/*websocketpp提供的定时器,以毫秒为单位*/
timer_ptr set_timer(long duration, timer_handler callback);
};
template <typename config>
class server : public endpoint<connection<config>,config> {
/*初始化并启动服务端监听连接的accept事件处理*/
void start_accept();
};
namespace message_buffer {
/*获取websocket请求中的payload数据类型*/
frame::opcode::value get_opcode();
/*获取websocket中payload数据*/
std::string const & get_payload();
};
// 日志等级
namespace log {
struct alevel {
static level const none = 0x0;
static level const connect = 0x1;
static level const disconnect = 0x2;
static level const control = 0x4;
static level const frame_header = 0x8;
static level const frame_payload = 0x10;
static level const message_header = 0x20;
static level const message_payload = 0x40;
static level const endpoint = 0x80;
static level const debug_handshake = 0x100;
static level const debug_close = 0x200;
static level const devel = 0x400;
static level const app = 0x800;
static level const http = 0x1000;
static level const fail = 0x2000;
static level const access_core = 0x00003003;
static level const all = 0xffffffff;
};
}
// 连接对象
template <typename config>
class connection : public config::transport_type::transport_con_type , public config::connection_base
{
/*发送数据接⼝*/
error_code send(std::string&payload, frame::opcode::value op=frame::opcode::text);
/*获取http请求头部*/
std::string const & get_request_header(std::string const & key)
/*获取请求正⽂*/
std::string const & get_request_body();
/*设置响应状态码*/
void set_status(http::status_code::value code);
/*设置http响应正⽂*/
void set_body(std::string const & value);
/*添加http响应头部字段*/
void append_header(std::string const & key, std::string const & val);
/*获取http请求对象*/
request_type const & get_request();
/*获取connection_ptr 对应的 connection_hdl */
connection_hdl get_handle();
};
namespace frame {
namespace opcode {
enum value {
continuation = 0x0,
text = 0x1,
binary = 0x2,
...
close = 0x8,
ping = 0x9,
pong = 0xA,
...
}
}
}
}
2.8 Websocketpp 使用流程
-
包含头文件:引入 WebSocket++ 服务端和 ASIO 配置的头文件。
-
定义服务端类型:通过模板参数指定使用 ASIO 无加密配置的服务端类型。
-
实例化服务端对象 :创建
server类型的实例echo_server。 -
配置日志级别:设置访问日志通道并屏蔽帧载荷日志。
-
初始化 ASIO :调用
init_asio()初始化底层异步 I/O 系统。 -
注册消息处理器 :通过
set_message_handler绑定消息处理函数。 -
启用端口复用 :调用
set_reuse_addr(true)避免重启时端口被占用。 -
监听端口 :通过
listen(9002)绑定端口并开始监听。 -
开始接收连接 :调用
start_accept()启动接受客户端连接的流程。 -
启动事件循环 :调用
run()进入阻塞状态,持续处理网络事件和回调。
2.9 Websocketpp 使用示例
server.cc
cpp
#include <iostream>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
using namespace std;
typedef websocketpp::server<websocketpp::config::asio> websocketsvr;
typedef websocketsvr::message_ptr message_ptr;
// websocket连接成功的回调函数
void OnOpen(websocketsvr *server,websocketpp::connection_hdl hdl){
cout<<"连接成功"<<endl;
}
// websocket连接成功的回调函数
void OnClose(websocketsvr *server,websocketpp::connection_hdl hdl){
cout<<"连接关闭"<<endl;
}
// websocket连接收到消息的回调函数
void OnMessage(websocketsvr *server,websocketpp::connection_hdl hdl,message_ptr msg){
cout << "收到消息" << msg->get_payload() << endl;
// 收到消息将相同的消息发回给websocket客户端
server->send(hdl, msg->get_payload(), websocketpp::frame::opcode::text);
}
// websocket连接异常的回调函数
void OnFail(websocketsvr *server,websocketpp::connection_hdl hdl){
cout<<"连接异常"<<endl;
}
// 处理http请求的回调函数 返回一个html欢迎页面
void OnHttp(websocketsvr *server,websocketpp::connection_hdl hdl){
cout<<"处理http请求"<<endl;
websocketsvr::connection_ptr con = server->get_con_from_hdl(hdl);
std::stringstream ss;
ss << "<!doctype html><html><head>"
<< "<title>hello websocket</title><body>"
<< "<h1>hello websocketpp</h1>"
<< "</body></head></html>";
con->set_body(ss.str());
con->set_status(websocketpp::http::status_code::ok);
}
int main(){
// 使用websocketpp库创建服务器
websocketsvr server;
// 设置websocketpp库的日志级别
// all表示打印全部级别日志
// none表示什么日志都不打印
server.set_access_channels(websocketpp::log::alevel::none);
/*初始化asio*/
server.init_asio();
// 注册http请求的处理函数
server.set_http_handler(std::bind(&OnHttp, &server, std::placeholders::_1));
// 注册websocket请求的处理函数
server.set_open_handler(std::bind(&OnOpen, &server, std::placeholders::_1));
server.set_close_handler(std::bind(&OnClose, &server,std::placeholders::_1));
server.set_message_handler(std::bind(&OnMessage,&server,std::placeholders::_1,std::placeholders::_2));
// 监听8888端口
server.listen(8888);
// 开始接收tcp连接
server.start_accept();
// 开始运行服务器
server.run();
return 0;
}
makefile
makefile
server: server.cc
g++ -std=c++17 -o $@ $^ -lboost_system -lpthread
clean:
rm -f server
websocket 客户端
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Test Websocket</title>
</head>
<body>
<input type="text" id="message">
<button id="submit">提交</button>
<script>
// 创建 websocket 实例
// ws://192.168.51.100:8888
// 类⽐http
// ws表⽰websocket协议
// 192.168.51.100 表⽰服务器地址
// 8888表⽰服务器绑定的端⼝
let websocket = new WebSocket("ws://192.168.51.100:8888");
// 处理连接打开的回调函数
websocket.onopen = function () {
console.log("连接建⽴");
}
// 处理收到消息的回调函数
// 控制台打印消息
websocket.onmessage = function (e) {
console.log("收到消息: " + e.data);
}
// 处理连接异常的回调函数
websocket.onerror = function () {
console.log("连接异常");
}
// 处理连接关闭的回调函数
websocket.onclose = function () {
console.log("连接关闭");
}
// 实现点击按钮后, 通过 websocket实例 向服务器发送请求
let input = document.querySelector('#message');
let button = document.querySelector('#submit');
button.onclick = function () {
console.log("发送消息: " + input.value);
websocket.send(input.value);
}
</script>
</body>
</html>