Websocketpp库用法简介
WebSocket++ 是一个基于 C++ 的开源 WebSocket 客户端和服务器库。 它基于 Asio 提供异步 I/O 支持,支持 TLS 和非加密通信,并以 header-only 模式分发,易于集成。本笔记旨在记录 WebSocket++ 的基本使用方式,包括:
- 如何启动一个最小的 WebSocket 服务器
- 如何创建一个简单的客户端连接
- 如何通过消息处理函数处理客户端/服务器之间的通信
本示例采用的是 “no TLS” 配置,意味着通信内容是明文传输,适用于实验环境或内部测试。
WebSocket++ 的事件驱动架构基于以下核心回调函数:
┌───────────────┐
│ connect() 发起连接 │
└──────┬────────┘
▼
[连接成功] ➝ set_open_handler —— 连接建立时调用,可用来发送初始化数据
▼
set_message_handler —— 收到消息时调用(双向)
set_fail_handler —— 连接失败时调用
set_close_handler —— 连接关闭时调用
支持的数据类型包括:
- 文本消息(text):使用 msg->get_payload() 获取 std::string
- 二进制消息(binary):通过 get_payload() 仍可获取内容,但处理方式不同
自定义数据结构(如结构体)可通过 JSON 或手动序列化为字符串进行传输。更多高级功能(如广播、TLS、子协议、多线程支持等)可参考官方 examples/ 目录。
WebSocket 回显服务器代码简介
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <iostream>
using websocketpp::connection_hdl;
/*
* 使用无 TLS(不加密)的 Asio 配置:
* typedef websocketpp::server<websocketpp::config::asio> server;
*
* 这表示定义了一个基于 WebSocket++ 的服务器类型,使用 Asio 库作为底层网络实现。
* 此配置 不启用 TLS 加密,对应的连接协议是 ws://(非加密的 WebSocket)。
*
* ---------------------------- TLS 简介 ----------------------------
* TLS(Transport Layer Security)是传输层安全协议,类似于 HTTPS,
* 用于在客户端和服务器之间建立安全的加密连接。
*
* WebSocket 也支持 TLS。如果你使用 wss:// 开头的安全 WebSocket,就需要启用 TLS。
* 在 WebSocket++ 中,使用以下配置启用 TLS:
*
* #include <websocketpp/config/asio_tls.hpp>
* typedef websocketpp::server<websocketpp::config::asio_tls> server;
*
* 同时,你还需要提供服务器证书和私钥,例如:
*
* server.set_tls_init_handler([](websocketpp::connection_hdl) {
* auto ctx = std::make_shared<boost::asio::ssl::context>(boost::asio::ssl::context::tlsv12);
* ctx->use_certificate_chain_file("server.crt");
* ctx->use_private_key_file("server.key", boost::asio::ssl::context::pem);
* return ctx;
* });
*
* ----------------------- ws:// vs wss:// 对比 ------------------------
* - ws:// : 不加密的 WebSocket,默认端口 80,适用于测试或局域网通信
* - wss:// : 使用 TLS 加密的 WebSocket,默认端口 443,适用于互联网部署
*
* 生产环境建议使用 wss:// 来保护数据传输安全。
*/
typedef websocketpp::server<websocketpp::config::asio> server;
/*
* 简写类型:服务端消息指针
*
* message_ptr 是一个 std::shared_ptr 类型的智能指针,
* 指向接收到的 WebSocket 消息对象(message_type)。
* 它封装了消息的所有内容和元信息,如消息类型(opcode)、消息体(payload)等。
*
* 优点:
* - 智能指针会自动管理内存,无需手动释放;
* - 在回调函数中广泛使用,便于获取和处理消息内容。
*
* 常用接口:
* - msg->get_payload():
* 获取消息体内容,返回 std::string,一般用于 text 类型的消息。
*
* - msg->get_opcode():
* 获取消息的操作码(opcode),用于判断消息类型。
* 常见的操作码包括:
* * websocketpp::frame::opcode::text :文本消息
* * websocketpp::frame::opcode::binary :二进制消息
* * websocketpp::frame::opcode::close :关闭消息
* * websocketpp::frame::opcode::ping :Ping 心跳包
* * websocketpp::frame::opcode::pong :Pong 心跳响应
*
*
* -------------------------- 如何发送自定义结构体数据 --------------------------
*
*
* WebSocket 传输本质上是基于字符串或二进制,因此自定义结构体需序列化。
*
* 【方法一】使用 JSON 序列化库(如 nlohmann/json):
* struct Person {
* std::string name;
* int age;
* };
*
* void to_json(nlohmann::json& j, const Person& p) {
* j = nlohmann::json{{"name", p.name}, {"age", p.age}};
* }
*
* void from_json(const nlohmann::json& j, Person& p) {
* j.at("name").get_to(p.name);
* j.at("age").get_to(p.age);
* }
*
* // 发送端
* Person p = {"Alice", 25};
* nlohmann::json j = p;
* s->send(hdl, j.dump(), websocketpp::frame::opcode::text);
*
* // 接收端
* std::string str = msg->get_payload();
* Person p2 = nlohmann::json::parse(str);
*
* 【方法二】自定义字符串转换函数:
* struct MyData {
* int id;
* std::string content;
* };
*
* std::string MyData_to_string(const MyData& d) {
* return std::to_string(d.id) + "#" + d.content;
* }
*
* MyData string_to_MyData(const std::string& s) {
* size_t pos = s.find("#");
* return {std::stoi(s.substr(0, pos)), s.substr(pos + 1)};
* }
*
* // 发送时:
* MyData d = {42, "hello"};
* s->send(hdl, MyData_to_string(d), websocketpp::frame::opcode::text);
*
* // 接收时:
* MyData d2 = string_to_MyData(msg->get_payload());
*
* 注意:
* - 对于二进制消息,可以将结构体通过内存拷贝方式发送(适用于低层或固定格式)。
* - 建议使用 JSON 或字符串拼接,兼容性更好,也便于调试和跨语言通信。
*/
typedef server::message_ptr message_ptr;
/*
* 这是 WebSocket++ 中服务端收到消息时的回调函数。
* 它的函数签名及每个参数含义如下:
*
* void on_message(server* s, connection_hdl hdl, message_ptr msg)
*
* 参数说明:
*
* 1. server* s
* - 类型:指向 WebSocket++ 服务端对象的指针。
* - 作用:用于通过当前服务器对象发送消息(如 s->send())或
* 控制服务器行(如:s->stop_listening())。
* - 注意:需要用这个对象来调用发送消息的函数,不能直接用其他 server 实例。
*
* 2. websocketpp::connection_hdl hdl
* - 类型:connection_hdl 是 WebSocket++ 提供的 连接句柄(connection handle)。
* - 本质上是一个 std::weak_ptr<void>,可以唯一标识某个客户端连接。
* - 作用:
* - 用于标识和访问对应的连接。
* - 必须传入 send() 函数中作为目标连接。
* - 可通过 .lock() 获取实际连接对象的 shared_ptr(如果还未失效)。
*
* 3. message_ptr msg
* - 类型:message_ptr 是 WebSocket++ 提供的 消息智能指针,
* 本质是std::shared_ptr<message>。
* - 作用:
* - 封装了收到的消息内容和元信息(如类型、大小、操作码)。
* - 可通过 msg->get_payload() 获取消息的实际内容(若是 text 类型,就是字符串)。
* - 可通过 msg->get_opcode() 获取消息的类型(例如 text_frame 表示文本消息)。
* - 优点:作为智能指针,它自动管理内存,无需手动释放。
*
* 总结:
* - s:访问服务器的接口(可以发消息、控制服务器行为)。
* - hdl:标识客户端连接,用于指明消息的发送对象。
* - msg:保存了客户端发来的消息内容及元数据。
*
* ---------------------------------------------------------------------------
*
* WebSocket++ 四个核心事件说明(事件处理器):
*
* 1. set_open_handler
* - 触发时机:连接成功建立后(握手完成)。
* - 典型用途:初始化状态、发送欢迎消息、执行首次数据发送等。
*
* 2. set_message_handler
* - 触发时机:客户端或服务端收到消息时。
* - 参数包含消息内容和连接句柄,可用来解析消息、处理请求、做响应。
* - 本函数 on_message 就是该事件的回调处理器。
*
* 3. set_close_handler
* - 触发时机:连接关闭时。
* - 可用于释放资源、打印日志、更新连接状态等。
*
* 4. set_fail_handler
* - 触发时机:连接失败(如连接超时、握手失败)时。
* - 典型用途:错误重连、打印错误信息、上报失败原因等。
*
* 说明:所有事件处理器都接收一个 connection_hdl(连接句柄)作为参数,
* 并可通过指针访问连接或发送数据。
*
* -------------------------------------------------------------------
*
* WebSocket++ 客户端连接事件流程图说明:
*
* connect()
* │
* ┌──────────┴───────────┐
* ▼ ▼
* [连接失败] [连接成功]
* │ │
* set_fail_handler set_open_handler
* │
* send() / other 初始化操作
* │
* ┌──────────────┴───────────────┐
* ▼ ▼
* set_message_handler set_close_handler
* (收到服务端消息时) (连接断开时触发)
*
* 简要说明:
* - connect():发起连接请求。
* - set_fail_handler:若连接失败,如地址错误或网络异常,会触发该回调。
* - set_open_handler:连接成功后触发,可以在此处发送初始化数据。
* - set_message_handler:收到对方发来的消息时触发。
* - set_close_handler:连接关闭时触发(正常关闭或异常断开都会触发)。
*
* 通常推荐的顺序是:
* 1. init_asio()
* 2. set_access_channels(...) // 可选,用于调试输出
* 3. 设置四个 handler(open, message, close, fail)
* 4. get_connection() → connect() → run()
*/
void on_message(server* s, connection_hdl hdl, message_ptr msg) {
std::cout << "[Server] Received message: " << msg->get_payload() << std::endl;
// 如果收到特定命令,则停止监听(服务器不再接受新连接)
if (msg->get_payload() == "stop-listening") {
std::cout << "[Server] Stopping listening on port..." << std::endl;
s->stop_listening();
return;
}
// 尝试将原消息内容发回客户端(回显 echo)
try {
// 给 hdl 标识的客户端发送类型为 msg->get_opcode() 的消息 msg->get_payload()
s->send(hdl, msg->get_payload(), msg->get_opcode());
} catch (const websocketpp::exception& e) {
std::cout << "[Error] Echo failed: " << e.what() << std::endl;
}
}
int main() {
// 创建 WebSocket 服务器对象
server echo_server;
try {
/*
* 设置访问日志级别:
* - alevel(access level):控制访问级别的日志,例如连接、断开、消息等。
* - all:启用所有访问日志。调试阶段建议打开,可以看到连接、接收消息等细节。
*/
echo_server.set_access_channels(websocketpp::log::alevel::all);
/*
* 可选项:关闭帧内容的日志,避免日志输出过多
* - frame_payload 表示 WebSocket 帧的具体数据内容,通常是非常详细的。
* 如果你不需要查看每一帧的 payload,建议禁用。
*/
echo_server.clear_access_channels(websocketpp::log::alevel::frame_payload);
/*
* 初始化 Asio:
* - 必须在设置完 log 之后调用。
* - 该操作会初始化底层的 Boost.Asio 网络事件循环。
*/
echo_server.init_asio();
/*
* 设置消息处理函数:
* - 这是服务端最核心的回调函数之一。
* - 当有客户端向服务器发送消息时,此回调函数会被调用。
* - bind 的作用是将成员函数 `on_message` 绑定到 `echo_server` 实例上,
* 并指定接收两个参数:connection_hdl 和 message_ptr。
*/
echo_server.set_message_handler(
websocketpp::lib::bind(
&on_message, // 指向处理函数的指针
&echo_server, // 传入当前服务器对象
websocketpp::lib::placeholders::_1, // 第一个占位符参数:连接句柄
websocketpp::lib::placeholders::_2 // 第二个占位符参数:消息指针
)
);
// 设置监听端口(此处为 9002)
echo_server.listen(9002);
/*
* 启动异步接受连接:
* - 告诉服务器开始接受来自客户端的连接请求。
* - 这个调用会在后台准备 socket、创建连接、初始化状态等。
*/
echo_server.start_accept();
std::cout << "[Server] Listening on ws://localhost:9002" << std::endl;
/*
* 启动事件循环:
* - 这是 WebSocket++ 的主循环,类似于传统的 asio::io_service::run()
* - 它会处理所有事件(连接、接收消息、发送消息等)
* - 注意:这是一个阻塞函数,除非服务器被停止,否则不会返回。
*/
echo_server.run();
} catch (const websocketpp::exception& e) {
std::cout << "[Exception] " << e.what() << std::endl;
} catch (...) {
std::cout << "[Exception] Unknown exception occurred." << std::endl;
}
return 0;
}
WebSocket 回显客户端代码简介
#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>
#include <iostream>
typedef websocketpp::client<websocketpp::config::asio_client> client;
// 定义 client 类型为使用 Asio 的无 TLS WebSocket 客户端
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
// 定义消息指针类型(本质是 shared_ptr,指向 message 类型)
typedef websocketpp::config::asio_client::message_type::ptr message_ptr;
// ==============================
// 客户端接收消息时的处理函数
// ==============================
void on_message(client* c, websocketpp::connection_hdl hdl, message_ptr msg) {
std::cout << "on_message called with hdl: " << hdl.lock().get()
<< " and message: " << msg->get_payload()
<< std::endl;
websocketpp::lib::error_code ec;
// 收到的消息再发回服务器(echo)
c->send(hdl, msg->get_payload(), msg->get_opcode(), ec);
if (ec) {
std::cout << "Echo failed because: " << ec.message() << std::endl;
}
}
// ==============================
// 主函数
// ==============================
int main(int argc, char* argv[]) {
client c; // 创建 WebSocket 客户端对象
std::string uri = "ws://localhost:9002"; // 服务器的连接地址
if (argc == 2) {
uri = argv[1]; // 如果提供了命令行参数,就用参数作为 URI
}
try {
// 开启日志输出(除了消息内容)
c.set_access_channels(websocketpp::log::alevel::all);
c.clear_access_channels(websocketpp::log::alevel::frame_payload);
c.init_asio(); // 初始化 Asio(必须要调用)
// 设置消息处理函数(收到服务器消息后调用)
c.set_message_handler(bind(&on_message, &c, _1, _2));
// 尝试建立连接(但还不会真的连接)
websocketpp::lib::error_code ec;
client::connection_ptr con = c.get_connection(uri, ec);
if (ec) {
std::cout << "could not create connection because: " << ec.message() << std::endl;
return 0;
}
// 建立连接(注册连接请求)
c.connect(con);
// 启动 IO 事件循环(连接、收发消息都在这里完成)
c.run(); // run 会阻塞直到连接关闭
} catch (websocketpp::exception const & e) {
std::cout << e.what() << std::endl;
}
}