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;
}
}