feat(libhttp): integrate WebSocket support into HttpServer

Merge WebSocketServer functionality into HttpServer so a single port can
serve both HTTP(S) and WebSocket(S) endpoints while preserving full backward
compatibility for existing HTTP interfaces.

Key changes:
- Add WebSocket route registration: ws(), onWsConnect(), onWsDisconnect()
- Add client management: sendToWsClient(), broadcastWs(), disconnectWsClient()
- Rewrite Session class using beast::tcp_stream for unified HTTP/WS handling
- Support automatic ws:// and wss:// upgrades on HTTP/HTTPS ports
- Thread-safe message queue with asio::post for cross-thread sends
- Update Makefile to link OpenSSL and pthread for TLS/WebSocket support
- Add test_server.cpp demonstrating HTTP + WebSocket mixed usage
This commit is contained in:
qing committed 2026-08-01 04:43:33 +08:00
1 parent e007990594
commit ba9a77fe66
4 files changed
+580 -108

No files matched your search

+383 -103
View File
@@ -5,6 +5,7 @@
#include <iomanip>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <boost/beast/websocket/ssl.hpp>
namespace pmc {
namespace net {
@@ -29,25 +30,30 @@ public:
// ===================== Session =====================
class HttpServer::Session : public std::enable_shared_from_this<Session> {
using tcp_stream = beast::tcp_stream;
using ssl_stream = asio::ssl::stream<tcp_stream&>;
using ws_stream = websocket::stream<tcp_stream&>;
using wss_stream = websocket::stream<ssl_stream&>;
public:
// HTTP 构造
Session(tcp::socket&& socket, HttpServer* server)
: socket_(std::move(socket)), server_(server), ssl_enabled_(false),
strand_(asio::make_strand(socket_.get_executor())) {}
: stream_(std::move(socket)), server_(server), ssl_enabled_(false),
is_websocket_(false), client_id_("") {}
// HTTPS 构造
Session(tcp::socket&& socket, asio::ssl::context& ssl_ctx, HttpServer* server)
: socket_(std::move(socket)), server_(server), ssl_enabled_(true),
strand_(asio::make_strand(socket_.get_executor())) {
ssl_stream_ = std::make_unique<asio::ssl::stream<tcp::socket>>(
std::move(socket_), ssl_ctx);
: stream_(std::move(socket)), server_(server), ssl_enabled_(true),
is_websocket_(false), client_id_("") {
ssl_stream_ = std::make_unique<ssl_stream>(stream_, ssl_ctx);
}
~Session() { close(); }
void run() {
if (ssl_enabled_) doSSLHandshake();
else doRead();
if (ssl_enabled_) {
doSSLHandshake();
} else {
doRead();
}
}
void setRemoteInfo(const std::string& ip, unsigned short port) {
@@ -55,16 +61,35 @@ public:
remote_port_ = port;
}
void wsSend(const std::string& message) {
auto exec = stream_.get_executor();
asio::post(exec, [self = shared_from_this(), message]() {
self->ws_send_queue_.push_back(message);
if (self->ws_send_queue_.size() == 1) {
self->doWsWrite();
}
});
}
std::string getClientId() const { return client_id_; }
std::string getRemoteAddress() const { return remote_ip_ + ":" + std::to_string(remote_port_); }
bool isWebSocket() const { return is_websocket_; }
private:
void close() {
beast::error_code ec;
if (ssl_enabled_ && ssl_stream_) {
ssl_stream_->shutdown(ec);
ssl_stream_->lowest_layer().close(ec);
} else if (socket_.is_open()) {
socket_.shutdown(tcp::socket::shutdown_send, ec);
socket_.close(ec);
if (is_websocket_) {
if (ssl_enabled_ && wss_stream_) {
wss_stream_->close(websocket::close_code::normal, ec);
} else if (ws_stream_) {
ws_stream_->close(websocket::close_code::normal, ec);
}
}
if (ssl_stream_) {
ssl_stream_->shutdown(ec);
}
stream_.socket().shutdown(tcp::socket::shutdown_send, ec);
stream_.close();
}
void doSSLHandshake() {
@@ -74,39 +99,60 @@ private:
ssl_stream_->async_handshake(
asio::ssl::stream_base::server,
asio::bind_executor(strand_,
[this, self](beast::error_code ec) {
if (!ec) {
std::cout << "[SSL] Handshake succeeded: "
<< remote_ip_ << ":" << remote_port_ << std::endl;
doRead();
} else {
std::cerr << "[SSL] Handshake error: "
<< remote_ip_ << ":" << remote_port_
<< " - " << ec.message()
<< " (code=" << ec.value() << ")" << std::endl;
ERR_print_errors_fp(stderr);
}
}));
[self](beast::error_code ec) {
if (ec) {
std::cerr << "[SSL] Handshake error: "
<< self->remote_ip_ << ":" << self->remote_port_
<< " - " << ec.message()
<< " (code=" << ec.value() << ")" << std::endl;
ERR_print_errors_fp(stderr);
return;
}
std::cout << "[SSL] Handshake succeeded: "
<< self->remote_ip_ << ":" << self->remote_port_ << std::endl;
self->doRead();
});
}
void doRead() {
auto self = shared_from_this();
request_ = {};
buffer_.consume(buffer_.size());
if (ssl_enabled_ && ssl_stream_) {
http::async_read(*ssl_stream_, buffer_, request_,
asio::bind_executor(strand_,
[this, self](beast::error_code ec, std::size_t) { handleRead(ec); }));
[self](beast::error_code ec, std::size_t) {
self->handleRead(ec);
});
} else {
http::async_read(socket_, buffer_, request_,
asio::bind_executor(strand_,
[this, self](beast::error_code ec, std::size_t) { handleRead(ec); }));
http::async_read(stream_, buffer_, request_,
[self](beast::error_code ec, std::size_t) {
self->handleRead(ec);
});
}
}
void handleRead(beast::error_code ec) {
if (!ec) {
if (isWebSocketUpgrade(request_)) {
std::string path = std::string(request_.target());
auto qpos = path.find('?');
if (qpos != std::string::npos) path = path.substr(0, qpos);
auto ws_handler = server_->findWsRoute(path);
if (ws_handler) {
ws_handler_ = ws_handler;
ws_path_ = path;
if (ssl_enabled_) {
doWssUpgrade();
} else {
doWsUpgrade();
}
return;
}
}
auto req_copy = std::move(request_);
request_ = {};
processRequest(std::move(req_copy));
} else if (ec != http::error::end_of_stream) {
std::cerr << "[Read] Error from " << remote_ip_ << ":" << remote_port_
@@ -114,6 +160,154 @@ private:
}
}
bool isWebSocketUpgrade(const http::request<http::string_body>& req) {
if (req.method() != http::verb::get) return false;
auto upgrade = req.find(http::field::upgrade);
if (upgrade == req.end()) return false;
std::string upgrade_val(upgrade->value());
boost::to_lower(upgrade_val);
boost::trim(upgrade_val);
if (upgrade_val != "websocket") return false;
auto connection = req.find(http::field::connection);
if (connection == req.end()) return false;
std::string conn_val(connection->value());
boost::to_lower(conn_val);
return conn_val.find("upgrade") != std::string::npos;
}
void doWsUpgrade() {
auto self = shared_from_this();
ws_stream_ = std::make_unique<ws_stream>(stream_);
ws_stream_->text(true);
ws_stream_->async_accept(
request_,
[self](beast::error_code ec) {
self->onWsAccept(ec);
});
}
void doWssUpgrade() {
auto self = shared_from_this();
wss_stream_ = std::make_unique<wss_stream>(*ssl_stream_);
wss_stream_->text(true);
wss_stream_->async_accept(
request_,
[self](beast::error_code ec) {
self->onWsAccept(ec);
});
}
void onWsAccept(beast::error_code ec) {
if (ec) {
std::cerr << "[WS] Accept error from " << remote_ip_ << ":" << remote_port_
<< ": " << ec.message() << std::endl;
return;
}
is_websocket_ = true;
client_id_ = server_->generateWsClientId();
server_->addWsClient(client_id_, shared_from_this());
std::cout << "[WS] Client " << client_id_ << " connected from "
<< remote_ip_ << ":" << remote_port_ << " at " << ws_path_ << std::endl;
if (server_->ws_connect_handler_) {
server_->ws_connect_handler_(client_id_, getRemoteAddress());
}
doWsRead();
}
void doWsRead() {
auto self = shared_from_this();
ws_buffer_.consume(ws_buffer_.size());
if (ssl_enabled_ && wss_stream_) {
wss_stream_->async_read(
ws_buffer_,
[self](beast::error_code ec, std::size_t bytes_transferred) {
self->onWsRead(ec, bytes_transferred);
});
} else if (ws_stream_) {
ws_stream_->async_read(
ws_buffer_,
[self](beast::error_code ec, std::size_t bytes_transferred) {
self->onWsRead(ec, bytes_transferred);
});
}
}
void onWsRead(beast::error_code ec, std::size_t bytes_transferred) {
if (ec == websocket::error::closed) {
onWsClose();
return;
}
if (ec) {
std::cerr << "[WS] Read error from " << client_id_ << ": " << ec.message() << std::endl;
onWsClose();
return;
}
std::string message = beast::buffers_to_string(ws_buffer_.data());
ws_buffer_.consume(bytes_transferred);
if (ws_handler_) {
auto self = shared_from_this();
ws_handler_(client_id_, message,
[self](const std::string& response) {
self->wsSend(response);
});
}
doWsRead();
}
void doWsWrite() {
if (ws_send_queue_.empty()) return;
auto self = shared_from_this();
std::string& message = ws_send_queue_.front();
auto write_handler = [self](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "[WS] Write error to " << self->client_id_ << ": " << ec.message() << std::endl;
self->onWsClose();
return;
}
self->ws_send_queue_.pop_front();
if (!self->ws_send_queue_.empty()) {
self->doWsWrite();
}
};
if (ssl_enabled_ && wss_stream_) {
wss_stream_->text(true);
wss_stream_->async_write(asio::buffer(message), write_handler);
} else if (ws_stream_) {
ws_stream_->text(true);
ws_stream_->async_write(asio::buffer(message), write_handler);
}
}
void onWsClose() {
if (client_id_.empty()) return;
std::cout << "[WS] Client " << client_id_ << " disconnected" << std::endl;
server_->removeWsClient(client_id_);
if (server_->ws_disconnect_handler_) {
server_->ws_disconnect_handler_(client_id_);
}
client_id_.clear();
is_websocket_ = false;
}
void processRequest(http::request<http::string_body> req) {
try {
auto resp = std::make_shared<http::response<http::string_body>>(
@@ -135,42 +329,49 @@ private:
auto self = shared_from_this();
if (ssl_enabled_ && ssl_stream_) {
http::async_write(*ssl_stream_, *response,
asio::bind_executor(strand_,
[this, self, response](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "[Write] Error to " << remote_ip_ << ":" << remote_port_
<< ": " << ec.message() << std::endl;
return;
}
if (!response->keep_alive()) close();
else doRead();
}));
[self, response](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "[Write] Error to " << self->remote_ip_ << ":" << self->remote_port_
<< ": " << ec.message() << std::endl;
return;
}
if (!response->keep_alive()) self->close();
else self->doRead();
});
} else {
http::async_write(socket_, *response,
asio::bind_executor(strand_,
[this, self, response](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "[Write] Error to " << remote_ip_ << ":" << remote_port_
<< ": " << ec.message() << std::endl;
return;
}
if (!response->keep_alive()) close();
else doRead();
}));
http::async_write(stream_, *response,
[self, response](beast::error_code ec, std::size_t) {
if (ec) {
std::cerr << "[Write] Error to " << self->remote_ip_ << ":" << self->remote_port_
<< ": " << ec.message() << std::endl;
return;
}
if (!response->keep_alive()) self->close();
else self->doRead();
});
}
}
tcp::socket socket_;
std::unique_ptr<asio::ssl::stream<tcp::socket>> ssl_stream_;
tcp_stream stream_;
std::unique_ptr<ssl_stream> ssl_stream_;
std::unique_ptr<ws_stream> ws_stream_;
std::unique_ptr<wss_stream> wss_stream_;
beast::flat_buffer buffer_;
beast::flat_buffer ws_buffer_;
http::request<http::string_body> request_;
HttpServer* server_;
bool ssl_enabled_;
asio::strand<asio::any_io_executor> strand_;
std::string remote_ip_ = "unknown";
unsigned short remote_port_ = 0;
bool is_websocket_;
std::string client_id_;
std::string ws_path_;
WebSocketMessageHandler ws_handler_;
std::deque<std::string> ws_send_queue_;
};
// ===================== Listener =====================
@@ -204,8 +405,10 @@ private:
}
void doAccept() {
auto self = shared_from_this();
acceptor_.async_accept(
[this, self = shared_from_this()](beast::error_code ec, tcp::socket socket) {
asio::make_strand(ioc_),
[self](beast::error_code ec, tcp::socket socket) {
if (!ec) {
auto ep = socket.remote_endpoint(ec);
std::string ip = ec ? "unknown" : ep.address().to_string();
@@ -213,17 +416,18 @@ private:
std::cout << "[Listener] New connection from " << ip << ":" << port << std::endl;
std::shared_ptr<Session> session;
if (ssl_enabled_ && ssl_ctx_) {
session = std::make_shared<Session>(std::move(socket), *ssl_ctx_, server_);
if (self->ssl_enabled_ && self->ssl_ctx_) {
session = std::make_shared<Session>(std::move(socket), *self->ssl_ctx_, self->server_);
} else {
session = std::make_shared<Session>(std::move(socket), server_);
session = std::make_shared<Session>(std::move(socket), self->server_);
}
session->setRemoteInfo(ip, port);
self->server_->addSession(session);
session->run();
} else {
std::cerr << "[Listener] Accept error: " << ec.message() << std::endl;
}
doAccept();
self->doAccept();
});
}
@@ -236,7 +440,6 @@ private:
// ===================== HttpServer 实现 =====================
// ---- 构造函数 ----
HttpServer::HttpServer(const std::string& address, unsigned short port, unsigned int threads)
: address_(address), port_(port), threads_(threads), ssl_enabled_(false),
ioc_(std::make_unique<asio::io_context>(threads)) {
@@ -255,7 +458,6 @@ HttpServer::HttpServer(const std::string& address, unsigned short port,
HttpServer::~HttpServer() { stop(); }
// ---- SSL 初始化 ----
void HttpServer::setPrivateKeyPassword(const std::string& password) {
private_key_password_ = password;
}
@@ -294,7 +496,6 @@ void HttpServer::initSSLContext() {
}
}
// ---- 启动/停止 ----
void HttpServer::start() {
if (running_) return;
try {
@@ -318,7 +519,11 @@ void HttpServer::start() {
}
std::cout << (ssl_enabled_ ? "HTTPS" : "HTTP")
<< " Server started on " << address_ << ":" << port_
<< " with " << threads_ << " threads\n";
<< " with " << threads_ << " threads";
if (!ws_handlers_.empty()) {
std::cout << " (WebSocket enabled, " << ws_handlers_.size() << " WS route(s))";
}
std::cout << "\n";
} catch (const std::exception& e) {
std::cerr << "[Start] Failed: " << e.what() << std::endl;
running_ = false;
@@ -328,6 +533,12 @@ void HttpServer::start() {
void HttpServer::stop() {
if (!running_) return;
running_ = false;
{
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
ws_clients_.clear();
}
closeAllSessions();
if (ioc_) ioc_->stop();
for (auto& t : worker_threads_) if (t.joinable()) t.join();
@@ -341,13 +552,12 @@ void HttpServer::run() {
if (ioc_) ioc_->run();
}
// ---- 状态查询 ----
bool HttpServer::isRunning() const { return running_; }
unsigned short HttpServer::getPort() const { return port_; }
std::string HttpServer::getAddress() const { return address_; }
bool HttpServer::isSSLEnabled() const { return ssl_enabled_; }
// ---- 路由注册 ----
// ---- HTTP 路由注册 ----
void HttpServer::get(const std::string& path, HttpRequestHandler handler) {
RouteEntry entry;
entry.path = path;
@@ -388,7 +598,6 @@ void HttpServer::del(const std::string& path, HttpRequestHandler handler) {
delete_handlers_.push_back(entry);
}
// ---- 新增 OPTIONS 路由注册 ----
void HttpServer::options(const std::string& path, HttpRequestHandler handler) {
RouteEntry entry;
entry.path = path;
@@ -407,6 +616,62 @@ void HttpServer::setStaticDirectory(const std::string& p) {
static_directory_ = p;
}
// ---- WebSocket 路由注册 ----
void HttpServer::ws(const std::string& path, WebSocketMessageHandler handler) {
WsRouteEntry entry;
entry.path = path;
entry.handler = handler;
ws_handlers_.push_back(entry);
}
void HttpServer::onWsConnect(WebSocketConnectHandler handler) {
ws_connect_handler_ = handler;
}
void HttpServer::onWsDisconnect(WebSocketDisconnectHandler handler) {
ws_disconnect_handler_ = handler;
}
bool HttpServer::sendToWsClient(const std::string& clientId, const std::string& message) {
std::shared_ptr<Session> session;
{
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
auto it = ws_clients_.find(clientId);
if (it != ws_clients_.end()) {
session = it->second;
}
}
if (session) {
session->wsSend(message);
return true;
}
return false;
}
size_t HttpServer::broadcastWs(const std::string& message) {
std::vector<std::shared_ptr<Session>> clients;
{
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
for (auto& pair : ws_clients_) {
clients.push_back(pair.second);
}
}
for (auto& session : clients) {
session->wsSend(message);
}
return clients.size();
}
void HttpServer::disconnectWsClient(const std::string& clientId) {
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
ws_clients_.erase(clientId);
}
size_t HttpServer::getWsClientCount() const {
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
return ws_clients_.size();
}
// ---- 通配符转换 ----
std::regex HttpServer::wildcardToRegex(const std::string& pattern) {
std::string r;
@@ -453,22 +718,48 @@ HttpRequestHandler HttpServer::findRoute(const std::string& method, const std::s
else if (method == "OPTIONS") handlers = &options_handlers_;
else return nullptr;
// 精确匹配优先
for (const auto& e : *handlers) {
if (!e.is_wildcard && e.path == path) return e.handler;
}
// 通配符匹配
for (const auto& e : *handlers) {
if (e.is_wildcard && matchPath(path, e.pattern)) return e.handler;
}
return nullptr;
}
// ---- 新增辅助方法:检查路由是否存在 ----
bool HttpServer::routeExists(const std::string& method, const std::string& path) {
return findRoute(method, path) != nullptr;
}
WebSocketMessageHandler HttpServer::findWsRoute(const std::string& path) {
for (const auto& e : ws_handlers_) {
if (e.path == path) return e.handler;
}
return nullptr;
}
// ---- WebSocket 客户端管理 ----
std::string HttpServer::generateWsClientId() {
return "ws_client_" + std::to_string(++ws_client_counter_);
}
void HttpServer::addWsClient(const std::string& clientId, std::shared_ptr<Session> session) {
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
ws_clients_[clientId] = session;
}
void HttpServer::removeWsClient(const std::string& clientId) {
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
ws_clients_.erase(clientId);
}
std::shared_ptr<HttpServer::Session> HttpServer::getWsClient(const std::string& clientId) {
std::lock_guard<std::mutex> lock(ws_clients_mutex_);
auto it = ws_clients_.find(clientId);
if (it != ws_clients_.end()) return it->second;
return nullptr;
}
// ---- 请求处理核心 ----
http::response<http::string_body> HttpServer::handleRequest(const http::request<http::string_body>& req) {
std::string target_path(req.target().data(), req.target().size());
@@ -486,7 +777,6 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
res.set(http::field::content_type, "text/plain");
res.keep_alive(req.keep_alive());
// 执行中间件
bool middleware_handled = false;
for (auto& m : middlewares_) {
if (!m(req, res, params)) {
@@ -500,10 +790,8 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
}
std::string method = http::to_string(req.method());
// ========== 处理 OPTIONS 预检请求 ==========
if (method == "OPTIONS") {
// 检查是否有自定义 OPTIONS 路由
auto handler = findRoute("OPTIONS", target_path);
if (handler) {
try {
@@ -516,34 +804,33 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
return res;
}
}
// 自动生成 OPTIONS 响应
auto origin = req.find(http::field::origin);
if (origin != req.end()) {
// 构建允许的方法列表
std::vector<std::string> all_methods = {"GET", "POST", "PUT", "DELETE", "OPTIONS"};
std::vector<std::string> allowed_methods;
// 检查每个方法是否有对应的路由
for (const auto& m : all_methods) {
if (m == "OPTIONS" || routeExists(m, target_path)) {
allowed_methods.push_back(m);
}
}
if (!allowed_methods.empty()) {
if (!allowed_methods.empty() || findWsRoute(target_path)) {
if (allowed_methods.empty()) allowed_methods.push_back("GET");
std::string allow_methods;
for (size_t i = 0; i < allowed_methods.size(); ++i) {
if (i > 0) allow_methods += ", ";
allow_methods += allowed_methods[i];
}
res.result(http::status::ok);
res.set(http::field::allow, allow_methods);
res.set(http::field::access_control_allow_origin,
res.set(http::field::access_control_allow_origin,
std::string(origin->value()));
res.set(http::field::access_control_allow_methods, allow_methods);
res.set(http::field::access_control_allow_headers,
res.set(http::field::access_control_allow_headers,
"Content-Type, Authorization, X-Requested-With, Accept, Origin");
res.set(http::field::access_control_max_age, "86400");
res.body() = "";
@@ -552,19 +839,15 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
return res;
}
}
// 没有 Origin 头或没有匹配的方法
res.result(http::status::not_found);
res.body() = "404 Not Found\n";
res.prepare_payload();
return res;
}
// ========== OPTIONS 处理结束 ==========
// 查找普通路由
auto handler = findRoute(method, target_path);
// 静态文件
if (!handler && !static_directory_.empty()) {
std::string file_path = static_directory_ + target_path;
std::ifstream file(file_path, std::ios::binary);
@@ -592,7 +875,6 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
}
}
// 路由处理
if (handler) {
try {
return handler(req, params);
@@ -605,14 +887,12 @@ http::response<http::string_body> HttpServer::handleRequest(const http::request<
}
}
// 404
res.result(http::status::not_found);
res.body() = "404 Not Found\n";
res.prepare_payload();
return res;
}
// ---- 状态显示 ----
void HttpServer::showStatus() const {
std::cout << "=== " << (ssl_enabled_ ? "HTTPS" : "HTTP") << " Server Status ===" << std::endl;
std::cout << "Protocol: " << (ssl_enabled_ ? "HTTPS (SSL/TLS)" : "HTTP") << std::endl;
@@ -638,9 +918,13 @@ void HttpServer::showStatus() const {
std::cout << " DELETE: " << delete_handlers_.size() << " handler(s)" << std::endl;
for (const auto& entry : delete_handlers_)
std::cout << " DELETE " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
std::cout << " OPTIONS: " << options_handlers_.size() << " handler(s)" << std::endl; // 新增
std::cout << " OPTIONS: " << options_handlers_.size() << " handler(s)" << std::endl;
for (const auto& entry : options_handlers_)
std::cout << " OPTIONS " << entry.path << (entry.is_wildcard ? " (wildcard)" : "") << std::endl;
std::cout << " WebSocket: " << ws_handlers_.size() << " route(s)" << std::endl;
for (const auto& entry : ws_handlers_)
std::cout << " WS " << entry.path << std::endl;
std::cout << " WebSocket clients: " << getWsClientCount() << std::endl;
std::cout << "Middlewares: " << middlewares_.size() << std::endl;
std::cout << "==========================" << std::endl;
}
@@ -664,10 +948,6 @@ void HttpServer::removeSession(const Session* session) {
void HttpServer::closeAllSessions() {
std::lock_guard<std::mutex> lock(sessions_mutex_);
for (auto& weak : sessions_) {
auto sp = weak.lock();
if (sp) { /* 会话析构时自动关闭 socket */ }
}
sessions_.clear();
}