
🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!
🎬 博主简介:

文章目录
- 前言:
- [一、httplib 库的安装与使用](#一、httplib 库的安装与使用)
-
- [1.1 库介绍](#1.1 库介绍)
- [1.2 安装方式](#1.2 安装方式)
- [1.3 核心数据结构与接口](#1.3 核心数据结构与接口)
-
- [Request 请求结构体](#Request 请求结构体)
- [Response 响应结构体](#Response 响应结构体)
- [Server 服务端类](#Server 服务端类)
- [Client 客户端类](#Client 客户端类)
- [1.4 服务端实战样例](#1.4 服务端实战样例)
- [二、websocketpp 的安装与介绍](#二、websocketpp 的安装与介绍)
-
- [2.1 为什么需要 WebSocket](#2.1 为什么需要 WebSocket)
- [2.2 WebSocket 协议原理解析](#2.2 WebSocket 协议原理解析)
-
- [连接建立:HTTP 握手升级](#连接建立:HTTP 握手升级)
- 数据帧格式
- [2.3 WebSocketpp 库介绍与安装](#2.3 WebSocketpp 库介绍与安装)
- [2.4 核心类与接口梳理](#2.4 核心类与接口梳理)
-
- 基础类型
- [server 服务端类](#server 服务端类)
- [connection 连接类](#connection 连接类)
- [frame::opcode 枚举](#frame::opcode 枚举)
- [三、websocketpp 样例编写与测试](#三、websocketpp 样例编写与测试)
-
- [3.1 服务端搭建流程](#3.1 服务端搭建流程)
- [3.2 完整服务端代码](#3.2 完整服务端代码)
- [3.3 编译运行](#3.3 编译运行)
- [3.4 功能测试](#3.4 功能测试)
- 四、核心要点总结
- 结尾:
前言:
做即时通讯系统的网关层,绕不开两类网络请求:一类是注册、登录、用户信息查询这类HTTP 短连接请求 ,一问一答即可完成;另一类是消息接收、状态推送这类长连接场景 ,需要服务端主动向客户端推送数据。在我们这套 C++ 微服务 IM 架构里,选用了两个非常经典的轻量库来分别承担这两块工作:cpp-httplib 负责 HTTP 服务 ,WebSocketpp 负责 WebSocket 长连接。本文就顺着项目落地的顺序,从安装、核心接口、实战代码到踩坑记录,完整拆解这两个网关基建组件。
一、httplib 库的安装与使用
1.1 库介绍
cpp-httplib 是一个非常轻量的 C++ HTTP 客户端/服务器库,整个库只有一个头文件,集成成本极低。它的特点非常鲜明:
- 极致轻量 :仅一个头文件
httplib.h,不依赖任何外部库,包含就能用 - 跨平台:支持 Windows、Linux、macOS 全平台
- 同步异步都支持:两种调用模式,开发者按需选择
- 标准兼容:完整实现 HTTP/1.1 协议,支持持久连接、管道化
- 文件上传友好:原生支持 multipart form-data,做文件上传很方便
- SSL 支持:对接 OpenSSL 或 mbedTLS,支持 HTTPS、WSS
- API 简洁:接口设计非常直白,上手快,学习成本低
补充说明:最开始我们也考虑过直接用 WebSocketpp 自带的 HTTP 能力,但实际测试发现,它在高并发 HTTP 请求场景下存在一些稳定性问题。所以最终 HTTP 服务独立用 cpp-httplib 实现,WebSocket 专用 WebSocketpp,职责拆分更清晰,运行也更稳定。
1.2 安装方式
因为是 header-only(仅头文件)的库,不需要编译安装,直接引入头文件即可。
# 克隆仓库
git clone [https://github.com/yhirose/cpp-httplib.git](https://github.com/yhirose/cpp-httplib.git)
# 直接把核心头文件拷贝到项目公共头文件目录即可
mv ./cpp-httplib/httplib.h ../common/
整个头文件代码接近一万行,封装了完整的 HTTP 协议解析、网络 IO、路由匹配等所有能力,业务层完全不用关心底层细节。
1.3 核心数据结构与接口
库的核心就四个类型,理解了就能快速上手:
Request 请求结构体
struct Request {
std::string method; // 请求方法:GET/POST/PUT/DELETE
std::string path; // 请求路径
Headers headers; // 请求头,键值对
std::string body; // 请求体
Params params; // URL参数
};
Response 响应结构体
struct Response {
std::string version; // HTTP版本
int status = -1; // 状态码
Headers headers; // 响应头
std::string reason; // 状态描述
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);
};
Server 服务端类
用来搭建 HTTP 服务器,核心是注册路由和启动监听:
class Server {
using Handler = std::function<void(const Request&, Response&)>;
// 注册不同方法的路由
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);
};
Client 客户端类
用来发起 HTTP 请求,同步调用方式非常简洁:
class Client {
explicit Client(const std::string& host, int port);
Result Get(const std::string& path, const Headers& headers);
Result Post(const std::string& path, const std::string& body, const std::string& content_type);
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);
};
1.4 服务端实战样例
搭建一个最简单的 HTTP 服务器,只需要三步:实例化服务器、注册路由回调、启动监听。
#include <iostream>
#include "httplib.h"
int main() {
// 1. 实例化服务器对象
httplib::Server server;
// 2. 注册GET路由回调
server.Get("/hi", [](const httplib::Request& req, httplib::Response& rsp) {
// 打印请求信息
std::cout << "请求方法: " << req.method << std::endl;
std::cout << "请求路径: " << req.path << std::endl;
for (auto& header : req.headers) {
std::cout << header.first << ": " << header.second << std::endl;
}
// 设置响应内容和状态码
std::string body = "<html><body><h1>Hello Bite</h1></body></html>";
rsp.set_content(body, "text/html");
rsp.status = 200;
});
// 3. 启动服务器,监听9090端口
std::cout << "服务器启动,端口9090" << std::endl;
server.listen("0.0.0.0", 9090);
return 0;
}
编译运行:
g++ -std=c++17 main.cc -o main -lpthread
./main
启动后浏览器访问 http://服务器IP:9090/hi,就能看到页面返回 Hello Bite,说明 HTTP 服务就跑通了。
这个库本身接口已经足够简洁,业务场景直接用原生接口就行,不需要额外二次封装。
二、websocketpp 的安装与介绍
2.1 为什么需要 WebSocket
传统 HTTP 协议是「一问一答」模式:客户端发请求,服务端回响应,服务端永远是被动的。但在聊天系统里,对方发消息、好友申请这些场景,都需要服务端主动向客户端推送数据。
如果硬用 HTTP 实现,只能用轮询:客户端每隔几秒问一次服务端有没有新消息。这样做不仅延迟高,还会产生大量无效请求,服务器压力大。
WebSocket 就是为了解决这个问题诞生的:它是基于 TCP 的应用层长连接协议,连接建立之后,客户端和服务端都可以主动向对方发送数据,全双工通信,非常适合即时通讯、实时游戏这类需要消息推送的场景。
2.2 WebSocket 协议原理解析
连接建立:HTTP 握手升级
WebSocket 连接不是凭空建立的,而是从 HTTP 协议升级而来:
- 客户端先发起一个普通 HTTP 请求,头里带上
Upgrade: websocket、Sec-WebSocket-Key等特殊字段 - 服务端识别到升级请求,返回
101 Switching Protocols响应 - 握手完成,协议升级为 WebSocket,之后双方都用 WebSocket 帧格式传输数据
这个设计的好处是兼容 HTTP 端口,不需要额外开端口,也能兼容现有的 HTTP 代理、防火墙。
数据帧格式
WebSocket 传输以帧为单位,帧头里有几个关键字段:
-
FIN:1 表示这是消息的最后一帧,0 表示还有后续分片
-
opcode
:标识帧的类型,这是最核心的字段
0x0:延续帧,消息分片传输时的中间帧0x1:文本帧,传输字符串数据0x2:二进制帧,传输二进制数据(我们项目用 protobuf 通信,就用这个类型)0x8:连接断开帧0x9:ping 心跳帧0xa:pong 心跳回复
-
mask:表示载荷是否掩码,客户端发服务端的消息必须掩码
-
Payload length:载荷长度,根据长度不同有 7 位、16 位、64 位三种格式
2.3 WebSocketpp 库介绍与安装
库介绍
WebSocketpp 是 C++ 领域非常成熟的开源 WebSocket 库,基于 Boost.Asio 实现,特性很全面:
- 事件驱动的 API 设计,异步 IO 性能好
- 同时支持 HTTP/HTTPS、WS/WSS、IPv6
- 灵活的依赖管理,支持 Boost 库也支持 C++11 标准库
- 跨平台、线程安全
- 同时支持 HTTP 和 WebSocket 两种协议
我们项目选它,核心原因就是一个库就能搞定网关的长连接需求,生态成熟,坑也少。
安装方式
Ubuntu 下可以直接 apt 安装开发包,简单快捷:
# 安装依赖
sudo apt-get install libboost-dev libboost-system-dev
# 安装websocketpp开发库
sudo apt-get install libwebsocketpp-dev
安装完成后,/usr/include/websocketpp/ 目录下会有所有头文件。它也是偏 header-only 的设计,包含头文件就能用,编译时链接对应的系统库即可。
2.4 核心类与接口梳理
基础类型
connection_hdl:连接句柄,本质是weak_ptr<void>,用来标识一个连接。不能直接操作,需要通过服务器转成真正的连接指针对象。message_ptr:消息指针,用来获取收到的消息数据。
server 服务端类
核心的服务管理类,常用接口:
class server {
// 设置日志级别
void set_access_channels(log::level channels);
// 设置回调
void set_open_handler(open_handler h); // 连接建立成功回调
void set_close_handler(close_handler h); // 连接关闭回调
void set_message_handler(message_handler h); // 收到消息回调
// 初始化asio框架
void init_asio();
// 启用地址重用,避免重启端口占用
void set_reuse_addr(bool value);
// 设置监听端口、开始监听、启动事件循环
void listen(uint16_t port);
void start_accept();
size_t run();
// 通过句柄获取连接对象
connection_ptr get_con_from_hdl(connection_hdl hdl);
// 发送消息、关闭连接
void send(connection_hdl hdl, std::string& payload, frame::opcode::value op);
void close(connection_hdl hdl, close::status::value code, std::string& reason);
};
connection 连接类
代表一个具体的 WebSocket 连接,常用的就是获取消息载荷和发送消息。
frame::opcode 枚举
对应协议里的帧类型:
namespace frame {
enum value {
continuation = 0x0, // 延续帧
text = 0x1, // 文本帧
binary = 0x2, // 二进制帧
close = 0x8, // 关闭帧
ping = 0x9, // ping
pong = 0xA // pong
};
}
三、websocketpp 样例编写与测试
3.1 服务端搭建流程
搭建一个 WebSocket 服务器,标准步骤是九步:
- 定义 server 类型
- 实例化服务器对象
- 初始化日志输出(建议关闭默认日志,避免刷屏)
- 初始化 asio 框架
- 设置连接建立、连接关闭、消息接收三个回调函数
- 启用地址重用
- 设置监听端口
- 开始接受连接
- 启动服务器事件循环
3.2 完整服务端代码
#include <iostream>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
// 0. 定义服务器类型
typedef websocketpp::server<websocketpp::config::asio> server_t;
// 连接建立回调
void onOpen(websocketpp::connection_hdl hdl) {
std::cout << "WebSocket 长连接建立成功!" << std::endl;
}
// 连接关闭回调
void onClose(websocketpp::connection_hdl hdl) {
std::cout << "WebSocket 长连接断开!" << std::endl;
}
// 消息接收回调
void onMessage(server_t* server, websocketpp::connection_hdl hdl, server_t::message_ptr msg) {
// 1. 获取消息载荷
std::string body = msg->get_payload();
std::cout << "收到消息: " << body << std::endl;
// 2. 获取连接对象,回发消息
auto conn = server->get_con_from_hdl(hdl);
conn->send(body + " --Hello!", websocketpp::frame::opcode::text);
}
int main() {
// 1. 实例化服务器对象
server_t server;
// 2. 关闭所有日志输出
server.set_access_channels(websocketpp::log::alevel::none);
// 3. 初始化asio框架
server.init_asio();
// 4. 设置回调函数
server.set_open_handler(onOpen);
server.set_close_handler(onClose);
// 消息处理回调需要访问server对象,用bind绑定
auto msg_handler = std::bind(onMessage, &server,
std::placeholders::_1, std::placeholders::_2);
server.set_message_handler(msg_handler);
// 5. 启用地址重用
server.set_reuse_addr(true);
// 6. 设置监听端口
server.listen(9090);
// 7. 开始接受连接
server.start_accept();
// 8. 启动服务器事件循环
std::cout << "WebSocket服务器启动,端口9090" << std::endl;
server.run();
return 0;
}
代码解读与踩坑
- connection_hdl 是弱指针 它只是个标识,不能直接用来发消息,必须调用
server.get_con_from_hdl(hdl)拿到真正的connection_ptr才能操作。 - 回调参数匹配问题 这是新手最高频的编译错误。
set_message_handler要求的回调类型是void(connection_hdl, message_ptr),如果我们的回调需要额外访问 server 对象,就必须用std::bind绑定参数,不能直接传多参数的函数。 - 不要给 connection_hdl 加引用 很多人图方便写
connection_hdl& hdl,会直接报类型不匹配的编译错误。connection_hdl本身就是个轻量句柄,直接传值就行。
3.3 编译运行
编译命令,需要链接 pthread 和 boost_system:
g++ -std=c++17 main.cc -o main -lpthread -lboost_system
运行:
./main
3.4 功能测试
写个简单的 HTML 页面就能测试,不用专门写客户端:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>WebSocket测试</title>
</head>
<body>
<input type="text" id="message" placeholder="输入要发送的消息">
<button id="submit">提交</button>
<script>
// 连接WebSocket服务端,改成自己的服务器地址
let websocket = new WebSocket("ws://127.0.0.1:9090");
websocket.onopen = function() {
console.log("连接建立成功");
}
websocket.onmessage = function(e) {
console.log("收到服务端消息: " + e.data);
}
websocket.onerror = function() {
console.log("连接异常");
}
websocket.onclose = function() {
console.log("连接关闭");
}
// 点击按钮发送消息
let input = document.querySelector('#message');
let button = document.querySelector('#submit');
button.onclick = function() {
console.log("发送消息: " + input.value);
websocket.send(input.value);
}
</script>
</body>
</html>
浏览器打开这个页面,按 F12 打开控制台,输入消息点击发送,就能看到收发日志,说明整个 WebSocket 链路就通了。
四、核心要点总结
- 职责拆分清晰:HTTP 短连接业务用 cpp-httplib,单头文件零依赖,集成简单稳定;WebSocket 长连接消息推送用 WebSocketpp,功能完整生态成熟。两者配合就是网关服务的标准网络底座。
- WebSocket 本质是 HTTP 升级:先通过 HTTP 握手,再升级为长连接协议,不是凭空的新协议,这也是它能兼容现有网络环境的核心原因。
- 句柄和连接要区分 :
connection_hdl只是弱标识,操作连接必须通过get_con_from_hdl转换成真正的连接指针对象,这是新手最高频的坑。 - 回调参数严格匹配 :库的回调类型是固定的,参数不对就编译失败。需要额外上下文就用
std::bind绑定,不要硬改函数签名。 - 默认日志建议关闭:两个库默认日志都比较多,生产环境一定要关掉,避免日志刷屏影响性能,用自己的日志系统统一管理。
结尾:
html
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!
结语:到这里,网关层的两个核心网络组件就都跑通了。HTTP 负责处理所有的短连接业务请求,WebSocket 负责维护客户端长连接、做消息主动推送,这就是整个即时通讯系统和客户端交互的入口。接下来我们会基于这两个组件,实现完整的网关服务:协议解析、鉴权、服务转发、消息推送,把前面讲的 etcd、brpc 都串起来,形成完整的微服务调用链路。后续也会继续分享更多 C++ IM 微服务的实战内容,欢迎关注交流。
✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど
