From e4831522a47ef77d4dd436ef4e3ea98775df6366 Mon Sep 17 00:00:00 2001 From: qing <1776177662@qq.com> Date: Thu, 30 Jul 2026 12:38:31 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=96=B0=E5=A2=9E=20libhttp/libtry/pym?= =?UTF-8?q?sg/README.robot.md=20=E6=8A=80=E6=9C=AF=E6=89=8B=E5=86=8C?= =?UTF-8?q?=EF=BC=8C=E6=9B=B4=E6=96=B0=20BotOps/README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libhttp/README.robot.md | 606 ++++++++++++++++++++++++++++ libtry/README.robot.md | 856 ++++++++++++++++++++++++++++++++++++++++ pymsg/README.robot.md | 764 +++++++++++++++++++++++++++++++++++ 3 files changed, 2226 insertions(+) create mode 100644 libhttp/README.robot.md create mode 100644 libtry/README.robot.md create mode 100644 pymsg/README.robot.md diff --git a/libhttp/README.robot.md b/libhttp/README.robot.md new file mode 100644 index 0000000..cf6ef54 --- /dev/null +++ b/libhttp/README.robot.md @@ -0,0 +1,606 @@ +# libhttp — C++20 HTTP / WebSocket 网络库 + +## 1. 模块概览 + +`libhttp` 是 `micro_servers` 项目的基础网络通信层,封装了 HTTP/HTTPS 服务器、HTTP 客户端(基于 libcurl)、WebSocket 服务器和 WebSocket 客户端四大功能。 +以 C++20 标准编写,编译为动态库 `libus_http.so` 和静态库 `libus_http_static.a`,命名空间 `pmc::net`。 + +| 属性 | 值 | +|----------|-----------------------------| +| 版本 | 0.0.1 | +| 语言标准 | C++20 | +| 许可证 | MIT | +| 框架名 | `us` | +| 库名 | `libus_http` | +| 命名空间 | `pmc::net` | + +**项目角色**:`libhttp` 被 `gateway` 模块和 `core` 模块直接依赖,承担所有入站请求的接收、路由、SSL 卸载和 WebSocket 长连接管理。 + +--- + +## 2. 文件清单 + +``` +libhttp/ +├── .gitignore # 构建产物忽略规则 +├── CMakeLists.txt # CMake 构建 (v3.12+) +├── LICENSE # MIT License +├── Makefile # GNU Make 构建 +├── _set.sh # 一键编译安装脚本 +├── inc/us/ +│ ├── HttpClient.hpp # HTTP 客户端 (header-only) +│ ├── HttpServer.hpp # HTTP/HTTPS 服务器声明 +│ ├── WebSocketClient.hpp # WebSocket 客户端声明 +│ ├── WebSocketClientBase.hpp # WebSocket 客户端抽象基类 +│ └── WebSocketServer.hpp # WebSocket 服务器声明 +└── src/ + ├── HttpServer.cpp # HTTP/HTTPS 服务器实现 + ├── WebSocketClient.cpp # WebSocket 客户端实现 + └── WebSocketServer.cpp # WebSocket 服务器实现 +``` + +--- + +## 3. 外部依赖 + +| 依赖 | 用途 | 备注 | +|------------------|-----------------------------------|---------------------------| +| Boost 1.91+ | Asio (I/O)、Beast (HTTP/WS)、Algorithms | 默认 `BOOST_ROOT=C:/local/boost_1_91_0`,可通过环境变量覆盖 | +| OpenSSL | TLS/SSL 加密传输 | 通过 Boost.Asio.SSL 调用 | +| libcurl | HTTP 客户端请求 | 仅 `HttpClient.hpp` 依赖 | +| pthread (Unix) | 多线程 | CMake 自动链接 | +| ws2_32 (Windows) | Winsock | CMake 自动链接 | + +--- + +## 4. 构建 + +### 4.1 GNU Make + +```bash +# 编译 (仅 HttpServer.o → libus_http.so) +make + +# 清理 +make clear + +# 安装到 /lib 和 /usr/include/us/ +make install + +# 卸载 +make uninstall +``` + +> **注意**:Makefile 第 17 行默认只链接 `HttpServer.o`。如需 WebSocket 支持,需取消注释 `WebSocketServer.o` 和 `WebSocketClient.o`。 + +### 4.2 CMake + +```bash +mkdir build && cd build +cmake .. -DBOOST_ROOT=/path/to/boost +cmake --build . +cmake --install . --prefix /usr/local +``` + +CMake 会同时产出 `libus_http.so` (动态) 和 `libus_http_static.a` (静态)。 + +### 4.3 一键脚本 + +```bash +bash _set.sh # make clear → make → make install +``` + +--- + +## 5. API 参考 + +### 5.1 HttpServer — HTTP/HTTPS 服务器 + +**头文件**: `` +**实现**: `src/HttpServer.cpp` + +#### 5.1.1 类型别名 + +```cpp +using HttpRequestHandler = std::function( + const http::request& req, + const std::unordered_map& params)>; + +using MiddlewareHandler = std::function& req, + http::response& res, + const std::unordered_map& params)>; +``` + +- **`HttpRequestHandler`**: 路由处理函数,接收 Boost.Beast 请求对象和已解析的查询参数,返回 HTTP 响应。 +- **`MiddlewareHandler`**: 中间件函数,返回 `false` 表示拦截(短路后续处理),`true` 表示放行。 + +#### 5.1.2 构造函数 + +| 签名 | 说明 | +|----------------------------------------------------------------------------------------|------------------------| +| `HttpServer(address, port, threads = 1)` | 纯 HTTP 模式 | +| `HttpServer(address, port, cert_file, key_file, threads = 1)` | HTTPS 模式(自动启用 SSL) | + +- `address`: 监听地址,如 `"0.0.0.0"` +- `port`: 监听端口 +- `threads`: 工作线程数(即 `io_context` 线程池大小) +- `cert_file` / `key_file`: PEM 格式证书和私钥路径 + +#### 5.1.3 路由注册 + +```cpp +void get(const std::string& path, HttpRequestHandler handler); +void post(const std::string& path, HttpRequestHandler handler); +void put(const std::string& path, HttpRequestHandler handler); +void del(const std::string& path, HttpRequestHandler handler); +void options(const std::string& path, HttpRequestHandler handler); +void use(MiddlewareHandler middleware); +``` + +- 路径支持通配符:`*` (任意字符) 和 `?` (单个字符),自动编译为 `std::regex`(大小写不敏感)。 +- 匹配优先级:精确匹配 > 通配符匹配。 +- 请求方法字符串对应:`"GET"`, `"POST"`, `"PUT"`, `"DELETE"`, `"OPTIONS"`。 + +#### 5.1.4 中间件 + +```cpp +void use(MiddlewareHandler middleware); +``` + +中间件在路由匹配之前执行,按注册顺序依次调用。任意中间件返回 `false` 时,请求处理结束,响应直接返回客户端。 + +#### 5.1.5 静态文件服务 + +```cpp +void setStaticDirectory(const std::string& path); +``` + +设置静态资源根目录。当请求路径未匹配任何路由时,自动尝试从该目录读取文件。支持 MIME 类型自动识别:`.html`, `.css`, `.js`, `.json`, `.png`, `.jpg/.jpeg`。 + +#### 5.1.6 OPTIONS 预检请求 + +当收到 `OPTIONS` 请求时: +1. 优先查找自定义 `OPTIONS` 路由处理器 +2. 若未注册自定义处理器且请求包含 `Origin` 头,自动生成 CORS 响应 + - 自动检测该路径下已注册的方法(GET/POST/PUT/DELETE/OPTIONS) + - 返回 `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`, `Access-Control-Max-Age` + +#### 5.1.7 SSL 配置 + +```cpp +void setPrivateKeyPassword(const std::string& password); +``` + +- SSL 上下文使用 TLS 协议,禁用 SSLv2/SSLv3 +- 仅允许 ECDHE/DHE 密钥交换的强密码套件:`ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM:DHE+CHACHA20:!aNULL:!MD5:!DSS` +- 支持 PEM 加密私钥密码 + +#### 5.1.8 生命周期 + +```cpp +void start(); // 启动服务器(非阻塞,线程池跑 io_context) +void stop(); // 停止服务器(关闭所有会话,join 线程) +void run(); // 启动并在当前线程运行 io_context(阻塞) +bool isRunning() const; +``` + +#### 5.1.9 调试 + +```cpp +void showStatus() const; // 打印服务器状态和已注册路由 +``` + +内嵌类 `HttpServer::debug` 提供 `print_all_header_fields()` 静态方法,打印请求的全部头字段。 + +#### 5.1.10 内部架构 + +``` +HttpServer +├── Listener (内部类) +│ └── acceptor_ (tcp::acceptor) +│ └── async_accept → Session 实例 +└── Session (内部类,enable_shared_from_this) + ├── HTTP: socket_ (tcp::socket) + ├── HTTPS: ssl_stream_ (asio::ssl::stream) + ├── strand_ 保证线程安全 + └── 读取 → handleRequest() → 写入 → [keep-alive 则继续读取] +``` + +--- + +### 5.2 HttpClient — HTTP 客户端 + +**头文件**: `` (header-only,无独立 .cpp) +**依赖**: libcurl + +#### 5.2.1 枚举 `HttpMethod` + +```cpp +enum class HttpMethod { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS }; +``` + +#### 5.2.2 公共方法 + +```cpp +HttpClient(); // 初始化 CURL 句柄,失败抛 runtime_error +~HttpClient(); // 清理 CURL 和 header 链表 + +void set_header(const std::string& header); +void clear_headers(); + +const std::map& get_response_headers() const; +long get_response_code() const; +``` + +流式请求(通过回调函数接收响应体,保留原始字节): + +```cpp +void request_stream_sync(HttpMethod method, const std::string& url, + const std::string& data, + std::function callback); + +// 便捷方法 +void get_stream_sync(const std::string& url, std::function callback); +void post_stream_sync(const std::string& url, const std::string& data, + std::function callback); +void put_stream_sync(const std::string& url, const std::string& data, + std::function callback); +void delete_stream_sync(const std::string& url, std::function callback); +``` + +**特点**: +- 同步阻塞调用 +- 响应体通过 `CURLOPT_WRITEFUNCTION` 直接追加到 `std::string`,**保留所有换行符和特殊字符** +- 超时设置:连接超时 10 秒,总超时 30 秒 +- 每次请求调用 `curl_easy_reset` 重置状态 + +--- + +### 5.3 WebSocketServer — WebSocket 服务器 + +**头文件**: `` +**实现**: `src/WebSocketServer.cpp` + +#### 5.3.1 回调类型 + +```cpp +using WebSocketMessageHandler = std::function sendCallback)>; + +using WebSocketConnectHandler = std::function; + +using WebSocketDisconnectHandler = std::function; +``` + +#### 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`),保证消息顺序 +- 客户端 ID 由全局原子计数器自动生成,格式 `"client_N"` +- 线程安全:客户端列表受 `clientsMutex_` 保护 +- 消息处理器支持路径路由(按 `path` 键索引) + +**内部架构**: + +``` +WebSocketServer +├── Impl +│ ├── io_context +│ ├── Listener (内部类) → tcp::acceptor +│ └── threads_ +└── Session (内部类,enable_shared_from_this) + ├── websocket::stream + ├── sendQueue_ (std::deque) + └── 读取循环 → onMessage 回调 → sendCallback 写回 +``` + +--- + +### 5.4 WebSocketClient — WebSocket 客户端 + +**头文件**: ``, `` +**实现**: `src/WebSocketClient.cpp` + +#### 5.4.1 抽象基类 `WebSocketClientBase` + +定义统一接口: + +```cpp +using MessageCallback = std::function; +using ErrorCallback = std::function; +using ConnectCallback = std::function; +using CloseCallback = std::function; + +// 回调设置 +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` + +--- + +## 6. 使用示例 + +### 6.1 基本 HTTP 服务器 + +```cpp +#include +#include + +int main() { + pmc::net::HttpServer server("0.0.0.0", 8080, 4); + + server.get("/hello", [](const auto& req, const auto& params) { + http::response res{http::status::ok, req.version()}; + res.set(http::field::content_type, "text/plain"); + res.body() = "Hello, world!"; + res.prepare_payload(); + return res; + }); + + server.post("/echo", [](const auto& req, const auto& params) { + http::response res{http::status::ok, req.version()}; + res.set(http::field::content_type, "application/json"); + res.body() = req.body(); + res.prepare_payload(); + return res; + }); + + server.start(); // 非阻塞启动 + std::cout << "Server running on port " << server.getPort() << std::endl; + server.isRunning(); // 阻塞直到手动 stop() +} +``` + +### 6.2 HTTPS 服务器 + +```cpp +pmc::net::HttpServer server("0.0.0.0", 443, + "/etc/ssl/certs/server.crt", + "/etc/ssl/private/server.key", + 4); +server.setPrivateKeyPassword("mysecret"); +server.start(); +``` + +### 6.3 中间件 + 静态文件 + +```cpp +pmc::net::HttpServer server("0.0.0.0", 8080); + +// 日志中间件 +server.use([](const auto& req, auto&, const auto&) { + std::cout << "[" << req.method_string() << "] " << req.target() << std::endl; + return true; // 继续处理 +}); + +// 认证中间件 +server.use([](const auto& req, auto& res, const auto&) { + auto auth = req.find(http::field::authorization); + if (auth == req.end()) { + res.result(http::status::unauthorized); + res.body() = "Unauthorized"; + res.prepare_payload(); + return false; // 拦截 + } + return true; +}); + +server.setStaticDirectory("/var/www/html"); +server.start(); +``` + +### 6.4 HTTP 客户端 + +```cpp +pmc::net::HttpClient client; + +client.set_header("Authorization: Bearer token123"); +client.set_header("Content-Type: application/json"); + +client.post_stream_sync("https://api.example.com/data", + R"({"key":"value"})", + [](const std::string& body) { + std::cout << "Response: " << body << std::endl; + }); + +long status = client.get_response_code(); +auto& headers = client.get_response_headers(); +``` + +### 6.5 WebSocket 服务器 + +```cpp +pmc::net::WebSocketServer ws_server(9000, 2); + +ws_server.onConnect([](const std::string& clientId, const std::string& addr) { + std::cout << "Client connected: " << clientId << " from " << addr << std::endl; +}); + +ws_server.onMessage("/chat", [&ws_server](const std::string& clientId, + const std::string& message, + auto sendCallback) { + std::cout << "[" << clientId << "]: " << message << std::endl; + ws_server.broadcast(clientId + ": " + message); +}); + +ws_server.onDisconnect([](const std::string& clientId) { + std::cout << "Client disconnected: " << clientId << std::endl; +}); + +ws_server.start(); +``` + +### 6.6 WebSocket 客户端 + +```cpp +pmc::net::WebSocketClient ws_client("localhost", "9000", "/chat"); + +ws_client.setConnectCallback([](bool success) { + std::cout << (success ? "Connected!" : "Failed!") << std::endl; +}); + +ws_client.setMessageCallback([](const std::string& msg) { + 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)); +} +``` + +--- + +## 7. 请求处理流程 + +``` +客户端 → TCP 连接 + ├─ HTTP: Session::doRead() + └─ HTTPS: Session::doSSLHandshake() → Session::doRead() + ↓ + HttpServer::handleRequest(req) + ├─ 解析 target_path + query params + ├─ 执行中间件链 (MiddlewareHandler[]) + │ └─ 任意中间件返回 false → 短路返回 + ├─ OPTIONS 请求? + │ ├─ 有自定义处理器 → 执行 + │ └─ 有 Origin → 自动生成 CORS 响应 + ├─ 路由匹配 (精确匹配 → 通配符匹配) + │ └─ 命中 → 执行 HttpRequestHandler + ├─ 路由未命中 + static_directory_ 非空 + │ └─ 尝试读取静态文件 → 自动设 Content-Type + └─ 全部未命中 → 404 +``` + +--- + +## 8. 设计要点 + +### 8.1 线程模型 + +- **HttpServer**: 每个工作线程独立运行 `io_context::run()`,所有异步操作通过 `strand` 序列化,同一 Session 的读写不会并发冲突 +- **WebSocketServer**: 同 HttpServer,工作线程池跑 `io_context` +- **WebSocketClient**: 单 IO 线程,所有异步回调在该线程中执行 +- **HttpClient**: 同步阻塞,无额外线程 + +### 8.2 PIMPL 模式 + +`WebSocketClient` 和 `WebSocketServer` 使用 PIMPL 隐藏 Boost 依赖,保持头文件轻量。 + +### 8.3 会话管理 + +- `HttpServer` 通过 `weak_ptr` 跟踪所有活跃会话,`stop()` 时遍历清理 +- `WebSocketServer` 通过 `unordered_map>` + mutex 管理 + +### 8.4 发送队列 + +`WebSocketServer::Session` 和 `WebSocketClient::Impl` 均采用 `std::deque` 消息队列,确保消息按序发送且不会因并发写入冲突。 + +--- + +## 9. 安全特性 + +| 特性 | 实现位置 | +|---------------------------|------------------------------| +| SSL/TLS 加密 | HttpServer SSL 构造函数 | +| 禁止 SSLv2/SSLv3 | `initSSLContext()` | +| 仅强密码套件 (ECDHE/DHE) | `initSSLContext()` 密码列表 | +| 禁用 NULL/MD5/DSS | `initSSLContext()` 密码列表 | +| 加密私钥密码支持 | `setPrivateKeyPassword()` | +| CORS 自动处理 | `handleRequest()` OPTIONS 分支 | + +--- + +## 10. 与其他模块的关系 + +``` +micro_servers/ +├── gateway/ ──依赖──→ libhttp (HttpServer 接收外部请求) +├── core/ ──依赖──→ libhttp (HTTP 服务基础能力) +└── libhttp/ ──依赖──→ Boost, OpenSSL, libcurl +``` + +- **gateway** 使用 `pmc::net::HttpServer` 作为 API 网关入口,处理 JWT 认证、令牌刷新、请求转发 +- **core** 模块使用 libhttp 提供的 HTTP 服务能力 + +--- + +## 11. 已知限制 + +1. **Makefile 默认不编译 WebSocket**:第 17 行仅链接 `HttpServer.o`,需手动启用 +2. **WebSocketClient 无 SSL 实现**:当前仅有非加密版本;SSL 接口已声明但为空实现 +3. **无 HTTP/2 支持**:仅 HTTP/1.1 +4. **静态文件 MIME 有限**:仅支持 6 种常见类型 +5. **无请求体大小限制**:依赖 Boost.Beast 默认配置 +6. **HttpClient 仅同步**:无异步 HTTP 客户端实现 +``` + diff --git a/libtry/README.robot.md b/libtry/README.robot.md new file mode 100644 index 0000000..5722e88 --- /dev/null +++ b/libtry/README.robot.md @@ -0,0 +1,856 @@ +# libtry 模块技术手册 + +> 文档版本:2026-07-30 +> 代码版本:try.c(~741 行) +> 语言:C(嵌入式解释器) +> 定位:教学级脚本解释器,用于操作界面 shell + +--- + +## 一、模块概述 + +### 1.1 定位 + +**libtry** 是一个基于 C 语言手写的**教学级解释器**,魔改自 GitHub 上的 tryC 项目。用于解析 `.try` 脚本文件,设计目标是作为**操作界面的 shell**。 + +### 1.2 特点 + +| 特性 | 说明 | +|------|------| +| 手写解释器 | 仅 741 行实现完整的词法/语法/执行引擎 | +| 零依赖 | 仅依赖标准 C 库 + `console.h` 输出接口 | +| 宽字符支持 | 使用 `wchar_t`,支持中文/多语言脚本 | +| 递归下降 | 递归下降语法分析器,边解析边执行 | +| 嵌入式设计 | 提供 `init_tryc`/`enqueue_tryc`/`eval_tryc`/`free_tryc` 四个核心 API | + +### 1.3 与微服务体系的关系 + +| 属性 | 说明 | +|------|------| +| 模块关系 | **独立底层库**,不直接与任何微服务交互 | +| 可能用途 | 作为前端 UI 的脚本解释器,或嵌入式脚本引擎 | +| 启动方式 | 被其他模块(可能是 gateway 或 static)动态加载调用 | + +--- + +## 二、技术架构 + +### 2.1 架构图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 上层应用 │ +│ (gateway / static / 其他模块) │ +└─────────────────────────┬───────────────────────────────────┘ + │ 调用 API +┌─────────────────────────▼───────────────────────────────────┐ +│ libtry 解释器 │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 词法分析器 (next) │ │ +│ │ 逐字符扫描 → 识别 token → 更新符号表 │ │ +│ └──────────────────────────┬──────────────────────────────┘ │ +│ │ token 流 │ +│ ┌──────────────────────────▼──────────────────────────────┐ │ +│ │ 递归下降语法分析器 │ │ +│ │ statement() / expression() / factor() / term() │ │ +│ │ 边解析边执行(解释器模式) │ │ +│ └──────────────────────────┬──────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼──────────────────────────────┐ │ +│ │ 符号表 │ │ +│ │ symtab[128] — 符号栈(变量/函数/数组) │ │ +│ │ levelNum — 嵌套层级,支持作用域 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ +┌─────────────────────────▼───────────────────────────────────┐ +│ 执行结果输出 │ +│ printtovga() / 控制台输出 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.2 执行流程 + +``` +init_tryc() 初始化 + │ + ├── memset(symtab, 0, ...) 清零符号表 + ├── 预注册关键字 (array, func, else, if, return, while, print, puts, read) + └── malloc(128) 分配源代码缓冲区 + │ +enqueue_tryc(wc) 追加字符(交互式输入) + │ + └── src[top_src] = wc; src[++top_src] = L'\0'; + │ +eval_tryc() 执行 + │ + ├── next() 获取第一个 token + ├── while (token != L'\0') { statement(); } 逐条执行 + └── 重置缓冲区 + │ +free_tryc() 释放资源 + │ + └── free(p_src); +``` + +### 2.3 词法分析流程 + +``` +next() 函数: + │ + ├── 跳过空白符 (L' ', L'\t') + ├── 跳过注释 (# 开头到换行) + ├── 识别标识符 (字母/数字/下划线/中文) + │ ├── 在符号表中搜索 + │ │ ├── 找到:根据 type 返回对应 token (Sym, FuncSym, ArraySym, Str) + │ │ └── 未找到:创建新符号,类型为 Void + ├── 识别数字 (整数 + 小数) + ├── 识别字符字面量 ('a') + ├── 识别字符串字面量 ("hello") + ├── 识别运算符 (=, ==, !=, <, >, <=, >=, ||, &&) + └── 识别标点符号 (;, ,, +, -, *, /, (, ), {, }, [, ]) +``` + +--- + +## 三、代码结构 + +### 3.1 文件结构 + +``` +libtry/ +├── try.c # 核心解释器实现(~741 行) +├── t.try # 示例脚本(斐波那契数列) +├── test/ # 测试目录 +│ ├── t_assign_basic.try # 赋值测试 +│ ├── t_function_basic.try # 函数测试 +│ └── t_output_basic.try # 输出测试 +├── .gitignore +└── README.robot.md # 本文档 +``` + +### 3.2 try.c 函数索引 + +| 函数 | 行号 | 功能 | +|------|------|------| +| `init_tryc()` | L650 | 初始化解释器,预注册关键字 | +| `free_tryc()` | L672 | 释放源代码缓冲区 | +| `enqueue_tryc(wc)` | L677 | 追加一个字符到缓冲区 | +| `eval_tryc()` | L687 | 解析并执行当前缓冲区脚本 | +| `next()` | L85 | 词法分析,获取下一个 token | +| `match(tk)` | L289 | 匹配并消费期望 token | +| `statement()` | L466 | 解析并执行语句 | +| `expression()` | L362 | 解析加减法表达式 | +| `term()` | L311 | 解析乘除法表达式 | +| `factor()` | L326 | 解析因子(变量/常量/函数调用/数组) | +| `function()` | L616 | 执行函数调用 | +| `boolexp()` | L378 | 解析布尔表达式 | +| `boolAND()` | L423 | 解析 AND 表达式(短路) | +| `boolOR()` | L438 | 解析 OR 表达式(短路) | +| `clear(level)` | L269 | 清理指定层级的符号 | +| `clearThis()` | L280 | 清理单个符号的动态内存 | +| `skipStatments()` | L453 | 跳过 if/else 未执行分支 | +| `skipBoolExpr()` | L413 | 跳过短路布尔表达式 | +| `__exit()` | L702 | 错误处理(死循环挂起) | +| `main()` | L710 | 文件执行模式(仅 `_DEBUG` 编译) | + +### 3.3 核心数据结构 + +#### 符号表(symStruct) + +```c +typedef struct symStruct { + int type; // 类型:Num, Char, Str, Array, Func + wchar_t name[16]; // 符号名(MAXNAMESIZE) + double value; // 值或数组/字符串长度 + union { + wchar_t *funcp; // 函数指针或字符串内容 + struct symStruct* list; // 数组元素指针 + } pointer; + int levelNum; // 嵌套层级(作用域) +} symbol; +``` + +#### Token 类型枚举 + +```c +enum { + Num = 128, // 数字 + Char, // 字符字面量 + Str, // 字符串 + Array, // array 关键字 + Func, // func 关键字 + Else, // else 关键字 + If, // if 关键字 + Return, // return 关键字 + While, // while 关键字 + Print, // print 关键字 + Puts, // puts 关键字 + Read, // read 关键字 + Assign, // = 赋值 + OR, // || + AND, // && + Equal, // == + Sym, // 变量符号 + FuncSym, // 函数符号 + ArraySym, // 数组符号 + Void, // 未初始化 + Nequal, // != + LessEqual, // <= + GreatEqual // >= +}; +``` + +#### 全局变量 + +| 变量 | 类型 | 说明 | +|------|------|------| +| `symtab[128]` | `symbol[]` | 符号表栈(SYMTABSIZE) | +| `symPointer` | `int` | 符号表栈指针 | +| `currentlevel` | `int` | 当前嵌套层级 | +| `src` | `wchar_t*` | 当前扫描位置 | +| `old_src` | `wchar_t*` | 行首位置(调试用) | +| `p_src` | `wchar_t*` | 源代码缓冲区起始 | +| `top_src` | `int` | 缓冲区写入位置 | +| `token` | `int` | 当前 token 类型 | +| `token_val` | `union tokenValue` | 当前 token 值 | +| `return_val` | `double` | 函数返回值 | +| `compileState` | `int` | 编译状态(debug/run) | + +--- + +## 四、API 接口 + +### 4.1 初始化与销毁 + +#### init_tryc() + +```c +int init_tryc(void); +``` + +**功能**:初始化解释器 +- 清零符号表 +- 预注册 8 个关键字(array, func, else, if, return, while, print, puts, read) +- 分配 128 字节宽字符缓冲区 + +**返回值**: +- `0`:成功 +- `-1`:内存分配失败 + +#### free_tryc() + +```c +void free_tryc(void); +``` + +**功能**:释放源代码缓冲区 + +### 4.2 输入与执行 + +#### enqueue_tryc(wc) + +```c +int enqueue_tryc(wchar_t wc); +``` + +**功能**:追加一个字符到源代码缓冲区(用于交互式输入) + +**参数**: +- `wc`:要追加的宽字符 + +**返回值**: +- `0`:成功 +- `-1`:缓冲区已满 + +#### eval_tryc() + +```c +void eval_tryc(void); +``` + +**功能**:解析并执行当前缓冲区中的脚本 + +**说明**:执行完成后会重置缓冲区。 + +### 4.3 典型使用示例 + +#### 交互式模式 + +```c +#include "try.c" + +int main() { + init_tryc(); + + // 交互式输入 + wchar_t input; + while (read_input(&input)) { + enqueue_tryc(input); + if (input == L'\n') { + eval_tryc(); // 遇到换行执行 + } + } + + free_tryc(); + return 0; +} +``` + +#### 文件执行模式 + +```c +#include "try.c" + +int main(int argc, char **argv) { + init_tryc(); + + // 读取文件 + FILE *file = fopen(argv[1], "r"); + wchar_t wc; + int i = 0; + while ((wc = fgetwc(file)) != WEOF) { + src[i++] = wc; + } + src[i] = L'\0'; + fclose(file); + + // 执行 + eval_tryc(); + + clear(0); // 清理全局符号 + free_tryc(); + return 0; +} +``` + +--- + +## 五、语言特性 + +### 5.1 数据类型 + +| 类型 | 说明 | 示例 | +|------|------|------| +| `Num` | 数字(整数或浮点) | `a = 5;`, `b = 3.14;` | +| `Char` | 单个字符 | `c = 'A';` | +| `Str` | 字符串(宽字符) | `s = "hello";` | +| `Array` | 数组(动态分配) | `array arr(15);` | +| `Func` | 函数 | `func name{...};` | + +### 5.2 变量与作用域 + +#### 变量声明 + +变量通过首次赋值自动声明: + +``` +x = 5; // 声明并初始化数字变量 +name = "hello"; // 声明并初始化字符串变量 +flag = 'A'; // 声明并初始化字符变量 +``` + +#### 作用域 + +- 全局变量:在顶层声明 +- 局部变量:在函数内声明,函数结束后自动销毁 +- 符号表使用栈结构,`levelNum` 标识嵌套层级 + +### 5.3 运算符 + +#### 算术运算符 + +| 运算符 | 说明 | 示例 | +|--------|------|------| +| `+` | 加法 | `a + b` | +| `-` | 减法 | `a - b` | +| `*` | 乘法 | `a * b` | +| `/` | 除法 | `a / b` | + +#### 比较运算符 + +| 运算符 | 说明 | 示例 | +|--------|------|------| +| `>` | 大于 | `a > b` | +| `<` | 小于 | `a < b` | +| `>=` | 大于等于 | `a >= b` | +| `<=` | 小于等于 | `a <= b` | +| `==` | 等于 | `a == b` | +| `!=` | 不等于 | `a != b` | + +#### 逻辑运算符 + +| 运算符 | 说明 | 示例 | +|--------|------|------| +| `\|\|` | 逻辑或(短路) | `a \|\| b` | +| `&&` | 逻辑与(短路) | `a && b` | +| `!` | 逻辑非 | `!a` | + +#### 赋值运算符 + +| 运算符 | 说明 | 示例 | +|--------|------|------| +| `=` | 赋值 | `a = 5;` | + +### 5.4 控制流 + +#### if/else 语句 + +``` +if (condition) { + // true 分支 +} +else { + // false 分支 +} +``` + +#### while 循环 + +``` +while (condition) { + // 循环体 +} +``` + +#### return 语句 + +``` +func name{ + return(value); +}; +``` + +### 5.5 函数 + +#### 函数定义 + +``` +func function_name{ + // 函数体 + return(return_value); +}; +``` + +**特性**: +- 支持递归调用 +- 支持参数传递(通过符号表) +- `return` 语句会终止函数执行并返回值 + +#### 函数调用 + +``` +result = function_name(arg1, arg2); +``` + +### 5.6 数组 + +#### 数组声明 + +``` +array arr(size); +``` + +- `size` 必须是正整数 +- 数组元素初始化为 0 +- 数组元素类型为 `Num`(double) + +#### 数组访问与赋值 + +``` +arr[0] = 10; +value = arr[i]; +``` + +### 5.7 输入输出 + +#### print() + +``` +print(expression); +``` + +**功能**:输出数字 + +**示例**: +``` +print(42); // 输出: 42.000000 +print(3.14); // 输出: 3.140000 +print(arr[0]); // 输出数组元素 +``` + +#### puts() + +``` +puts("string"); +``` + +**功能**:输出字符串 + +**示例**: +``` +puts("Hello, World!"); +``` + +#### read() + +``` +read(variable); +``` + +**功能**:读取输入(当前实现返回 -1) + +### 5.8 注释 + +``` +# This is a comment +``` + +注释从 `#` 开始到行尾结束。 + +--- + +## 六、示例脚本 + +### 6.1 斐波那契数列 + +``` +# Fibonacci sequence +func fun{ + if(x <= 2){ + return(1); + } + y = 0; + x = x - 1; + y = fun(x); + x = x - 1; + return(y + fun(x)); +}; + +# 计算 1-15 的斐波那契数列并存入数组 +array arr(15); +x = 0; +while(x<15){ + arr[x] = fun(x) + 1000; + x = x + 1; +} + +puts("Fibonacci sequence:"); +i = 0; +while(i < 15){ + print(arr[i]); + i=i+1; +} +``` + +### 6.2 基础赋值 + +``` +a = 5; +b = "ok"; + +print(5); +puts(b); +``` + +### 6.3 基础函数 + +``` +func f_test{ + return(0); +}; + +r = f_test(); +print(r); +``` + +### 6.4 基础输出 + +``` +print(1); +puts("1"); +``` + +--- + +## 七、已知限制 + +### 🔴 严重问题 + +| ID | 位置 | 描述 | 影响 | +|----|------|------|------| +| L1 | [L20](file:///Users/qingsong/micro_servers/libtry/try.c#L20) | **缓冲区大小固定**:`POOLSIZE = 128` 字节,无法修改 | 长脚本将溢出 | +| L2 | [L702](file:///Users/qingsong/micro_servers/libtry/try.c#L702) | **`__exit()` 死循环**:`while(1);` 错误时直接挂死程序 | 无法恢复 | +| L3 | [L186](file:///Users/qingsong/micro_servers/libtry/try.c#L186) | **字符串内存泄漏**:`malloc` 分配的字符串在重新赋值时未释放 | 长期运行泄漏 | +| L4 | [L528](file:///Users/qingsong/micro_servers/libtry/try.c#L528) | **数组越界**:无越界检查 | 未定义行为 | + +### 🟡 中等问题 + +| ID | 位置 | 描述 | +|----|------|------| +| L5 | [L598](file:///Users/qingsong/micro_servers/libtry/try.c#L598) | **`puts` 实现存疑**:原 `wprintf` 被注释,改用 `printtovga` | +| L6 | [L518](file:///Users/qingsong/micro_servers/libtry/try.c#L518) | **`clearThis()` 被注释**:作者注释说"不知为何需要注释了才能跑" | +| L7 | [L706](file:///Users/qingsong/micro_servers/libtry/try.c#L706) | **文件模式仅限 `_DEBUG`**:正常构建时 `main()` 不编译 | +| L8 | [L103](file:///Users/qingsong/micro_servers/libtry/try.c#L103) | **栈缓冲区溢出风险**:`nameBuffer[16]` 但标识符长度限制由 `MAXNAMESIZE=16` 控制 | +| L9 | [L21](file:///Users/qingsong/micro_servers/libtry/try.c#L21) | **符号表大小固定**:`SYMTABSIZE = 128` 个符号 | + +### 🟢 轻微问题 + +| ID | 描述 | +|----|------| +| L10 | 错误处理过于简单,`match()` 失败直接 `__exit(-1)` | +| L11 | 大量被注释的调试代码(`//wprintf(...)`) | +| L12 | 全局变量过多(`symtab`, `token`, `token_val`, `src`, `old_src`, `currentlevel` 等) | +| L13 | 无并发支持,非线程安全 | +| L14 | 无单元测试 | + +### 修复建议 + +#### 优先级 1(安全相关) + +1. **修复数组越界**:在 [L528](file:///Users/qingsong/micro_servers/libtry/try.c#L528) 添加检查 + ```c + if (index < 0 || index >= s->value) { + printtovga(L"Array index out of bounds\n"); + __exit(-1); + } + ``` + +2. **修复 `__exit()`**:改为返回错误码 + ```c + static int error_code = 0; + static void __exit(int code) { + error_code = code; + // 不挂起,让调用者处理错误 + } + ``` + +3. **修复字符串内存泄漏**:在赋值前释放旧字符串 + ```c + else if (token == Str) { + if (s->pointer.funcp != NULL) { + free(s->pointer.funcp); // 释放旧字符串 + } + s->pointer.funcp = (wchar_t*)token_val.ptr; + s->type = Str; + match(Str); + } + ``` + +#### 优先级 2(功能增强) + +4. **动态缓冲区**:将 `POOLSIZE` 改为可配置或动态扩容 +5. **错误回调**:添加错误回调函数,让调用者处理错误 +6. **符号表扩容**:允许 `SYMTABSIZE` 动态增长 + +--- + +## 八、开发指南 + +### 8.1 添加新关键字 + +在 [init_tryc()](file:///Users/qingsong/micro_servers/libtry/try.c#L650) 中添加: + +```c +// 1. 在 enum 中添加新 token 类型 +enum { + Num = 128, Char, Str, Array, Func, + Else, If, Return, While, Print, Puts, Read, + Assign, OR, AND, Equal, Sym, FuncSym, ArraySym, Void, + Nequal, LessEqual, GreatEqual, + NewKeyword // 新增 +}; + +// 2. 在 init_tryc() 中注册 +src = L"array func else if return while print puts read new_keyword"; +for (i = Array; i <= NewKeyword; ++i) { + next(); + symtab[symPointer - 1].type = i; +} + +// 3. 在 statement() 中添加处理逻辑 +else if (token == NewKeyword) { + match(NewKeyword); + // 处理逻辑 +} +``` + +### 8.2 添加新运算符 + +在 [next()](file:///Users/qingsong/micro_servers/libtry/try.c#L85) 中添加识别逻辑: + +```c +// 例如:+= 运算符 +else if (token == L'+') { + if (*src == L'=') { + src++; + token = AddAssign; // 新增 token 类型 + } +} +``` + +在 [statement()](file:///Users/qingsong/micro_servers/libtry/try.c#L466) 中添加执行逻辑。 + +### 8.3 自定义输出 + +修改 `printtovga()` 的实现,或添加可配置的输出回调: + +```c +// 函数指针类型 +typedef void (*output_func)(const wchar_t*); + +// 全局输出函数 +static output_func current_output = default_output; + +// 设置输出函数 +void set_output_func(output_func func) { + current_output = func; +} +``` + +### 8.4 构建说明 + +#### 编译器要求 + +- C 编译器(GCC/Clang) +- 支持 C99 标准 +- 支持宽字符(`wchar.h`) + +#### 编译选项 + +```bash +# 基本编译 +gcc -c try.c -o try.o -std=c99 + +# 调试模式(启用 main 函数) +gcc -c try.c -o try.o -std=c99 -D_DEBUG + +# 优化编译 +gcc -c try.c -o try.o -std=c99 -O2 +``` + +#### 链接使用 + +```bash +# 创建测试程序 +cat > test_main.c << 'EOF' +#include +#include "try.h" + +int main() { + init_tryc(); + + // 注入脚本 + wchar_t script[] = L"print(42);"; + for (int i = 0; script[i] != L'\0'; i++) { + enqueue_tryc(script[i]); + } + eval_tryc(); + + free_tryc(); + return 0; +} +EOF + +# 编译链接 +gcc test_main.c try.o -o test_try -std=c99 +./test_try +``` + +--- + +## 九、测试 + +### 9.1 现有测试 + +| 测试文件 | 内容 | +|----------|------| +| `test/t_assign_basic.try` | 变量赋值与输出 | +| `test/t_function_basic.try` | 函数定义与调用 | +| `test/t_output_basic.try` | print/puts 输出 | +| `t.try` | 斐波那契数列(复杂示例) | + +### 9.2 运行测试 + +```bash +# 编译(调试模式) +gcc try.c -o try_interp -std=c99 -D_DEBUG + +# 执行测试脚本 +./try_interp test/t_assign_basic.try +./try_interp test/t_function_basic.try +./try_interp test/t_output_basic.try +./try_interp t.try +``` + +### 9.3 添加新测试 + +在 `test/` 目录下创建 `.try` 文件: + +``` +# test/test_new_feature.try +# 描述新功能 + +a = 10; +b = 20; +c = a + b; +print(c); +``` + +--- + +## 十、版本历史 + +| 版本 | 日期 | 主要变更 | +|------|------|---------| +| 当前 | 2025-08-27 | 增加宽字符支持,修复 Str 匹配 BUG,使用 longjmp 替代 exit() | +| 初始 | — | 魔改自 tryC 项目,手写简易解释器 | + +### 修改记录(代码注释) + +| 日期 | 作者 | 变更 | +|------|------|------| +| 2025-08-27 | qing | 增加宽字符支持 | +| 2025-08-27 | qing | 修复 Str 漏匹配的 BUG | +| 2025-08-27 | qing | 注明内存泄漏风险 | +| 2025-08-27 | qing | 使用 `longjmp()` 替代 `exit()` | +| 2026-07-07 | qing | 增加 `clearThis()` 函数,用于释放变量 | + +--- + +## 附录 + +### A. 完整关键字列表 + +``` +array, func, else, if, return, while, print, puts, read +``` + +### B. Token 类型完整列表 + +``` +Num(128), Char(129), Str(130), Array(131), Func(132), +Else(133), If(134), Return(135), While(136), Print(137), Puts(138), Read(139), +Assign(140), OR(141), AND(142), Equal(143), Sym(144), FuncSym(145), ArraySym(146), Void(147), +Nequal(148), LessEqual(149), GreatEqual(150) +``` + +### C. 限制常量 + +| 常量 | 值 | 说明 | +|------|-----|------| +| `POOLSIZE` | 128 | 源代码缓冲区大小(字节) | +| `SYMTABSIZE` | 128 | 符号表大小 | +| `MAXNAMESIZE` | 16 | 标识符最大长度 | +| `RETURNFLAG` | DBL_MAX | return 语句标记 | + +### D. 参考资源 + +- [Crafting Interpreters](https://craftinginterpreters.com/) — 解释器编写经典教程 +- [Writing an Interpreter in Go](https://interpreterbook.com/) — Go 实现的解释器教程 +- [tryC (GitHub)](https://github.com/) — 原始项目(已魔改) + +--- + +*本文档基于 try.c 源码分析生成,供开发团队参考。使用前请评估已知限制。* diff --git a/pymsg/README.robot.md b/pymsg/README.robot.md new file mode 100644 index 0000000..d94d743 --- /dev/null +++ b/pymsg/README.robot.md @@ -0,0 +1,764 @@ +# pymsg 模块技术手册 + +> 文档版本:2026-07-30 +> 代码版本:0.1.0 +> 语言:Python 3.9+(核心)、C++(gateway 集成)、Go(waf 集成) +> 定位:跨语言 TCP 消息帧协议库 + +--- + +## 一、模块概述 + +### 1.1 定位 + +**pymsg** 是一个极简跨语言 TCP 消息帧协议库,基于长度前缀(4 字节大端 uint32)+ UTF-8 载荷。为微服务体系提供轻量级 RPC 通信层。 + +### 1.2 设计哲学 + +| 原则 | 说明 | +|------|------| +| 零依赖 | 仅使用 Python stdlib,无需任何第三方包 | +| 极简协议 | 4 字节头 + 载荷,实现只需 20 行代码 | +| 跨语言 | Python/C++/Go 三端实现完全兼容 | +| 二进制安全 | 长度前缀帧,JSON 中的换行符无需转义 | +| fail-safe | 日志推送在 db 不可用时静默丢弃,不影响业务 | + +### 1.3 与微服务体系的关系 + +pymsg 是微服务的**核心通信层**: + +``` +┌──────────────┐ pymsg ┌──────────────┐ +│ dnfs:8006 │ ───────→│ db:8005 │ +│ (Python) │ 短连接 │ (Python) │ +└──────────────┘ RPC └──────────────┘ +┌──────────────┐ pymsg ┌──────────────┐ +│ gateway:8443 │ ───────→│ db:8005 │ +│ (C++20) │ 短连接 │ │ +└──────────────┘ RPC └──────────────┘ +┌──────────────┐ pymsg ┌──────────────┐ +│ waf:443 │ ───────→│ db:8005 │ +│ (Go) │ 短连接 │ │ +└──────────────┘ RPC └──────────────┘ +``` + +--- + +## 二、技术架构 + +### 2.1 协议格式 + +``` +┌────────────────────────┬──────────────────────────┐ +│ 4 字节(大端序) │ N 字节(UTF-8) │ +│ uint32 载荷字节长度 │ JSON / 文本载荷 │ +└────────────────────────┴──────────────────────────┘ +``` + +### 2.2 架构图 + +``` +┌─────────────────────────────────────────────────────────────┐ +│ 微服务调用方 │ +│ (dnfs / gateway / waf / static / smtp / captcha) │ +└─────────────────────────┬───────────────────────────────────┘ + │ MsgClient.call() / send() +┌─────────────────────────▼───────────────────────────────────┐ +│ pymsg 协议层 │ +├─────────────────────────────────────────────────────────────┤ +│ ┌─────────────────────────────────────────────────────────┐ │ +│ │ 底层协议函数 │ │ +│ │ send(sock, data) → [4字节长度头][UTF-8载荷] │ │ +│ │ recv(sock) → 读4字节→解析长度→读对应字节→返回 │ │ +│ └──────────────────────────┬──────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼──────────────────────────────┐ │ +│ │ MsgClient 客户端 │ │ +│ │ send(msg) — 短连接,发送即忘(带 name 前缀) │ │ +│ │ call(msg) — 短连接,RPC 请求-应答 │ │ +│ │ listen(cb) — 长连接,回调模式 │ │ +│ └──────────────────────────┬──────────────────────────────┘ │ +│ │ │ +│ ┌──────────────────────────▼──────────────────────────────┐ │ +│ │ MsgServer 服务端 │ │ +│ │ start(on_message) — 启动监听 │ │ +│ │ reply(sock, msg) — 回复客户端 │ │ +│ │ broadcast(msg) — 广播到所有客户端 │ │ +│ │ stop() — 停止服务 │ │ +│ └─────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ TCP +┌─────────────────────────▼───────────────────────────────────┐ +│ db:8005 服务端 │ +│ MsgServer + 路由分发 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 2.3 消息流 + +#### RPC 请求-应答 + +``` +调用方 服务端 (db) +────── ────────── +MsgClient.call(json_str) + │ + ├── socket.connect(127.0.0.1:8005) + ├── send(sock, json_str) + │ ├── payload = json_str.encode("utf-8") + │ ├── header = struct.pack("!I", len(payload)) + │ └── sock.sendall(header + payload) + │ + ├── recv(sock) ←── on_message(sock, addr, text) + │ ├── sock.recv(4) ├── json.loads(text) + │ ├── length = struct.unpack("!I", header)[0] + │ ├── sock.recv(length) ├── handler(req) + │ └── payload.decode("utf-8") └── server.reply(sock, response) + │ ├── send(sock, response) + ├── close(sock) │ └── sock.sendall(header + payload) + └── return response ←── recv(sock) 返回 response +``` + +#### 日志推送 + +``` +各服务 (gateway/static/smtp) db:8005 +───────────────────────────── ────────── +log_client.push(entry) + │ + ├── 入内存队列 (ch, 容量 256) + │ + ├── 后台 goroutine 批量 flush + │ ├── 满 50 条 或 2 秒超时 + │ └── 逐条 MsgClient.call(json) + │ + └── (同 RPC 流程,fail-safe:失败静默丢弃) +``` + +--- + +## 三、代码结构 + +### 3.1 文件结构 + +``` +pymsg/ +├── pymsg/ +│ └── __init__.py # 核心实现(~370 行) +├── README.md # 用户文档 +├── pyproject.toml # 项目配置 +└── .gitignore +``` + +### 3.2 跨语言实现位置 + +| 语言 | 文件 | 说明 | +|------|------|------| +| Python | [pymsg/__init__.py](file:///Users/qingsong/micro_servers/pymsg/pymsg/__init__.py) | 主实现 | +| C++ | [gateway/inc/Msg.hpp](file:///Users/qingsong/micro_servers/gateway/inc/Msg.hpp) | gateway 使用 | +| Go | [waf/internal/logclient/logclient.go](file:///Users/qingsong/micro_servers/waf/internal/logclient/logclient.go) | waf 使用 | +| Python (黑盒) | [tests/blackbox/helpers.py](file:///Users/qingsong/micro_servers/tests/blackbox/helpers.py) | 纯 stdlib 测试实现 | + +### 3.3 代码索引 + +#### Python 实现(__init__.py) + +| 函数/类 | 行号 | 功能 | +|---------|------|------| +| `send(sock, data, *, timeout=None)` | L48 | 底层发送 | +| `recv(sock, *, timeout=None)` | L72 | 底层接收 | +| `_recv_exact(sock, n)` | L103 | 精确读 n 字节 | +| `_timeout(sock, seconds)` | L34 | 超时 context manager | +| `TimeoutError` | L30 | 超时异常 | +| `MsgClient` | L120 | TCP 客户端 | +| `MsgClient.__init__` | L136 | 构造器 | +| `MsgClient.connect()` | L144 | 建立长连接 | +| `MsgClient.send(msg)` | L150 | 短连接发送(带 name 前缀) | +| `MsgClient.call(msg, *, timeout=None)` | L163 | 短连接 RPC | +| `MsgClient.listen(on_message)` | L193 | 长连接监听 | +| `MsgClient.stop()` | L220 | 停止监听 | +| `MsgServer` | L234 | TCP 服务端 | +| `MsgServer.start(on_message)` | L259 | 启动服务 | +| `MsgServer.broadcast(msg, except_sock)` | L296 | 广播 | +| `MsgServer.reply(sock, msg)` | L321 | 回复 | +| `MsgServer.stop()` | L329 | 停止服务 | + +#### C++ 实现(Msg.hpp) + +| 函数/类 | 行号 | 功能 | +|---------|------|------| +| `MsgException` | L47 | 基础异常 | +| `MsgTimeoutException` | L52 | 超时异常 | +| `MsgConnectionException` | L57 | 连接异常 | +| `writeUint32BE(buf, val)` | L72 | 4 字节大端编码 | +| `readUint32BE(buf)` | L79 | 4 字节大端解码 | +| `setSockTimeout(sockfd, seconds)` | L88 | 设置 socket 超时 | +| `recvExact(sockfd, buf, n)` | L99 | 精确读 n 字节 | +| `sendAll(sockfd, data, len)` | L121 | 完整发送 | +| `openConnection(addr, port, timeout)` | L139 | 打开短连接 | +| `pmsg_send(sockfd, data)` | L174 | 协议发送 | +| `pmsg_recv(sockfd)` | L188 | 协议接收 | +| `MsgClient` | L210 | C++ 客户端 | +| `MsgClient.connect()` | L220 | 长连接 | +| `MsgClient.send(msg)` | L228 | 短连接发送 | +| `MsgClient.call(msg, timeout)` | L248 | 短连接 RPC | +| `MsgClient.listen(on_message)` | L269 | 长连接监听 | +| `MsgClient.stop()` | L311 | 停止 | + +#### Go 实现(logclient.go) + +| 函数 | 行号 | 功能 | +|------|------|------| +| `pymsgCall(payload)` | L226 | 发送并接收一条 pymsg 消息 | +| `Client.sendOne(entry, ts)` | L179 | 发送单条日志 | +| `Client.send(batch)` | L171 | 批量发送 | +| `Client.flushLoop()` | L125 | 后台 flush goroutine | +| `Push(service, level, message, extra)` | L82 | 全局推送 | + +--- + +## 四、API 参考 + +### 4.1 底层函数 + +#### send(sock, data, *, timeout=None) + +```python +def send(sock: socket.socket, data: str, *, timeout: float | None = None) -> None +``` + +**功能**:在已连接的 socket 上发送一条长度前缀帧消息 +- 将字符串编码为 UTF-8 +- 前面加上 4 字节大端长度头 +- 调用 `sock.sendall()` 发送 + +**参数**: +- `sock`:已连接的 socket 对象 +- `data`:要发送的字符串 +- `timeout`:可选超时(秒),超时抛出 `TimeoutError` + +#### recv(sock, *, timeout=None) + +```python +def recv(sock: socket.socket, *, timeout: float | None = None) -> str +``` + +**功能**:从已连接的 socket 接收一条长度前缀帧消息 +- 读取 4 字节头解出长度 +- 读取指定字节数 +- 返回解码后的 UTF-8 字符串 + +**参数**: +- `sock`:已连接的 socket 对象 +- `timeout`:可选超时(秒) + +**返回值**:解码后的字符串 + +**异常**: +- `ConnectionError`:连接在完整消息接收前断开 +- `TimeoutError`:操作超时 + +#### TimeoutError + +```python +class TimeoutError(Exception) +``` + +`send` 或 `recv` 超过指定超时时抛出的异常。 + +--- + +### 4.2 MsgClient(客户端) + +#### 构造 + +```python +client = MsgClient(addr: str, port: int, name: str) +``` + +**参数**: +- `addr`:服务端地址或 IP +- `port`:服务端端口 +- `name`:客户端标识(`send()` 模式下以 `"name: message"` 格式发送) + +#### 方法 + +| 方法 | 模式 | 说明 | +|------|------|------| +| `connect()` | 长连接 | 创建持久 TCP 连接(供 `listen()` 使用) | +| `send(msg)` | 短连接 | 发送即忘,消息带 `name: msg` 前缀 | +| `call(msg, timeout=10.0)` | 短连接 | RPC 模式,阻塞等待回复 | +| `listen(on_message)` | 长连接 | 阻塞当前线程,持续接收消息 | +| `stop()` | — | 中断 `listen()`,可从另一线程调用 | + +#### 使用示例 + +```python +from pymsg import MsgClient + +# RPC 调用(短连接) +client = MsgClient("127.0.0.1", 8005, "my-service") +resp = client.call('{"_route":"execute","action":"select","table":"Users"}', timeout=10.0) +print(resp) # {"success": true, "data": [...]} + +# 发送即忘 +client.send("hello") + +# 长连接监听 +def on_message(text): + print(f"收到: {text}") + +client.connect() +client.listen(on_message) # 阻塞,直到 stop() +``` + +--- + +### 4.3 MsgServer(服务端) + +#### 构造 + +```python +server = MsgServer(addr: str, port: int) +``` + +**参数**: +- `addr`:监听地址(`0.0.0.0` 表示所有网卡) +- `port`:监听端口 + +#### 方法 + +| 方法 | 说明 | +|------|------| +| `start(on_message)` | 启动服务器(阻塞),回调签名:`on_message(sock, addr, text)` | +| `reply(sock, msg)` | 向指定客户端回复 | +| `broadcast(msg, except_sock=None)` | 广播到所有客户端(可排除某个 socket) | +| `stop()` | 停止服务(可从另一线程调用) | + +#### 回调签名 + +```python +def on_message(sock: socket.socket, addr: tuple, text: str): + """ + Args: + sock: 发送消息的客户端套接字(用于 reply) + addr: (ip, port) 元组 + text: 解码后的字符串消息 + """ + server.reply(sock, "ack") +``` + +#### 使用示例 + +```python +from pymsg import MsgServer + +def on_message(sock, addr, text): + print(f"[{addr}] {text}") + server.reply(sock, "ack") + +server = MsgServer("0.0.0.0", 8005) +server.start(on_message) # 阻塞 +``` + +--- + +### 4.4 异常速查 + +| 异常 | 触发条件 | +|------|----------| +| `pymsg.TimeoutError` | `send()` 或 `recv()` 超过 `timeout` | +| `ConnectionError`(内置) | 完整消息接收前 socket 断开 | +| `ConnectionRefusedError`(内置) | `MsgClient.call()` 或 `send()` 无法连接目标 | +| `RuntimeError` | `MsgClient.listen()` 在 `connect()` 之前调用 | + +--- + +## 五、使用场景 + +### 5.1 场景一:数据库服务(db:8005) + +db 服务通过 pymsg 暴露通用 CRUD 接口: + +```python +import json +from pymsg import MsgServer + +class DBServer: + def __init__(self): + self._server = MsgServer("127.0.0.1", 8005) + self._routes = {"execute": self._handle_execute} + + def start(self): + self._server.start(self._dispatch) + + def _dispatch(self, sock, addr, text): + req = json.loads(text) + route = req.pop("_route", "") + handler = self._routes.get(route) + if handler is None: + self._server.reply(sock, + json.dumps({"success": False, "error": "未知路由"})) + return + try: + result = handler(req) + self._server.reply(sock, + json.dumps({"success": True, "data": result})) + except Exception as e: + self._server.reply(sock, + json.dumps({"success": False, "error": str(e)})) + + def _handle_execute(self, data): + # 通过 SQLAlchemy 执行 SQL,返回 dict 列表 + pass +``` + +### 5.2 场景二:RPC 调用(dnfs → db) + +```python +import json +from pymsg import MsgClient + +def _call_dbv2(payload): + client = MsgClient("127.0.0.1", 8005, "dnfs") + resp = client.call(json.dumps(payload), timeout=10.0) + return json.loads(resp) + +# 创建笔记 +result = _call_dbv2({ + "_route": "execute", + "action": "insert", + "table": "Notes", + "values": {"title": "会议记录", "content": "..."} +}) +``` + +### 5.3 场景三:异步日志推送(log_client) + +```python +import json +from pymsg import MsgClient + +def _send(batch): + for entry in batch: + client = MsgClient("127.0.0.1", 8005, "gateway-log") + client.call(json.dumps({ + "_route": "execute", + "action": "insert", + "table": "Logs", + "values": { + "ts": now, + "service": entry["service"], + "level": entry["level"], + "message": entry["message"], + } + }), timeout=5) +``` + +### 5.4 场景四:日志推送(Go 实现) + +```go +// waf/internal/logclient/logclient.go +func (c *Client) sendOne(entry Entry, ts string) { + req := dbV2Request{ + Route: "execute", + Action: "insert", + Table: "Logs", + Values: values, + } + data, _ := json.Marshal(req) + resp, err := pymsgCall(data) // 短连接 RPC + if err != nil { + return // fail-safe + } +} + +func pymsgCall(payload []byte) ([]byte, error) { + conn, err := net.DialTimeout("tcp", "127.0.0.1:8005", 5*time.Second) + // ... 写 4 字节头 + 载荷,读回复 +} +``` + +--- + +## 六、跨语言实现对照 + +### 6.1 协议层 + +| 功能 | Python | C++ | Go | +|------|--------|-----|-----| +| 4 字节编码 | `struct.Struct("!I")` | `writeUint32BE` (手动位移) | `binary.BigEndian.PutUint32` | +| 4 字节解码 | `struct.unpack("!I", header)` | `readUint32BE` (手动位移) | `binary.BigEndian.Uint32` | +| 超时设置 | `_timeout` context manager | `setSockTimeout` (setsockopt) | `net.DialTimeout` + `SetDeadline` | +| 安全上限 | 无 | 64 MiB | 无 | + +### 6.2 客户端对照 + +| 操作 | Python | C++ | Go | +|------|--------|-----|-----| +| 构造 | `MsgClient(addr, port, name)` | `MsgClient(addr, port, name)` | — | +| 长连接 | `connect()` | `connect()` | — | +| 发送即忘 | `send(msg)` 短连接 + `name:` 前缀 | `send(msg)` 短连接 + `name:` 前缀 | — | +| RPC 调用 | `call(msg, timeout)` 短连接 | `call(msg, timeout)` 短连接 | `pymsgCall(payload)` 短连接 | +| 长监听 | `listen(on_message)` | `listen(on_message)` | — | +| 停止 | `stop()` | `stop()` | — | + +### 6.3 服务端对照 + +| 操作 | Python | C++ | Go | +|------|--------|-----|-----| +| 启动 | `start(on_message)` | — | — | +| 回复 | `reply(sock, msg)` | `pmsg_send(fd, msg)` | — | +| 广播 | `broadcast(msg, except_sock)` | — | — | +| 停止 | `stop()` | — | — | + +### 6.4 从头实现协议 + +仅需四步: + +``` +1. 将字符串编码为 UTF-8 字节 +2. 将字节长度打包为 4 字节大端无符号整数 +3. 写入 头 + 载荷 到 socket +4. 接收端先读 4 字节解出长度,再读对应字节数,解码为 UTF-8 +``` + +--- + +## 七、线程安全 + +| 场景 | 安全性 | 说明 | +|------|--------|------| +| `MsgServer.stop()` 跨线程调用 | ✅ 安全 | 设置原子标志并关闭监听 socket | +| `MsgServer.reply()` 在回调内调用 | ✅ 安全 | 同一线程内调用 | +| `MsgServer.broadcast()` 在回调内调用 | ✅ 安全 | 同一线程内调用 | +| `MsgClient.stop()` 跨线程调用 | ✅ 安全 | 可中断 `listen()` | +| `send()`/`recv()` 多线程共享 socket | ⚠️ 不安全 | 调用方需自行串行化 | +| `MsgServer` 并发调用 broadcast/reply | ⚠️ 不支持 | 仅在 `on_message` 回调内(同一线程)安全 | + +--- + +## 八、性能特性 + +### 8.1 性能特点 + +| 特性 | 说明 | +|------|------| +| 零拷贝 | 长度前缀帧避免数据复制 | +| 短连接复用 | `call()` 每次新建连接,但调用完成立即释放 | +| fail-safe | 日志推送失败静默丢弃,不阻塞业务 | +| 批量缓冲 | 日志推送:50 条或 2 秒自动 flush | + +### 8.2 连接模型 + +| 模式 | 连接数 | 延迟 | 适用场景 | +|------|--------|------|---------| +| `send()` | 每次 1 个 | 中 | 低频通知 | +| `call()` | 每次 1 个 | 中 | RPC 调用 | +| `listen()` | 持久 1 个 | 低 | 高频消息流 | + +--- + +## 九、已知限制 + +| ID | 描述 | 严重等级 | 影响范围 | +|----|------|---------|---------| +| L1 | **无消息校验**:协议无 CRC/哈希,传输损坏无法检测 | 🟡 Medium | 所有消息 | +| L2 | **无认证**:任何人可连接 8005 端口,无鉴权机制 | 🔴 High | db 服务 | +| L3 | **无加密**:明文传输,无 TLS/加密保护 | 🔴 High | 所有服务 | +| L4 | **Python 版无安全上限**:C++ 版有 64 MiB 上限,Python 版未检查 | 🟡 Medium | Python 客户端 | +| L5 | **无重试机制**:连接失败直接抛异常,无自动重试 | 🟢 Low | 所有调用 | +| L6 | **无版本号**:协议升级无兼容路径 | 🟢 Low | 长期维护 | +| L7 | **无心跳**:长连接无 keep-alive,可能被防火墙切断 | 🟡 Medium | listen 模式 | +| L8 | **全局阻塞**:`MsgServer.start()` 阻塞主线程,需另开线程 | 🟢 Low | 服务端 | + +### 建议改进 + +1. **L2/L3**:在防火墙层面限制 8005 端口仅允许本机连接(当前部署已这样做) +2. **L4**:在 Python `recv()` 中添加消息大小上限检查 +3. **L7**:在 `listen()` 循环中添加 ping/pong 机制 +4. **L5**:在 `call()` 中添加简单重试(1-2 次) + +--- + +## 十、设计考量 + +### 10.1 为什么用长度前缀帧? + +| 方案 | 优点 | 缺点 | +|------|------|------| +| **长度前缀帧** ✅ | 二进制安全、实现简单、无需流式解析 | 无 | +| 换行分隔 | 简单 | JSON 中换行需转义、缓冲区管理复杂 | +| HTTP | 成熟、有工具 | 头部开销大、需额外依赖 | +| Protobuf | 高效、类型安全 | 需额外依赖、跨语言实现复杂 | + +### 10.2 为什么用短连接? + +| 考虑 | 说明 | +|------|------| +| 简单性 | 短连接实现最简洁,无连接管理开销 | +| 可靠性 | 每次调用都是独立连接,单个请求失败不影响其他 | +| 性能 | 本地回环(`127.0.0.1`)下 TCP 连接建立开销很小 | +| 可测试性 | 易于模拟各种网络故障 | + +### 10.3 协议设计取舍 + +| 取舍 | 选择 | 原因 | +|------|------|------| +| 头大小 | 4 字节 | 支持最大 4 GiB 消息,足够使用 | +| 字节序 | 大端 | 网络标准序,跨语言一致 | +| 编码 | UTF-8 | 支持中文/多语言,与 JSON 兼容 | +| 版本号 | 无 | 保持最简,升级时可扩展 | + +--- + +## 十一、开发指南 + +### 11.1 安装 + +```bash +# 开发模式 +cd pymsg +pip install -e . + +# 作为本地依赖 +# 在其他项目的 requirements.txt 中: +# ../pymsg +``` + +### 11.2 添加新功能 + +#### 添加消息校验(CRC) + +```python +import zlib + +def send_with_crc(sock, data): + payload = data.encode("utf-8") + crc = zlib.crc32(payload) & 0xFFFFFFFF + # [4字节长度][4字节CRC][UTF-8载荷] + header = _HEADER.pack(len(payload) + 4) + crc_bytes = struct.pack("!I", crc) + sock.sendall(header + crc_bytes + payload) +``` + +#### 添加心跳机制 + +```python +import time + +def listen_with_heartbeat(self, on_message, interval=30): + last_recv = time.time() + while self._running: + # 发送心跳 + if time.time() - last_recv > interval: + send(self._sock, "PING") + # 接收消息 + text = recv(self._sock, timeout=1.0) + if text == "PONG": + continue + last_recv = time.time() + on_message(text) +``` + +### 11.3 扩展协议版本 + +```python +# 在头中添加版本号 +_VERSION = 1 # 1 字节版本号 + +def send_v2(sock, data): + payload = data.encode("utf-8") + header = struct.pack("!BI", _VERSION, len(payload)) + sock.sendall(header + payload) + +def recv_v2(sock): + header = _recv_exact(sock, 5) + version, length = struct.unpack("!BI", header) + if version != _VERSION: + raise ValueError(f"Unsupported protocol version: {version}") + payload = _recv_exact(sock, length) + return payload.decode("utf-8") +``` + +### 11.4 构建与测试 + +```bash +# Python 测试 +cd pymsg +python -c " +from pymsg import MsgClient, MsgServer, send, recv +# ... 单元测试 +" + +# 黑盒测试 +cd tests/blackbox +python test_runner.py +``` + +--- + +## 十二、版本历史 + +| 版本 | 日期 | 主要变更 | +|------|------|---------| +| 0.1.0 | 当前 | 初始版本:核心协议、MsgClient、MsgServer、跨语言实现 | + +--- + +## 附录 + +### A. 协议规范(RFC 风格) + +``` +Protocol: pymsg v0 + +Frame Format: +┌──────────┬──────────┬──────────────────┐ +│ 4 bytes │ N bytes │ │ +├──────────┼──────────┼──────────────────┤ +│ uint32 │ UTF-8 │ │ +│ big-end │ string │ │ +│ payload │ payload │ │ +│ length │ │ │ +└──────────┴──────────┴──────────────────┘ + +Encoding: UTF-8 +Byte Order: Big-Endian (network byte order) +Max Message Size: 2^32 - 1 bytes (theoretical) +Timeout: Optional, specified per operation + +Modes: + - Short Connection (send/call): connect → send → recv → close + - Long Connection (listen): connect → loop(recv → callback) +``` + +### B. 错误码映射 + +| 异常 | 来源 | 场景 | +|------|------|------| +| `TimeoutError` | pymsg | 操作超过指定超时 | +| `ConnectionError` | Python | 连接在接收前断开 | +| `ConnectionRefusedError` | Python | 目标服务不可达 | +| `RuntimeError` | Python | 在 `connect()` 前调用 `listen()` | +| `MsgTimeoutException` | C++ | C++ 版超时 | +| `MsgConnectionException` | C++ | C++ 版连接异常 | + +### C. 参考实现清单 + +| 语言 | 文件 | 类型 | +|------|------|------| +| Python | [pymsg/__init__.py](file:///Users/qingsong/micro_servers/pymsg/pymsg/__init__.py) | 主实现 | +| Python | [tests/blackbox/helpers.py](file:///Users/qingsong/micro_servers/tests/blackbox/helpers.py) | 测试客户端 | +| C++ | [gateway/inc/Msg.hpp](file:///Users/qingsong/micro_servers/gateway/inc/Msg.hpp) | gateway 客户端 | +| Go | [waf/internal/logclient/logclient.go](file:///Users/qingsong/micro_servers/waf/internal/logclient/logclient.go) | waf 客户端 | + +### D. 参考资源 + +- [Beej's Guide to Network Programming](https://beej.us/guide/bgnet/) — Socket 编程经典 +- [The SO_REUSEADDR socket option](https://man7.org/linux/man-pages/man2/socket.2.html) +- [Python socket module](https://docs.python.org/3/library/socket.html) + +--- + +*本文档基于 pymsg 源码分析生成,供开发团队参考。*