fix(libhttp): 删除遗留 WebSocketServer/WebSocketClient 并同步 CMakeLists 与 Makefile
This commit is contained in:
1 parent
2f5fe8baaa
commit
b37bf255d9
10 files changed
+85
-1322
No files matched your search
@@ -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
@@ -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 "========================================")
|
||||||
@@ -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
@@ -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 客户端实现
|
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
Reference in new issue
Block a user