fix(libhttp): 删除遗留 WebSocketServer/WebSocketClient 并同步 CMakeLists 与 Makefile

This commit is contained in:
qing committed 2026-08-03 23:46:10 +08:00
1 parent 2f5fe8baaa
commit b37bf255d9
10 files changed
+85 -1322

No files matched your search

+1 -1
View File
@@ -168,7 +168,7 @@
| 库 | 说明 | | 库 | 说明 |
|----|------| |----|------|
| [libth](libth/) | 线程库:`Thread`、`Cv_wait`(条件变量等待)、`Fsm`、`ITask` 任务接口 | | [libth](libth/) | 线程库:`Thread`、`Cv_wait`(条件变量等待)、`Fsm`、`ITask` 任务接口 |
| [libhttp](libhttp/) | HTTP/WebSocket 库:`HttpServer`、`HttpClient`、`WebSocketServer/Client`(基于 Boost.Beast) | | [libhttp](libhttp/) | HTTP/WebSocket 库:`HttpServer`(含 WebSocket 服务/代理)、`HttpClient`(含 WebSocket 客户端,基于 Boost.Beast) |
| [libcrypt](libcrypt/) | 加密库:`AES_CBC`、`MyRSA`、`SHA256`、`Base64`、`Crypto_Basic` | | [libcrypt](libcrypt/) | 加密库:`AES_CBC`、`MyRSA`、`SHA256`、`Base64`、`Crypto_Basic` |
| [libsqlite3](libsqlite3/) | SQLite3 C++ 封装(Header-only) | | [libsqlite3](libsqlite3/) | SQLite3 C++ 封装(Header-only) |
| [libtry](libtry/) | 嵌入式脚本解释器(~741 行 C),手写递归下降词法/语法/执行引擎,支持变量/数组/函数/控制流,宽字符(中文标识符),用于嵌入式操作界面 shell | | [libtry](libtry/) | 嵌入式脚本解释器(~741 行 C),手写递归下降词法/语法/执行引擎,支持变量/数组/函数/控制流,宽字符(中文标识符),用于嵌入式操作界面 shell |
@@ -13,7 +13,7 @@
| P0 | 0 | 0 | 0 | 0 | | P0 | 0 | 0 | 0 | 0 |
| P1 | 6 | 0 | 0 | 6 | | P1 | 6 | 0 | 0 | 6 |
| P2 | 4 | 0 | 0 | 4 | | P2 | 4 | 0 | 0 | 4 |
| **合计** | **10** | **0** | **0** | **10** | | **合计** | **10** | **2** | **0** | **8** |
--- ---
@@ -47,8 +47,8 @@
| # | 问题 | 状态 | 说明 | | # | 问题 | 状态 | 说明 |
|---|------|------|------| |---|------|------|------|
| P3-1 | 遗留 WebSocketServer/WebSocketClient 未构建 | ❌ 未修复 | `WebSocketServer.cpp`/`WebSocketClient.cpp` 仅被 CMakeLists.txt 引用,**Makefile 未编译**,与 `HttpClient` 混用。其实现存在缺陷(无超时、`disconnectClient` 不真正关闭连接 L344-352、`onRead` 对错误路径未关闭),建议删除或维护 | | P3-1 | 遗留 WebSocketServer/WebSocketClient 未构建 | ✅ 已修复 | 已删除 `WebSocketServer.cpp`/`WebSocketClient.cpp`/`WebSocketServer.hpp`/`WebSocketClient.hpp`/`WebSocketClientBase.hpp`(未构建、与 `HttpClient` 别名同名冲突、有缺陷),并清理 Makefile/README 引用。WebSocket 能力由 `HttpServer` + `HttpClient` 提供 |
| P3-2 | CMakeLists.txt 过时 | ❌ 未修复 | CMakeLists 仍引用旧文件路径(`inc/us/HttpServer.hpp` 等)与旧拆分前源码,与 Makefile 实际构建不一致,易误导 | | P3-2 | CMakeLists.txt 过时 | ✅ 已修复 | CMakeLists 已重写对齐 Makefile:`libus_http.so`(6 个服务端 TU)+ `libus_httpclient.so`(HttpClient),移除遗留文件引用与错误库名 |
--- ---
@@ -83,8 +83,8 @@
- [ ] P2-5 代理后端读写超时 + 空闲保活 - [ ] P2-5 代理后端读写超时 + 空闲保活
- [ ] P2-6 最大连接数限制 - [ ] P2-6 最大连接数限制
- [ ] P2-2 TLS 校验可配置(至少提供 `verify_peer` 选项) - [ ] P2-2 TLS 校验可配置(至少提供 `verify_peer` 选项)
- [ ] P3-1 删除或维护遗留 WebSocketServer/WebSocketClient - [x] P3-1 删除或维护遗留 WebSocketServer/WebSocketClient → ✅ 已删除
- [ ] P3-2 同步 CMakeLists.txt 与 Makefile - [x] P3-2 同步 CMakeLists.txt 与 Makefile → ✅ 已重写对齐
--- ---
+45 -70
View File
@@ -13,10 +13,9 @@ else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -pipe -Wall") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3 -pipe -Wall")
endif() endif()
# Set library names # Library names (与 Makefile 保持一致)
set(SHARED_LIB_NAME "us_http") set(SERVER_LIB_NAME "us_http")
set(STATIC_LIB_NAME "us_http_static") set(CLIENT_LIB_NAME "us_httpclient")
set(FRAMEWORK_NAME "us")
# ===== Boost Library Configuration (Header-Only) ===== # ===== Boost Library Configuration (Header-Only) =====
if(DEFINED ENV{BOOST_ROOT}) if(DEFINED ENV{BOOST_ROOT})
@@ -41,86 +40,66 @@ include_directories(${Boost_INCLUDE_DIR})
set(INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc") set(INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/inc")
include_directories(${INCLUDE_DIR}) include_directories(${INCLUDE_DIR})
# Source files # Lowest-path OpenSSL/libcurl (macOS homebrew) — 可被环境覆盖
set(SOURCES set(EXTERNAL_LIB_DIR "/opt/homebrew/lib")
set(EXTERNAL_INC_DIR "/opt/homebrew/include")
include_directories(${EXTERNAL_INC_DIR})
# Server sources (HttpServer 拆分后的 6 个 TU)
set(SERVER_SOURCES
src/HttpServer.cpp src/HttpServer.cpp
src/WebSocketServer.cpp src/HttpServer_Listener.cpp
src/WebSocketClient.cpp src/HttpServer_Routes.cpp
src/HttpServer_Request.cpp
src/HttpServer_Ws.cpp
src/HttpServer_WsSession.cpp
) )
# Header files list # Client sources
set(HEADERS set(CLIENT_SOURCES
inc/${FRAMEWORK_NAME}/HttpServer.hpp src/HttpClient.cpp
inc/${FRAMEWORK_NAME}/HttpClient.hpp
inc/${FRAMEWORK_NAME}/WebSocketServer.hpp
inc/${FRAMEWORK_NAME}/WebSocketClient.hpp
) )
# Create shared library (DLL) # Server library: libus_http.so (HTTP + WebSocket server + WS proxy, 不依赖 curl)
add_library(${SHARED_LIB_NAME} SHARED ${SOURCES}) add_library(${SERVER_LIB_NAME} SHARED ${SERVER_SOURCES})
target_link_libraries(${SERVER_LIB_NAME}
${EXTERNAL_LIB_DIR}/libssl.a
${EXTERNAL_LIB_DIR}/libcrypto.a
pthread
)
# Create static library (LIB) # Client library: libus_httpclient.so (HttpClient, 依赖 curl)
add_library(${STATIC_LIB_NAME} STATIC ${SOURCES}) add_library(${CLIENT_LIB_NAME} SHARED ${CLIENT_SOURCES})
target_link_libraries(${CLIENT_LIB_NAME}
# Set properties for shared library ${EXTERNAL_LIB_DIR}/libssl.a
if(WIN32) ${EXTERNAL_LIB_DIR}/libcrypto.a
target_link_libraries(${SHARED_LIB_NAME} ${EXTERNAL_LIB_DIR}/libcurl.a
ws2_32 pthread
) )
else()
target_link_libraries(${SHARED_LIB_NAME}
pthread
)
endif()
# Set output directories # Set output directories
set_target_properties(${SHARED_LIB_NAME} ${STATIC_LIB_NAME} PROPERTIES set_target_properties(${SERVER_LIB_NAME} ${CLIENT_LIB_NAME} PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build" LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build"
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build" RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build"
ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build" ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/build"
) )
# Additional properties for Windows
if(WIN32)
set_target_properties(${SHARED_LIB_NAME} PROPERTIES
PREFIX ""
SUFFIX ".dll"
)
set_target_properties(${STATIC_LIB_NAME} PROPERTIES
PREFIX ""
SUFFIX ".lib"
)
else()
set_target_properties(${SHARED_LIB_NAME} PROPERTIES
PREFIX "lib"
SUFFIX ".so"
)
set_target_properties(${STATIC_LIB_NAME} PROPERTIES
PREFIX "lib"
SUFFIX ".a"
)
endif()
# Position independent code for static library on Unix
if(UNIX AND NOT APPLE)
set_target_properties(${STATIC_LIB_NAME} PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
set_target_properties(${SHARED_LIB_NAME} PROPERTIES
POSITION_INDEPENDENT_CODE ON
)
endif()
# Install targets # Install targets
install(TARGETS ${SHARED_LIB_NAME} ${STATIC_LIB_NAME} install(TARGETS ${SERVER_LIB_NAME} ${CLIENT_LIB_NAME}
LIBRARY DESTINATION lib LIBRARY DESTINATION lib
RUNTIME DESTINATION bin RUNTIME DESTINATION bin
ARCHIVE DESTINATION lib ARCHIVE DESTINATION lib
) )
# Install headers # Install headers
install(FILES ${HEADERS} install(FILES
DESTINATION include/${FRAMEWORK_NAME} inc/us/HttpServer.hpp
inc/us/HttpClient.hpp
DESTINATION include/us
)
install(FILES
inc/us/detail/HttpServerImpl.hpp
DESTINATION include/us/detail
) )
# Clean target # Clean target
@@ -133,13 +112,9 @@ add_custom_target(clear
message(STATUS "========================================") message(STATUS "========================================")
message(STATUS "Project: ${PROJECT_NAME}") message(STATUS "Project: ${PROJECT_NAME}")
message(STATUS "Version: ${PROJECT_VERSION}") message(STATUS "Version: ${PROJECT_VERSION}")
message(STATUS "Shared library: ${SHARED_LIB_NAME}") message(STATUS "Server library: ${SERVER_LIB_NAME}")
message(STATUS "Static library: ${STATIC_LIB_NAME}") message(STATUS "Client library: ${CLIENT_LIB_NAME}")
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}") message(STATUS "Install prefix: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}") message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID}")
if(MSVC)
message(STATUS "MSVC version: ${MSVC_VERSION}")
endif()
message(STATUS "BOOST_ROOT: ${BOOST_ROOT}")
message(STATUS "========================================") message(STATUS "========================================")
-6
View File
@@ -62,9 +62,6 @@ install: all
@mkdir -vp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail @mkdir -vp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail
@cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/HttpServer.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/ @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/HttpServer.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/
@cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/HttpClient.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/ @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/HttpClient.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/
@cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/WebSocketServer.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/
@cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/WebSocketClient.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/
@cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/WebSocketClientBase.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/
@cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/detail/HttpServerImpl.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail/ @cp -vf $(INCLU_SDIR)/$(FRAMEWORK_NAME)/detail/HttpServerImpl.hpp $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail/
@cp -vf $(BUILD_TDIR)/$(LIB_NAME).so $(INSTL_TDIR)/ @cp -vf $(BUILD_TDIR)/$(LIB_NAME).so $(INSTL_TDIR)/
@cp -vf $(BUILD_TDIR)/$(LIBC_NAME).so $(INSTL_TDIR)/ @cp -vf $(BUILD_TDIR)/$(LIBC_NAME).so $(INSTL_TDIR)/
@@ -74,9 +71,6 @@ uninstall:
@echo "uninstall UniServe_libhttp..." @echo "uninstall UniServe_libhttp..."
@rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/HttpServer.hpp @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/HttpServer.hpp
@rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/HttpClient.hpp @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/HttpClient.hpp
@rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/WebSocketServer.hpp
@rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/WebSocketClient.hpp
@rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/WebSocketClientBase.hpp
@rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail/HttpServerImpl.hpp @rm -vf $(INCLU_TDIR)/$(FRAMEWORK_NAME)/detail/HttpServerImpl.hpp
@rm -vf $(INSTL_TDIR)/$(LIB_NAME).so @rm -vf $(INSTL_TDIR)/$(LIB_NAME).so
@rm -vf $(INSTL_TDIR)/$(LIBC_NAME).so @rm -vf $(INSTL_TDIR)/$(LIBC_NAME).so
+34 -171
View File
@@ -30,13 +30,15 @@ libhttp/
├── inc/us/ ├── inc/us/
│ ├── HttpClient.hpp # HTTP 客户端 (header-only) │ ├── HttpClient.hpp # HTTP 客户端 (header-only)
│ ├── HttpServer.hpp # HTTP/HTTPS 服务器声明 │ ├── HttpServer.hpp # HTTP/HTTPS 服务器声明
│ ├── WebSocketClient.hpp # WebSocket 客户端声明 │ └── detail/
│ ├── WebSocketClientBase.hpp # WebSocket 客户端抽象基类 │ └── HttpServerImpl.hpp # Session/Listener 内部共享定义
│ └── WebSocketServer.hpp # WebSocket 服务器声明
└── src/ └── src/
├── HttpServer.cpp # HTTP/HTTPS 服务器实现 ├── HttpServer.cpp # 核心生命周期/SSL 初始化
├── WebSocketClient.cpp # WebSocket 客户端实现 ├── HttpServer_Listener.cpp # TCP 连接接受器
└── WebSocketServer.cpp # WebSocket 服务器实现 ├── HttpServer_Routes.cpp # 路由注册/匹配/通配符
├── HttpServer_Request.cpp # 请求分发/静态文件/CORS/HTTP 读写
├── HttpServer_Ws.cpp # WebSocket 路由注册/客户端管理
└── HttpServer_WsSession.cpp# WebSocket 会话/代理桥接
``` ```
--- ---
@@ -71,7 +73,7 @@ make install
make uninstall make uninstall
``` ```
> **注意**:Makefile 第 17 行默认只链接 `HttpServer.o`。如需 WebSocket 支持,需取消注释 `WebSocketServer.o` 和 `WebSocketClient.o`。 > **注意**:HttpServer 拆分为 6 个 TU,统一编译进 `libus_http.so`;WebSocket 能力已内置于 HttpServer(单端口同时服务 HTTP + WebSocket),客户端通过 `HttpClient` 提供,无独立 WebSocketServer/WebSocketClient 类。
### 4.2 CMake ### 4.2 CMake
@@ -82,7 +84,7 @@ cmake --build .
cmake --install . --prefix /usr/local cmake --install . --prefix /usr/local
``` ```
CMake 会同时产出 `libus_http.so` (动态) 和 `libus_http_static.a` (静态)。 CMake 会同时产出 `libus_http.so` (服务端) 和 `libus_httpclient.so` (客户端)。
### 4.3 一键脚本 ### 4.3 一键脚本
@@ -257,130 +259,9 @@ void delete_stream_sync(const std::string& url, std::function<void(const std::st
--- ---
### 5.3 WebSocketServer — WebSocket 服务器 ### 5.3 WebSocket 支持
**头文件**: `<us/WebSocketServer.hpp>` WebSocket 能力已内置于 `HttpServer`(单端口同时服务 HTTP + WebSocket),通过 `ws()`/`ws_proxy()` 注册路由;客户端通过 `HttpClient` 的 `ws_*` 方法提供(`using WebSocketClient = HttpClient`)。原独立 `WebSocketServer`/`WebSocketClient` 类已删除(未构建的历史遗留,见安全审计 P3-1)。
**实现**: `src/WebSocketServer.cpp`
#### 5.3.1 回调类型
```cpp
using WebSocketMessageHandler = std::function<void(
const std::string& clientId,
const std::string& message,
std::function<void(const std::string&)> sendCallback)>;
using WebSocketConnectHandler = std::function<void(
const std::string& clientId, const std::string& remoteAddress)>;
using WebSocketDisconnectHandler = std::function<void(
const std::string& clientId)>;
```
#### 5.3.2 公共接口
```cpp
WebSocketServer(unsigned short port, unsigned int threads = 2);
~WebSocketServer();
void start();
void stop();
bool isRunning() const;
unsigned short getPort() const;
// 事件注册
void onMessage(const std::string& path, WebSocketMessageHandler handler);
void onConnect(WebSocketConnectHandler handler);
void onDisconnect(WebSocketDisconnectHandler handler);
// 消息/客户端管理
bool sendToClient(const std::string& clientId, const std::string& message);
size_t broadcast(const std::string& message);
void disconnectClient(const std::string& clientId);
size_t getClientCount() const;
```
**特点**:
- PIMPL 模式:内部实现封装于 `Impl` 结构体
- 每个 `Session` 包含一个发送队列 (`std::deque<std::string>`),保证消息顺序
- 客户端 ID 由全局原子计数器自动生成,格式 `"client_N"`
- 线程安全:客户端列表受 `clientsMutex_` 保护
- 消息处理器支持路径路由(按 `path` 键索引)
**内部架构**:
```
WebSocketServer
├── Impl
│ ├── io_context
│ ├── Listener (内部类) → tcp::acceptor
│ └── threads_
└── Session (内部类,enable_shared_from_this)
├── websocket::stream<beast::tcp_stream>
├── sendQueue_ (std::deque)
└── 读取循环 → onMessage 回调 → sendCallback 写回
```
---
### 5.4 WebSocketClient — WebSocket 客户端
**头文件**: `<us/WebSocketClient.hpp>`, `<us/WebSocketClientBase.hpp>`
**实现**: `src/WebSocketClient.cpp`
#### 5.4.1 抽象基类 `WebSocketClientBase`
定义统一接口:
```cpp
using MessageCallback = std::function<void(const std::string&)>;
using ErrorCallback = std::function<void(const std::string&)>;
using ConnectCallback = std::function<void(bool)>;
using CloseCallback = std::function<void()>;
// 回调设置
virtual void setMessageCallback(MessageCallback) = 0;
virtual void setErrorCallback(ErrorCallback) = 0;
virtual void setConnectCallback(ConnectCallback) = 0;
virtual void setCloseCallback(CloseCallback) = 0;
// 连接管理
virtual bool connect(int timeout_ms = 5000) = 0;
virtual void disconnect() = 0;
virtual bool isConnected() const = 0;
// 消息发送
virtual bool send(const std::string& message) = 0;
virtual bool sendBinary(const void* data, size_t size) = 0;
// 配置
virtual void setAutoReconnect(bool enable, int interval_ms = 3000) = 0;
virtual void setHeartbeatInterval(int interval_ms = 30000) = 0;
// SSL 相关(纯虚函数,非 SSL 版本忽略参数)
virtual bool isSecure() const = 0;
virtual void setVerifyCertificate(bool verify) = 0;
virtual void setCertificateFile(const std::string& cert_file) = 0;
virtual void setPrivateKeyFile(const std::string& key_file) = 0;
virtual void setCertificateAuthorityFile(const std::string& ca_file) = 0;
```
#### 5.4.2 公共接口
```cpp
WebSocketClient(const std::string& host, const std::string& port,
const std::string& path = "/");
~WebSocketClient();
```
**特点**:
- PIMPL 模式:`Impl` 结构体封装所有 Boost 对象和状态
- 构造时自动启动 IO 线程
- 异步连接流程:`resolve → connect → WebSocket handshake`
- 发送队列受 `std::mutex` 和 `std::condition_variable` 保护
- 自动重连:`onError` 或 `onClose` 中若 `auto_reconnect=true` 则自动发起重连
- 非 SSL 版本:`isSecure()` 返回 `false`,SSL 配置方法为空实现
- `sendBinary` 将二进制数据包装为 `std::string` 后调用 `send`
--- ---
@@ -476,45 +357,36 @@ auto& headers = client.get_response_headers();
### 6.5 WebSocket 服务器 ### 6.5 WebSocket 服务器
```cpp ```cpp
pmc::net::WebSocketServer ws_server(9000, 2); pmc::net::HttpServer server("0.0.0.0", 9000, 4);
ws_server.onConnect([](const std::string& clientId, const std::string& addr) { server.onWsConnect([](const std::string& clientId, const std::string& addr) {
std::cout << "Client connected: " << clientId << " from " << addr << std::endl; std::cout << "Client connected: " << clientId << " from " << addr << std::endl;
}); });
ws_server.onMessage("/chat", [&ws_server](const std::string& clientId, server.ws("/chat", [](const std::string& clientId,
const std::string& message, const std::string& message,
auto sendCallback) { auto sendCallback) {
std::cout << "[" << clientId << "]: " << message << std::endl; std::cout << "[" << clientId << "]: " << message << std::endl;
ws_server.broadcast(clientId + ": " + message); sendCallback("echo: " + message);
}); });
ws_server.onDisconnect([](const std::string& clientId) { server.onWsDisconnect([](const std::string& clientId) {
std::cout << "Client disconnected: " << clientId << std::endl; std::cout << "Client disconnected: " << clientId << std::endl;
}); });
ws_server.start(); server.start();
``` ```
### 6.6 WebSocket 客户端 ### 6.6 WebSocket 客户端
```cpp ```cpp
pmc::net::WebSocketClient ws_client("localhost", "9000", "/chat"); pmc::net::HttpClient ws_client;
ws_client.setConnectCallback([](bool success) { if (ws_client.ws_connect("ws://localhost:9000/chat")) {
std::cout << (success ? "Connected!" : "Failed!") << std::endl; ws_client.ws_send("hello");
}); std::string reply = ws_client.ws_recv();
std::cout << "Received: " << reply << std::endl;
ws_client.setMessageCallback([](const std::string& msg) { ws_client.ws_close();
std::cout << "Received: " << msg << std::endl;
});
ws_client.setAutoReconnect(true, 3000);
ws_client.connect();
// 阻塞主线程保持连接
while (ws_client.isConnected()) {
std::this_thread::sleep_for(std::chrono::seconds(1));
} }
``` ```
@@ -548,22 +420,15 @@ while (ws_client.isConnected()) {
### 8.1 线程模型 ### 8.1 线程模型
- **HttpServer**: 每个工作线程独立运行 `io_context::run()`,所有异步操作通过 `strand` 序列化,同一 Session 的读写不会并发冲突 - **HttpServer**: 每个工作线程独立运行 `io_context::run()`,所有异步操作通过 `strand` 序列化,同一 Session 的读写不会并发冲突
- **WebSocketServer**: 同 HttpServer,工作线程池跑 `io_context` - **HttpClient**: 同步阻塞,无额外线程(HTTP 经 libcurl;WebSocket 经 Beast)
- **WebSocketClient**: 单 IO 线程,所有异步回调在该线程中执行
- **HttpClient**: 同步阻塞,无额外线程
### 8.2 PIMPL 模式 ### 8.2 会话管理
`WebSocketClient` 和 `WebSocketServer` 使用 PIMPL 隐藏 Boost 依赖,保持头文件轻量。
### 8.3 会话管理
- `HttpServer` 通过 `weak_ptr<Session>` 跟踪所有活跃会话,`stop()` 时遍历清理 - `HttpServer` 通过 `weak_ptr<Session>` 跟踪所有活跃会话,`stop()` 时遍历清理
- `WebSocketServer` 通过 `unordered_map<string, shared_ptr<Session>>` + mutex 管理
### 8.4 发送队列 ### 8.3 发送队列
`WebSocketServer::Session` 和 `WebSocketClient::Impl` 均采用 `std::deque` 消息队列,确保消息按序发送且不会因并发写入冲突。 `HttpServer::Session` 的 `ws_send_queue_` 与代理队列 (`proxy_to_backend_queue_`/`proxy_to_client_queue_`) 采用 `std::deque` 消息队列,确保消息按序发送且不会因并发写入冲突。
--- ---
@@ -596,11 +461,9 @@ micro_servers/
## 11. 已知限制 ## 11. 已知限制
1. **Makefile 默认不编译 WebSocket**:第 17 行仅链接 `HttpServer.o`,需手动启用 1. **无 HTTP/2 支持**:仅 HTTP/1.1
2. **WebSocketClient 无 SSL 实现**:当前仅有非加密版本;SSL 接口已声明但为空实现 2. **静态文件 MIME 有限**:仅支持 6 种常见类型
3. **无 HTTP/2 支持**:仅 HTTP/1.1 3. **无请求体大小限制**:依赖 Boost.Beast 默认配置(见安全审计 P1-1)
4. **静态文件 MIME 有限**:仅支持 6 种常见类型 4. **HttpClient 仅同步**:无异步 HTTP 客户端实现
5. **无请求体大小限制**:依赖 Boost.Beast 默认配置
6. **HttpClient 仅同步**:无异步 HTTP 客户端实现
``` ```
-63
View File
@@ -1,63 +0,0 @@
#ifndef PMC_NET_WEBSOCKET_CLIENT_HPP
#define PMC_NET_WEBSOCKET_CLIENT_HPP
#include "WebSocketClientBase.hpp"
namespace pmc {
namespace net {
/**
* @brief Asynchronous WebSocket client based on Boost.Beast (non-SSL version)
*/
class WebSocketClient : public WebSocketClientBase {
public:
/**
* @brief Constructor
* @param host Server hostname or IP address
* @param port Server port number
* @param path WebSocket path (default "/")
*/
WebSocketClient(const std::string& host, const std::string& port, const std::string& path = "/");
/**
* @brief Destructor
*/
~WebSocketClient();
// Implement WebSocketClientBase interface
void setMessageCallback(MessageCallback callback) override;
void setErrorCallback(ErrorCallback callback) override;
void setConnectCallback(ConnectCallback callback) override;
void setCloseCallback(CloseCallback callback) override;
bool connect(int timeout_ms = 5000) override;
void disconnect() override;
bool isConnected() const override;
bool send(const std::string& message) override;
bool sendBinary(const void* data, size_t size) override;
void setAutoReconnect(bool enable, int interval_ms = 3000) override;
void setHeartbeatInterval(int interval_ms = 30000) override;
std::string getHost() const override;
std::string getPort() const override;
std::string getPath() const override;
// SSL/TLS related (non-SSL version returns default values)
bool isSecure() const override { return false; }
void setVerifyCertificate(bool verify) override { (void)verify;/* Ignored */ }
void setCertificateFile(const std::string& cert_file) override { (void)cert_file; /* Ignored */ }
void setPrivateKeyFile(const std::string& key_file) override { (void)key_file;/* Ignored */ }
void setCertificateAuthorityFile(const std::string& ca_file) override { (void)ca_file;/* Ignored */ }
private:
// Forward declaration of implementation class
class Impl;
std::unique_ptr<Impl> impl_;
};
} // namespace net
} // namespace pmc
#endif // PMC_NET_WEBSOCKET_CLIENT_HPP
-61
View File
@@ -1,61 +0,0 @@
#ifndef PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
#define PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
#include <string>
#include <functional>
#include <memory>
namespace pmc {
namespace net {
/**
* @brief WebSocket client base class
*
* Defines the common interface for all WebSocket clients
*/
class WebSocketClientBase {
public:
// Callback function types
using MessageCallback = std::function<void(const std::string&)>;
using ErrorCallback = std::function<void(const std::string&)>;
using ConnectCallback = std::function<void(bool)>;
using CloseCallback = std::function<void()>;
virtual ~WebSocketClientBase() = default;
// Callback setters
virtual void setMessageCallback(MessageCallback callback) = 0;
virtual void setErrorCallback(ErrorCallback callback) = 0;
virtual void setConnectCallback(ConnectCallback callback) = 0;
virtual void setCloseCallback(CloseCallback callback) = 0;
// Connection management
virtual bool connect(int timeout_ms = 5000) = 0;
virtual void disconnect() = 0;
virtual bool isConnected() const = 0;
// Message sending
virtual bool send(const std::string& message) = 0;
virtual bool sendBinary(const void* data, size_t size) = 0;
// Configuration options
virtual void setAutoReconnect(bool enable, int interval_ms = 3000) = 0;
virtual void setHeartbeatInterval(int interval_ms = 30000) = 0;
// Information retrieval
virtual std::string getHost() const = 0;
virtual std::string getPort() const = 0;
virtual std::string getPath() const = 0;
// SSL/TLS related
virtual bool isSecure() const = 0;
virtual void setVerifyCertificate(bool verify) = 0;
virtual void setCertificateFile(const std::string& cert_file) = 0;
virtual void setPrivateKeyFile(const std::string& key_file) = 0;
virtual void setCertificateAuthorityFile(const std::string& ca_file) = 0;
};
} // namespace net
} // namespace pmc
#endif // PMC_NET_WEBSOCKET_CLIENT_BASE_HPP
-176
View File
@@ -1,176 +0,0 @@
// WebSocketServer.hpp - WebSocket server header file
// Based on Boost.Beast and Boost.Asio
#ifndef PMC_NET_WEBSOCKETSERVER_HPP
#define PMC_NET_WEBSOCKETSERVER_HPP
#include <functional>
#include <string>
#include <memory>
#include <unordered_map>
#include <mutex>
#include <atomic>
#include <vector>
#include <thread>
#include <boost/asio/io_context.hpp>
#include <boost/asio/ip/tcp.hpp>
namespace pmc::net {
/**
* @brief WebSocket connection handler type definition
*
* Handles message sending and receiving after WebSocket connection is established
*/
using WebSocketMessageHandler = std::function<void(
const std::string& clientId,
const std::string& message,
std::function<void(const std::string&)> sendCallback)>;
/**
* @brief WebSocket connection callback type definition
*/
using WebSocketConnectHandler = std::function<void(
const std::string& clientId,
const std::string& remoteAddress)>;
/**
* @brief WebSocket disconnection callback type definition
*/
using WebSocketDisconnectHandler = std::function<void(
const std::string& clientId)>;
/**
* @brief WebSocket server class
*
* WebSocket server implemented based on Boost.Beast and Boost.Asio
*/
class WebSocketServer {
public:
/**
* @brief Constructor
* @param port Listening port
* @param threads Number of threads (default 2)
*/
WebSocketServer(unsigned short port, unsigned int threads = 2);
/**
* @brief Destructor
*/
~WebSocketServer();
/**
* @brief Start the server
*/
void start();
/**
* @brief Stop the server
*/
void stop();
/**
* @brief Register message handler
* @param path Path (for routing)
* @param handler Message handler
*/
void onMessage(const std::string& path, WebSocketMessageHandler handler);
/**
* @brief Register connection handler
* @param handler Connection handler
*/
void onConnect(WebSocketConnectHandler handler);
/**
* @brief Register disconnection handler
* @param handler Disconnection handler
*/
void onDisconnect(WebSocketDisconnectHandler handler);
/**
* @brief Send message to a specific client
* @param clientId Client ID
* @param message Message content
* @return Whether sending was successful
*/
bool sendToClient(const std::string& clientId, const std::string& message);
/**
* @brief Broadcast message to all clients
* @param message Message content
* @return Number of clients that successfully received the message
*/
size_t broadcast(const std::string& message);
/**
* @brief Disconnect a specific client
* @param clientId Client ID
*/
void disconnectClient(const std::string& clientId);
/**
* @brief Get the number of currently connected clients
* @return Number of clients
*/
size_t getClientCount() const;
/**
* @brief Check if the server is running
* @return Running status
*/
bool isRunning() const { return running_; }
/**
* @brief Get the server listening port
* @return Port number
*/
unsigned short getPort() const { return port_; }
private:
// Internal Session class
class Session;
// Internal Listener class
class Listener;
// Internal implementation structure
struct Impl {
boost::asio::io_context ioc_;
std::shared_ptr<Listener> listener_;
std::vector<std::thread> threads_;
};
// Server configuration
unsigned short port_;
unsigned int threads_;
std::atomic<bool> running_{false};
// Internal implementation
std::unique_ptr<Impl> impl_;
// Generate client ID
std::string generateClientId() const;
// Add client
void addClient(const std::string& clientId, std::shared_ptr<Session> session);
// Remove client
void removeClient(const std::string& clientId);
// Get client session
std::shared_ptr<Session> getClient(const std::string& clientId);
// Callback functions
std::unordered_map<std::string, WebSocketMessageHandler> messageHandlers_;
WebSocketConnectHandler connectHandler_;
WebSocketDisconnectHandler disconnectHandler_;
// Client management
mutable std::mutex clientsMutex_;
std::unordered_map<std::string, std::shared_ptr<Session>> clients_;
};
} // namespace pmc::net
#endif // PMC_NET_WEBSOCKETSERVER_HPP
-386
View File
@@ -1,386 +0,0 @@
#include "us/WebSocketClient.hpp"
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/post.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <atomic>
namespace pmc {
namespace net {
namespace beast = boost::beast;
namespace asio = boost::asio;
using tcp = asio::ip::tcp;
// Internal implementation structure
struct WebSocketClient::Impl {
// Configuration parameters
std::string host;
std::string port;
std::string path;
// Boost objects
asio::io_context ioc;
tcp::resolver resolver;
beast::websocket::stream<beast::tcp_stream> ws;
// Thread management
std::thread io_thread;
std::atomic<bool> running{false};
// Callback functions
WebSocketClientBase::MessageCallback message_callback;
WebSocketClientBase::ErrorCallback error_callback;
WebSocketClientBase::ConnectCallback connect_callback;
WebSocketClientBase::CloseCallback close_callback;
// Message queue
std::queue<std::string> send_queue;
std::mutex queue_mutex;
std::condition_variable queue_cv;
std::atomic<bool> sending{false};
// Status flags
std::atomic<bool> connected{false};
std::atomic<bool> connecting{false};
std::atomic<bool> stopping{false};
// Configuration options
bool auto_reconnect{false};
int reconnect_interval_ms{3000};
int heartbeat_interval_ms{30000};
// Constructor
Impl(const std::string& host, const std::string& port, const std::string& path)
: host(host)
, port(port)
, path(path)
, resolver(asio::make_strand(ioc))
, ws(asio::make_strand(ioc))
{
}
// Destructor
~Impl() {
stop();
}
// Start IO thread
void start() {
if (!running) {
running = true;
io_thread = std::thread([this]() {
while (running) {
try {
ioc.run();
break;
} catch (const std::exception& e) {
std::cerr << "IO context exception: " << e.what() << std::endl;
}
}
});
}
}
// Stop IO thread
void stop() {
running = false;
stopping = true;
if (io_thread.joinable()) {
ioc.stop();
io_thread.join();
}
}
// Perform connection
void doConnect() {
if (connecting || connected) {
return;
}
connecting = true;
connected = false;
// Resolve hostname
resolver.async_resolve(host, port,
[this](beast::error_code ec, tcp::resolver::results_type results) {
onResolve(ec, results);
});
}
// Resolve completion callback
void onResolve(beast::error_code ec, tcp::resolver::results_type results) {
if (ec) {
onError("Resolve failed: " + ec.message());
return;
}
// Connect to resolved endpoints
beast::get_lowest_layer(ws).async_connect(results,
[this](beast::error_code ec, tcp::resolver::results_type::endpoint_type) {
onConnect(ec);
});
}
// Connection completion callback
void onConnect(beast::error_code ec) {
if (ec) {
onError("Connection failed: " + ec.message());
return;
}
// Set WebSocket options
ws.set_option(beast::websocket::stream_base::timeout::suggested(
beast::role_type::client));
// Perform WebSocket handshake
ws.async_handshake(host, path,
[this](beast::error_code ec) {
onHandshake(ec);
});
}
// Handshake completion callback
void onHandshake(beast::error_code ec) {
connecting = false;
if (ec) {
onError("Handshake failed: " + ec.message());
return;
}
connected = true;
// Call connection success callback
if (connect_callback) {
connect_callback(true);
}
// Start reading messages
doRead();
// Start sending queued messages
doSend();
}
// Read messages
void doRead() {
if (!connected) {
return;
}
ws.async_read(buffer,
[this](beast::error_code ec, std::size_t bytes_transferred) {
onRead(ec, bytes_transferred);
});
}
// Read completion callback
void onRead(beast::error_code ec, std::size_t bytes_transferred) {
if (ec) {
if (ec == beast::websocket::error::closed) {
// Normal close
onClose();
} else {
onError("Read failed: " + ec.message());
}
return;
}
// Process received message
std::string message = beast::buffers_to_string(buffer.data());
buffer.consume(bytes_transferred);
if (message_callback) {
message_callback(message);
}
// Continue reading
doRead();
}
// Send messages
void doSend() {
if (!connected || sending) {
return;
}
std::unique_lock<std::mutex> lock(queue_mutex);
if (send_queue.empty()) {
return;
}
sending = true;
std::string message = std::move(send_queue.front());
send_queue.pop();
lock.unlock();
ws.async_write(asio::buffer(message),
[this](beast::error_code ec, std::size_t bytes_transferred) {
onWrite(ec, bytes_transferred);
});
}
// Write completion callback
void onWrite(beast::error_code ec, std::size_t bytes_transferred) {
sending = false;
if (ec) {
onError("Write failed: " + ec.message());
return;
}
// Continue sending queued messages
doSend();
}
// Error handling
void onError(const std::string& error) {
connecting = false;
connected = false;
std::cerr << "WebSocket error: " << error << std::endl;
if (error_callback) {
error_callback(error);
}
// Auto reconnect
if (auto_reconnect && !stopping) {
std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_interval_ms));
asio::post(ioc, [this]() { doConnect(); });
}
}
// Connection close
void onClose() {
connected = false;
if (close_callback) {
close_callback();
}
// Auto reconnect
if (auto_reconnect && !stopping) {
std::this_thread::sleep_for(std::chrono::milliseconds(reconnect_interval_ms));
asio::post(ioc, [this]() { doConnect(); });
}
}
private:
beast::flat_buffer buffer;
};
// WebSocketClient implementation
WebSocketClient::WebSocketClient(const std::string& host, const std::string& port, const std::string& path)
: impl_(std::make_unique<Impl>(host, port, path))
{
impl_->start();
}
WebSocketClient::~WebSocketClient() {
disconnect();
}
void WebSocketClient::setMessageCallback(MessageCallback callback) {
impl_->message_callback = callback;
}
void WebSocketClient::setErrorCallback(ErrorCallback callback) {
impl_->error_callback = callback;
}
void WebSocketClient::setConnectCallback(ConnectCallback callback) {
impl_->connect_callback = callback;
}
void WebSocketClient::setCloseCallback(CloseCallback callback) {
impl_->close_callback = callback;
}
bool WebSocketClient::connect(int timeout_ms) {
if (impl_->connecting || impl_->connected) {
return false;
}
impl_->doConnect();
return true;
}
void WebSocketClient::disconnect() {
if (!impl_->connected && !impl_->connecting) {
return;
}
impl_->stopping = true;
asio::post(impl_->ioc, [this]() {
beast::error_code ec;
impl_->ws.close(beast::websocket::close_code::normal, ec);
impl_->connected = false;
impl_->connecting = false;
});
}
bool WebSocketClient::isConnected() const {
return impl_->connected;
}
bool WebSocketClient::send(const std::string& message) {
if (!impl_->connected) {
return false;
}
{
std::lock_guard<std::mutex> lock(impl_->queue_mutex);
impl_->send_queue.push(message);
}
impl_->queue_cv.notify_one();
asio::post(impl_->ioc, [this]() { impl_->doSend(); });
return true;
}
bool WebSocketClient::sendBinary(const void* data, size_t size) {
if (!impl_->connected) {
return false;
}
std::string message(static_cast<const char*>(data), size);
return send(message);
}
void WebSocketClient::setAutoReconnect(bool enable, int interval_ms) {
impl_->auto_reconnect = enable;
impl_->reconnect_interval_ms = interval_ms;
}
void WebSocketClient::setHeartbeatInterval(int interval_ms) {
impl_->heartbeat_interval_ms = interval_ms;
}
std::string WebSocketClient::getHost() const {
return impl_->host;
}
std::string WebSocketClient::getPort() const {
return impl_->port;
}
std::string WebSocketClient::getPath() const {
return impl_->path;
}
} // namespace net
} // namespace pmc
-383
View File
@@ -1,383 +0,0 @@
// WebSocketServer.cpp - WebSocket server implementation
// Based on Boost.Beast and Boost.Asio
#include "us/WebSocketServer.hpp"
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <mutex>
#include <deque>
#include <boost/beast/core.hpp>
#include <boost/beast/websocket.hpp>
#include <boost/asio/strand.hpp>
#include <boost/asio/ip/tcp.hpp>
namespace beast = boost::beast;
namespace websocket = beast::websocket;
namespace asio = boost::asio;
using tcp = asio::ip::tcp;
namespace pmc::net {
// Internal Session class implementation
class WebSocketServer::Session : public std::enable_shared_from_this<Session> {
public:
Session(tcp::socket socket, WebSocketServer* server)
: ws_(std::move(socket))
, server_(server) {
// Get remote address
auto endpoint = ws_.next_layer().socket().remote_endpoint();
remoteAddress_ = endpoint.address().to_string() + ":" + std::to_string(endpoint.port());
}
void start() {
// Generate client ID
if (server_) {
clientId_ = server_->generateClientId();
} else {
// If server is null, use default ID
static std::atomic<uint64_t> counter{0};
clientId_ = "client_" + std::to_string(++counter);
}
// Asynchronously accept WebSocket handshake
ws_.async_accept(
beast::bind_front_handler(
&Session::onAccept,
shared_from_this()));
}
void send(const std::string& message) {
// Add message to send queue
bool write_in_progress = !sendQueue_.empty();
sendQueue_.push_back(message);
if (!write_in_progress) {
// Start asynchronous write
doWrite();
}
}
const std::string& getClientId() const { return clientId_; }
const std::string& getRemoteAddress() const { return remoteAddress_; }
private:
websocket::stream<beast::tcp_stream> ws_;
beast::flat_buffer buffer_;
std::deque<std::string> sendQueue_;
WebSocketServer* server_;
std::string clientId_;
std::string remoteAddress_;
void onAccept(beast::error_code ec) {
if (ec) {
std::cerr << "WebSocket accept error: " << ec.message() << std::endl;
return;
}
// Add to server client list
if (server_) {
server_->addClient(clientId_, shared_from_this());
// Call connection handler
if (server_->connectHandler_) {
server_->connectHandler_(clientId_, remoteAddress_);
}
}
// Start reading messages
doRead();
}
void doRead() {
// Asynchronously read message
ws_.async_read(
buffer_,
beast::bind_front_handler(
&Session::onRead,
shared_from_this()));
}
void onRead(beast::error_code ec, std::size_t bytes_transferred) {
if (ec == websocket::error::closed) {
// Normal close
if (server_) {
server_->removeClient(clientId_);
if (server_->disconnectHandler_) {
server_->disconnectHandler_(clientId_);
}
}
return;
}
if (ec) {
std::cerr << "WebSocket read error: " << ec.message() << std::endl;
if (server_) {
server_->removeClient(clientId_);
if (server_->disconnectHandler_) {
server_->disconnectHandler_(clientId_);
}
}
return;
}
// Process received message
std::string message = beast::buffers_to_string(buffer_.data());
buffer_.consume(bytes_transferred);
if (server_) {
// Find message handler
for (const auto& handler : server_->messageHandlers_) {
// Path matching logic can be added here
handler.second(clientId_, message, [this](const std::string& response) {
send(response);
});
}
}
// Continue reading
doRead();
}
void doWrite() {
if (sendQueue_.empty()) {
return;
}
// Get first message from queue
auto message = sendQueue_.front();
// Asynchronously write message
ws_.async_write(
asio::buffer(message),
beast::bind_front_handler(
&Session::onWrite,
shared_from_this()));
}
void onWrite(beast::error_code ec, std::size_t bytes_transferred) {
(void)bytes_transferred;
if (ec) {
std::cerr << "WebSocket write error: " << ec.message() << std::endl;
if (server_) {
server_->removeClient(clientId_);
if (server_->disconnectHandler_) {
server_->disconnectHandler_(clientId_);
}
}
return;
}
// Remove sent message
sendQueue_.pop_front();
// Continue sending next message in queue
if (!sendQueue_.empty()) {
doWrite();
}
}
};
// Internal Listener class implementation
class WebSocketServer::Listener : public std::enable_shared_from_this<Listener> {
public:
static std::shared_ptr<Listener> create(asio::io_context& ioc, tcp::endpoint endpoint, WebSocketServer* server) {
return std::shared_ptr<Listener>(new Listener(ioc, endpoint, server));
}
void run() {
if (!acceptor_.is_open()) {
throw std::runtime_error("Acceptor is not open");
}
doAccept();
}
private:
Listener(asio::io_context& ioc, tcp::endpoint endpoint, WebSocketServer* server)
: ioc_(ioc)
, acceptor_(asio::make_strand(ioc))
, server_(server) {
beast::error_code ec;
// Open acceptor
acceptor_.open(endpoint.protocol(), ec);
if (ec) {
std::cerr << "Failed to open acceptor: " << ec.message() << std::endl;
throw std::runtime_error("Failed to open acceptor: " + ec.message());
}
// Set address reuse
acceptor_.set_option(asio::socket_base::reuse_address(true), ec);
if (ec) {
std::cerr << "Failed to set reuse address: " << ec.message() << std::endl;
throw std::runtime_error("Failed to set reuse address: " + ec.message());
}
// Bind to endpoint
acceptor_.bind(endpoint, ec);
if (ec) {
std::cerr << "Failed to bind to endpoint: " << ec.message() << std::endl;
throw std::runtime_error("Failed to bind to endpoint: " + ec.message());
}
// Start listening
acceptor_.listen(asio::socket_base::max_listen_connections, ec);
if (ec) {
std::cerr << "Failed to listen: " << ec.message() << std::endl;
throw std::runtime_error("Failed to listen: " + ec.message());
}
}
void doAccept() {
acceptor_.async_accept(
asio::make_strand(ioc_),
beast::bind_front_handler(
&Listener::onAccept,
shared_from_this()));
}
void onAccept(beast::error_code ec, tcp::socket socket) {
if (ec) {
std::cerr << "Accept error: " << ec.message() << std::endl;
} else {
// Create new Session to handle connection
std::make_shared<Session>(std::move(socket), server_)->start();
}
// Continue accepting new connections
doAccept();
}
asio::io_context& ioc_;
tcp::acceptor acceptor_;
WebSocketServer* server_;
};
WebSocketServer::WebSocketServer(unsigned short port, unsigned int threads)
: port_(port)
, threads_(threads)
, impl_(std::make_unique<Impl>()) {}
WebSocketServer::~WebSocketServer() {
stop();
}
void WebSocketServer::start() {
if (running_) {
return;
}
// Create listener
tcp::endpoint endpoint(tcp::v4(), port_);
impl_->listener_ = Listener::create(impl_->ioc_, endpoint, this);
// Start listener
impl_->listener_->run();
// Start worker threads
for (unsigned int i = 0; i < threads_; ++i) {
impl_->threads_.emplace_back([this]() {
impl_->ioc_.run();
});
}
running_ = true;
std::cout << "WebSocketServer started on port " << port_ << " with " << threads_ << " threads" << std::endl;
}
void WebSocketServer::stop() {
if (!running_) {
return;
}
// Stop io_context
impl_->ioc_.stop();
// Wait for all threads to finish
for (auto& thread : impl_->threads_) {
if (thread.joinable()) {
thread.join();
}
}
impl_->threads_.clear();
running_ = false;
std::cout << "WebSocketServer stopped" << std::endl;
}
void WebSocketServer::onMessage(const std::string& path, WebSocketMessageHandler handler) {
messageHandlers_[path] = std::move(handler);
}
void WebSocketServer::onConnect(WebSocketConnectHandler handler) {
connectHandler_ = std::move(handler);
}
void WebSocketServer::onDisconnect(WebSocketDisconnectHandler handler) {
disconnectHandler_ = std::move(handler);
}
bool WebSocketServer::sendToClient(const std::string& clientId, const std::string& message) {
std::lock_guard<std::mutex> lock(clientsMutex_);
auto it = clients_.find(clientId);
if (it != clients_.end()) {
it->second->send(message);
return true;
}
return false;
}
size_t WebSocketServer::broadcast(const std::string& message) {
std::lock_guard<std::mutex> lock(clientsMutex_);
size_t count = 0;
for (auto& pair : clients_) {
pair.second->send(message);
count++;
}
return count;
}
void WebSocketServer::disconnectClient(const std::string& clientId) {
std::lock_guard<std::mutex> lock(clientsMutex_);
auto it = clients_.find(clientId);
if (it != clients_.end()) {
// Should close WebSocket connection here
// Currently only removing from list
clients_.erase(it);
}
}
size_t WebSocketServer::getClientCount() const {
std::lock_guard<std::mutex> lock(clientsMutex_);
return clients_.size();
}
std::string WebSocketServer::generateClientId() const {
static std::atomic<uint64_t> counter{0};
return "client_" + std::to_string(++counter);
}
void WebSocketServer::addClient(const std::string& clientId, std::shared_ptr<Session> session) {
std::lock_guard<std::mutex> lock(clientsMutex_);
clients_[clientId] = session;
}
void WebSocketServer::removeClient(const std::string& clientId) {
std::lock_guard<std::mutex> lock(clientsMutex_);
clients_.erase(clientId);
}
std::shared_ptr<WebSocketServer::Session> WebSocketServer::getClient(const std::string& clientId) {
std::lock_guard<std::mutex> lock(clientsMutex_);
auto it = clients_.find(clientId);
if (it != clients_.end()) {
return it->second;
}
return nullptr;
}
} // namespace pmc::net