实现一个异步 WebSocket 服务器(AsyncWebSocketServer),使其与之前定义的 INetwork 接口兼容,并能够处理 WebSocket 协议的握手、消息收发和连接管理
·
实现一个异步 WebSocket 服务器(AsyncWebSocketServer),使其与之前定义的 INetwork 接口兼容,并能够处理 WebSocket 协议的握手、消息收发和连接管理。
以下是基于 Boost.Beast 和 Boost.Asio 的完整实现,结合之前提到的 main 函数(修复了 lambda 捕获问题),并详细解释 WebSocket 协议的实现细节。
代码将在 Visual Studio 2022 中运行,支持 TCP、UDP 和 WebSocket 协议的统一接口。
涵盖实现步骤、代码示例、测试方法和注意事项。
一、目标与背景
- 目标:
- 实现 AsyncWebSocketServer 类,继承 INetwork 接口,支持异步 WebSocket 通信。
- 确保与 main 函数兼容,处理 lambda 捕获问题(network->Send)。
- 支持多客户端连接、异步消息收发和错误处理。
- 背景:
- main 函数使用 std::unique_ptr<INetwork> 管理服务器对象,通过协议字符串("WEBSOCKET")选择实现。
- 之前的问题(lambda 未捕获 network)已通过添加 [&network] 解决。
- WebSocket 协议基于 TCP,通过 HTTP 握手建立连接,使用数据帧传输消息。
二、实现 AsyncWebSocketServer
1. 前提条件
- 依赖库:
- Boost.Beast(处理 WebSocket 协议)。
- Boost.Asio(异步 I/O)。
- 安装方法(参考之前的说明):bash
.\vcpkg install boost-beast:x64-windows .\vcpkg integrate install
- 项目配置:
- Visual Studio 2022 中,添加 Boost 头文件和库路径。
- C++17 或更高标准,链接 Ws2_32.lib。
- INetwork 接口:cpp
#ifndef NETWORK_INTERFACE_H #define NETWORK_INTERFACE_H #include <string> #include <functional> class INetwork { public: virtual ~INetwork() = default; virtual bool Initialize(const std::string& ip, int port) = 0; virtual void Send(const std::string& data, const std::string& targetIp = "", int targetPort = 0, std::function<void(bool, const std::string&)> callback = nullptr) = 0; virtual void Receive(std::function<void(const std::string&, const std::string&, int)> callback) = 0; virtual void Close() = 0; virtual std::string GetError() const = 0; }; #endif
2. AsyncWebSocketServer 实现AsyncWebSocketServer.h:cpp
#ifndef ASYNC_WEBSOCKET_SERVER_H
#define ASYNC_WEBSOCKET_SERVER_H
#include "NetworkInterface.h"
#include <boost/beast.hpp>
#include <boost/asio.hpp>
#include <memory>
#include <string>
#include <list>
namespace beast = boost::beast;
namespace websocket = beast::websocket;
using tcp = boost::asio::ip::tcp;
class AsyncWebSocketServer : public INetwork {
public:
AsyncWebSocketServer();
~AsyncWebSocketServer() override;
bool Initialize(const std::string& ip, int port) override;
void Send(const std::string& data, const std::string& targetIp = "", int targetPort = 0,
std::function<void(bool, const std::string&)> callback = nullptr) override;
void Receive(std::function<void(const std::string&, const std::string&, int)> callback) override;
void Close() override;
std::string GetError() const override;
private:
class Connection; // 前向声明连接类
void StartAccept();
boost::asio::io_context io_context_;
std::unique_ptr<tcp::acceptor> acceptor_;
std::list<std::shared_ptr<Connection>> connections_; // 存储客户端连接
std::string error_msg_;
std::function<void(const std::string&, const std::string&, int)> receive_callback_;
};
#endif
AsyncWebSocketServer.cpp:cpp
#include "AsyncWebSocketServer.h"
#include <boost/beast.hpp>
#include <boost/asio.hpp>
#include <iostream>
using tcp = boost::asio::ip::tcp;
// 每个客户端的 WebSocket 连接
class AsyncWebSocketServer::Connection : public std::enable_shared_from_this<Connection> {
public:
Connection(boost::asio::io_context& io_context)
: ws_(io_context), strand_(io_context) {}
websocket::stream<tcp::socket>& GetWebSocket() { return ws_; }
void Start(std::function<void(const std::string&, const std::string&, int)> callback) {
// 异步执行 WebSocket 握手
ws_.async_accept(
boost::asio::bind_executor(strand_,
[this, self = shared_from_this(), callback](const boost::system::error_code& error) {
if (!error) {
StartRead(callback);
} else {
std::cerr << "WebSocket accept error: " << error.message() << std::endl;
}
}));
}
void Send(const std::string& data, std::function<void(bool, const std::string&)> callback) {
ws_.async_write(
boost::asio::buffer(data),
boost::asio::bind_executor(strand_,
[callback](const boost::system::error_code& error, std::size_t /*bytes_transferred*/) {
if (callback) {
callback(!error, error ? error.message() : "");
}
}));
}
std::string GetClientIp() const {
return ws_.next_layer().remote_endpoint().address().to_string();
}
int GetClientPort() const {
return ws_.next_layer().remote_endpoint().port();
}
private:
void StartRead(std::function<void(const std::string&, const std::string&, int)> callback) {
ws_.async_read(
buffer_,
boost::asio::bind_executor(strand_,
[this, self = shared_from_this(), callback](const boost::system::error_code& error, std::size_t /*bytes_transferred*/) {
if (!error) {
std::string data = beast::buffers_to_string(buffer_.data());
std::string client_ip = GetClientIp();
int client_port = GetClientPort();
callback(data, client_ip, client_port);
buffer_.consume(buffer_.size());
StartRead(callback); // 继续读取
} else if (error != beast::websocket::error::closed) {
std::cerr << "WebSocket read error: " << error.message() << std::endl;
}
}));
}
websocket::stream<tcp::socket> ws_;
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
beast::multi_buffer buffer_;
};
AsyncWebSocketServer::AsyncWebSocketServer() : acceptor_(nullptr) {}
AsyncWebSocketServer::~AsyncWebSocketServer() {
Close();
}
bool AsyncWebSocketServer::Initialize(const std::string& ip, int port) {
try {
acceptor_ = std::make_unique<tcp::acceptor>(
io_context_, tcp::endpoint(boost::asio::ip::address::from_string(ip), port));
StartAccept();
return true;
} catch (const std::exception& e) {
error_msg_ = e.what();
return false;
}
}
void AsyncWebSocketServer::StartAccept() {
auto connection = std::make_shared<Connection>(io_context_);
acceptor_->async_accept(
connection->GetWebSocket().next_layer(),
[this, connection](const boost::system::error_code& error) {
if (!error) {
connections_.push_back(connection);
connection->Start(receive_callback_);
StartAccept(); // 继续接受新连接
} else {
error_msg_ = error.message();
std::cerr << "Accept error: " << error_msg_ << std::endl;
}
});
}
void AsyncWebSocketServer::Send(const std::string& data, const std::string& targetIp, int targetPort,
std::function<void(bool, const std::string&)> callback) {
bool sent = false;
for (auto it = connections_.begin(); it != connections_.end();) {
auto& conn = *it;
try {
std::string client_ip = conn->GetClientIp();
int client_port = conn->GetClientPort();
if (targetIp.empty() || (client_ip == targetIp && client_port == targetPort)) {
conn->Send(data, callback);
sent = true;
}
++it;
} catch (const std::exception& e) {
// 客户端断开,移除连接
it = connections_.erase(it);
}
}
if (!sent && callback) {
callback(false, "No matching client found");
}
}
void AsyncWebSocketServer::Receive(std::function<void(const std::string&, const std::string&, int)> callback) {
receive_callback_ = callback;
io_context_.run(); // 启动事件循环
}
void AsyncWebSocketServer::Close() {
if (acceptor_) {
acceptor_->close();
acceptor_.reset();
}
for (auto& conn : connections_) {
try {
conn->GetWebSocket().close(websocket::close_code::normal);
} catch (const std::exception& e) {
std::cerr << "Close error: " << e.message() << std::endl;
}
}
connections_.clear();
io_context_.stop();
}
std::string AsyncWebSocketServer::GetError() const {
return error_msg_;
}
代码说明:
- Connection 类:
- 管理每个客户端的 WebSocket 连接,使用 websocket::stream<tcp::socket>。
- Start:执行异步握手(async_accept),然后调用 StartRead 读取数据。
- Send:异步发送 WebSocket 数据帧。
- strand_:确保线程安全,防止并发访问 socket。
- GetClientIp 和 GetClientPort:获取客户端 IP 和端口,用于 Send 匹配。
- AsyncWebSocketServer:
- Initialize:创建 TCP 接受器,绑定到指定 IP 和端口。
- StartAccept:异步接受新连接,创建 Connection 对象。
- Send:向匹配 targetIp 和 targetPort 的客户端发送数据,支持广播(targetIp 为空)。
- Receive:存储回调函数,启动 io_context_ 事件循环。
- Close:关闭所有连接和接受器,清理资源。
- connections_:维护客户端连接列表,支持多客户端管理。
三、main 函数集成以下是修复了 lambda 捕获问题的 main 函数,兼容 WebSocket、TCP 和 UDP:cpp
#include "TcpServer.h"
#include "UdpServer.h"
#include "AsyncWebSocketServer.h"
#include <memory>
#include <iostream>
int main() {
std::unique_ptr<INetwork> network;
std::string protocol = "WEBSOCKET"; // 可切换为 "TCP" 或 "UDP"
// 选择协议
if (protocol == "TCP") {
network = std::make_unique<TcpServer>();
} else if (protocol == "UDP") {
network = std::make_unique<UdpServer>();
} else if (protocol == "WEBSOCKET") {
network = std::make_unique<AsyncWebSocketServer>();
} else {
std::cerr << "Unsupported protocol" << std::endl;
return -1;
}
// 初始化服务器
if (!network->Initialize("127.0.0.1", 8090)) {
std::cerr << "Initialize failed: " << network->GetError() << std::endl;
return -1;
}
// 异步接收数据
network->Receive([&network](const std::string& data, const std::string& ip, int port) {
std::cout << "Received from " << ip << ":" << port << ": " << data << std::endl;
// 异步发送响应
network->Send("Server received: " + data, ip, port,
[](bool success, const std::string& error) {
if (!success) {
std::cerr << "Send failed: " << error << std::endl;
}
});
});
return 0;
}
修复说明:
- lambda 捕获:添加 [&network],按引用捕获 network,解决编译错误。
- 异步支持:AsyncWebSocketServer 使用 io_context_.run() 处理事件循环,main 函数无需 while (true)。
- Send 回调:添加回调函数,处理异步发送的成功或失败。
四、WebSocket 协议细节参考之前的 WebSocket 协议说明,AsyncWebSocketServer 实现涵盖以下关键点:
- 握手:
- ws_.async_accept 处理 HTTP 升级请求,验证 Sec-WebSocket-Key,返回 Sec-WebSocket-Accept。
- 数据帧:
- ws_.async_read 解析 WebSocket 帧(Opcode 0x1 文本帧),解码掩码数据。
- ws_.async_write 构造帧,无需掩码(服务器端)。
- 控制帧:
- 自动响应 Ping/Pong 帧。
- Close 方法发送关闭帧(websocket::close_code::normal)。
- 多客户端:
- connections_ 列表存储所有客户端连接,支持定向发送或广播。
五、测试 AsyncWebSocketServer
1. 测试工具
- wscat(推荐):bash
npm install -g wscat - 浏览器(JavaScript):javascript
const ws = new WebSocket("ws://127.0.0.1:8090"); ws.onopen = () => ws.send("Hello, WebSocket!"); ws.onmessage = (e) => console.log("Received: ", e.data); ws.onerror = (e) => console.error("Error: ", e); ws.onclose = () => console.log("Connection closed");
2. 测试步骤
- 编译和运行:
- 在 Visual Studio 2022 中编译 main.cpp(设置 protocol = "WEBSOCKET")。
- 运行程序,确认监听 127.0.0.1:8090:bash
netstat -a | findstr 8090
- 连接服务器:
- 使用 wscat:bash
wscat -c ws://127.0.0.1:8090 > Hello, WebSocket! - 或在浏览器开发者工具运行上述 JavaScript。
- 使用 wscat:bash
- 验证接收:
- 检查服务器输出:
Received from 127.0.0.1:<client_port>: Hello, WebSocket!
- 检查服务器输出:
- 验证发送:
- wscat 或浏览器应收到:
Server received: Hello, WebSocket!
- wscat 或浏览器应收到:
- 测试多客户端:
- 打开多个 wscat 实例,发送不同消息,验证服务器是否正确处理。
- 检查 connections_ 列表是否包含所有客户端。
- 测试关闭:
- 在 wscat 中按 Ctrl+C,或调用 ws.close()。
- 验证服务器是否移除断开连接的客户端(connections_.erase)。
3. 使用 SocketTool 测试(有限支持)
- SocketTool 不直接支持 WebSocket 数据帧,但可测试 HTTP 握手:
- 配置 TCP 客户端,连接 127.0.0.1:8090。
- 发送 HTTP 请求:http
GET / HTTP/1.1 Host: 127.0.0.1:8090 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 - 检查服务器是否返回 HTTP 101。
- 局限性:SocketTool 无法处理 WebSocket 数据帧,建议使用 wscat。
4. 调试
- 在 Visual Studio 2022 中:
- 设置断点在 Connection::StartRead 和 Send,检查数据帧和回调。
- 使用“诊断工具”监控网络流量。
- 查看 error_msg_ 和 std::cerr 输出,定位错误。
六、注意事项
- 异步事件循环:
- io_context_.run() 驱动所有异步操作,main 函数无需额外循环。
- 确保 Receive 只调用一次 io_context_.run(),避免重复运行。
- 客户端断开处理:
- Send 方法捕获异常,移除断开连接的客户端。
- 可添加心跳机制(Ping/Pong)检测客户端活跃状态:cpp
void Connection::StartPing() { boost::asio::steady_timer timer(ws_.get_executor()); timer.expires_after(std::chrono::seconds(30)); timer.async_wait([this, self = shared_from_this()](const boost::system::error_code& error) { if (!error) { ws_.async_ping({}, [](const boost::system::error_code&) {}); StartPing(); } }); }
- 线程安全:
- strand_ 确保每个 Connection 的异步操作顺序执行。
- 多线程支持需多个 io_context:cpp
std::vector<std::thread> threads; for (int i = 0; i < std::thread::hardware_concurrency(); ++i) { threads.emplace_back([&io_context] { io_context.run(); }); } for (auto& t : threads) t.join();
- 消息大小:
- 设置最大消息大小:cpp
ws_.read_message_max(64 * 1024 * 1024); // 64MB
- 设置最大消息大小:cpp
七、扩展建议
- TLS 支持:
- 使用 websocket::stream<boost::asio::ssl::stream<tcp::socket>> 支持 wss://:cpp
boost::asio::ssl::context ssl_context(boost::asio::ssl::context::tlsv12); ssl_context.use_certificate_chain_file("server.crt"); ssl_context.use_private_key_file("server.key", boost::asio::ssl::context::pem);
- 使用 websocket::stream<boost::asio::ssl::stream<tcp::socket>> 支持 wss://:cpp
- JSON 消息:
- 集成 nlohmann/json 序列化消息:cpp
#include <nlohmann/json.hpp> nlohmann::json j = {{"message", data}}; conn->Send(j.dump(), callback);
- 集成 nlohmann/json 序列化消息:cpp
- C++20 协程:
- 使用 co_await 简化异步逻辑:cpp
boost::asio::co_spawn(io_context_, [this]() -> boost::asio::awaitable<void> { co_await ws_.async_accept(boost::asio::use_awaitable); auto [error, bytes] = co_await ws_.async_read(buffer_, boost::asio::use_awaitable); if (!error) { std::string data = beast::buffers_to_string(buffer_.data()); receive_callback_(data, GetClientIp(), GetClientPort()); } }, boost::asio::detached);
- 使用 co_await 简化异步逻辑:cpp
- 日志记录:
- 使用 spdlog:cpp
#include <spdlog/spdlog.h> spdlog::info("Received from {}:{}: {}", client_ip, client_port, data);
- 使用 spdlog:cpp
八、总结
- 实现:AsyncWebSocketServer 使用 Boost.Beast 实现异步 WebSocket 通信,兼容 INetwork 接口。
- lambda 修复:通过 [&network] 捕获解决 network->Send 错误。
- 测试:使用 wscat 或浏览器验证握手和消息收发,SocketTool 仅限 HTTP 握手测试。
- 协议细节:处理 HTTP 握手、数据帧和关闭帧,支持多客户端。
- 参考资料:
- Boost.Beast:Boost.Beast
- RFC 6455:https://tools.ietf.org/html/rfc6455
如果需要更详细的 TLS 实现、协程支持或测试用例,请告诉我!
更多推荐


所有评论(0)