diff --git a/README.md b/README.md index cb7fe8728936a475f7292e8755fbf29f24874713..2d1103b0a104199d5cfc7ae864dfc1cd57417edf 100644 --- a/README.md +++ b/README.md @@ -161,6 +161,8 @@ cmake -B build -DCMAKE_INSTALL_PREFIX=$HOME/.tbox For details on how to use cpp-tbox to develop your own programs, see the tutorial: [cpp-tbox-tutorials](https://github.com/cpp-main/cpp-tbox-tutorials/blob/master/README.md) +For module usage documentation, see: [Module Documentation](documents/modules/README.md) + For example to use `find_package`: ``` cmake_minimum_required(VERSION 3.10) diff --git a/README_CN.md b/README_CN.md index cf812a1dfbc6a07dcbce09d891c855e964f69d60..949808a3492663c3fd5e92b7b9b78d6b6086534f 100644 --- a/README_CN.md +++ b/README_CN.md @@ -163,6 +163,8 @@ cmake -B build -DCMAKE_INSTALL_PREFIX=$HOME/.tbox 关于如何使用 cpp-tbox 开发自己的程序,详见教程: [cpp-tbox-tutorials](https://gitee.com/cpp-master/cpp-tbox-tutorials/blob/master/README.md) +各模块使用文档,详见:[模块使用文档](documents/modules/README_CN.md) + 使用`find_package`的例子: ``` diff --git a/documents/modules/README.md b/documents/modules/README.md new file mode 100644 index 0000000000000000000000000000000000000000..fc2d1c865bed74024fe45212d86e87a8768bd6e0 --- /dev/null +++ b/documents/modules/README.md @@ -0,0 +1,126 @@ +# cpp-tbox Module Documentation + +cpp-tbox is an event-driven C++ service application development library that provides a complete service program development framework. + +## Module Dependencies + +![modules-dependence](../images/modules-dependence.png) + +## Module List + +| Module | Description | Brief | Docs Link | +|--------|-------------|-------|-----------| +| **event** | Event-driven | Event loop and IO/timer/signal events | [event.md](event.md) | +| **base** | Base components | Log macros, assertions, object pool, lifetime tags, etc. | [base.md](base.md) | +| **main** | Application framework | Program startup framework, Module lifecycle management | [main.md](main.md) | +| **eventx** | Event extensions | Thread pool, timer pool, LoopThread, async operations | [eventx.md](eventx.md) | +| **network** | Network communication | TCP/UDP/UART communication and byte stream abstraction | [network.md](network.md) | +| **terminal** | Interactive terminal | Runtime command interaction, similar to Bash shell | [terminal.md](terminal.md) | +| **log** | Log channels | File/stdout/syslog and other log outputs | [log.md](log.md) | +| **http** | HTTP service | Express-style HTTP server and middleware | [http.md](http.md) | +| **coroutine** | Coroutine | Coroutine scheduler and Channel/Mutex helper components | [coroutine.md](coroutine.md) | +| **alarm** | Timer alarm | Cron/Oneshot/Weekly/Workday timers | [alarm.md](alarm.md) | +| **util** | Utilities | Buffer/Json/serialization/UUID/Base64 and 17+ tools | [util.md](util.md) | +| **mqtt** | MQTT client | MQTT protocol client with TLS and auto-reconnect | [mqtt.md](mqtt.md) | +| **flow** | Flow control | Multi-level state machine and behavior tree | [flow.md](flow.md) | +| **jsonrpc** | JSON-RPC | JSON-RPC 2.0 protocol implementation | [jsonrpc.md](jsonrpc.md) | +| **trace** | Performance tracing | Function-level performance tracing and binary recording | [trace.md](trace.md) | +| **crypto** | Encryption | MD5 message digest and AES encryption/decryption | [crypto.md](crypto.md) | +| **dbus** | D-Bus integration | D-Bus bus and event loop integration | [dbus.md](dbus.md) | +| **run** | Module runner | Dynamically load business module .so and run | [run.md](run.md) | + +## Quick Start + +### The Simplest Program + +```cpp +// app.cpp +#include +#include + +class App : public tbox::main::Module { + public: + App(tbox::main::Context &ctx) : Module("app", ctx) { } + bool onStart() override { LogInfo("started"); return true; } + void onStop() override { LogInfo("stopped"); } +}; + +namespace tbox { namespace main { +void RegisterApps(Module &apps, Context &ctx) { apps.add(new ::App(ctx)); } +std::string GetAppDescribe() { return "my first tbox app"; } +std::string GetAppBuildTime() { return __DATE__ " " __TIME__; } +void GetAppVersion(int &major, int &minor, int &rev, int &build) { major = 0; minor = 1; rev = 0; build = 0; } +}} +``` + +### Compile and Run + +```bash +# Compile +g++ -o myapp app.cpp -ltbox_main -ltbox_terminal -ltbox_network \ + -ltbox_eventx -ltbox_event -ltbox_util -ltbox_base -lpthread -ldl + +# Run +./myapp # Run in foreground, press Ctrl+C to exit +./myapp -d # Run in background +./myapp -h # Show help +./myapp -v # Show version +``` + +### Core Concepts + +1. **Event Loop (event::Loop)**: The scheduling center for all asynchronous events +2. **Module (main::Module)**: The carrier of business logic, following the initialize -> start -> stop -> cleanup lifecycle +3. **Callback-driven**: All asynchronous operations notify results through callback functions +4. **Single-thread model**: The event loop processes all event callbacks in a single thread; cross-thread operations are injected via runInLoop() + +### Recommended Learning Order + +1. [base](base.md) — Learn about logging, ScopeExit and other fundamentals +2. [event](event.md) — Understand the event loop mechanism +3. [main](main.md) — Master the program framework and Module lifecycle +4. Choose other modules based on business needs + +## Common Patterns + +### Initialize -> Start -> Stop -> Cleanup + +Almost all tbox components follow the same lifecycle pattern: + +```cpp +Component comp(loop); +comp.initialize(config); //! Initialize configuration +comp.setCallback([] { ... }); //! Set callback +comp.start(); // or comp.enable() //! Start/enable +// ... running normally ... +comp.stop(); // or comp.disable() //! Stop/disable +comp.cleanup(); //! Cleanup resources +``` + +### SetScopeExitAction Resource Management + +```cpp +auto ptr = new SomeObject; +SetScopeExitAction([ptr] { delete ptr; }); //! Automatically release on scope exit +``` + +### Cross-thread Task Injection + +```cpp +// Inject task into Loop from other threads +sp_loop->runInLoop([] { LogInfo("task in loop thread"); }); + +// Automatically choose route when thread is uncertain +sp_loop->run([] { LogInfo("auto route task"); }); +``` + +## Reference Images + +| Image | Description | +|-------|-------------| +| ![tbox-loop](../images/0001-tbox-loop.jpg) | Event loop working principle | +| ![main-framework](../images/0008-main-framework.png) | main module framework structure | +| ![modules-dependence](../images/modules-dependence.png) | Module dependency graph | +| ![state-machine](../images/0010-state-machine-graph.png) | State machine example | +| ![action-tree](../images/0010-action-tree-graph.jpg) | Behavior tree example | +| ![trace-view](../images/0011-trace-view.png) | Performance tracing visualization | diff --git a/documents/modules/README_CN.md b/documents/modules/README_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..816ebc26ad06677f001f1832ca08c0fd209eb0d3 --- /dev/null +++ b/documents/modules/README_CN.md @@ -0,0 +1,126 @@ +# cpp-tbox 模块使用文档 + +cpp-tbox 是一个基于事件驱动的 C++ 服务应用开发库,提供完整的服务程序开发框架。 + +## 模块依赖关系 + +![modules-dependence](../images/modules-dependence.png) + +## 模块列表 + +| 模块 | 中文名 | 功能简述 | 文档链接 | +|------|--------|---------|---------| +| **event** | 事件驱动 | 事件循环与 IO/定时/信号事件 | [event_CN.md](event_CN.md) | +| **base** | 基础组件 | 日志宏、断言、对象池、生命期标签等 | [base_CN.md](base_CN.md) | +| **main** | 应用框架 | 程序启动框架,Module 生命周期管理 | [main_CN.md](main_CN.md) | +| **eventx** | 事件扩展 | 线程池、定时池、LoopThread、异步操作 | [eventx_CN.md](eventx_CN.md) | +| **network** | 网络通信 | TCP/UDP/UART 通信与字节流抽象 | [network_CN.md](network_CN.md) | +| **terminal** | 交互终端 | 运行时命令交互,类似 Bash shell | [terminal_CN.md](terminal_CN.md) | +| **log** | 日志通道 | 文件/stdout/syslog 等日志输出 | [log_CN.md](log_CN.md) | +| **http** | HTTP 服务 | Express 式 HTTP 服务端与中间件 | [http_CN.md](http_CN.md) | +| **coroutine** | 协程 | 协程调度器与 Channel/Mutex 等辅助组件 | [coroutine_CN.md](coroutine_CN.md) | +| **alarm** | 定时闹钟 | Cron/Oneshot/Weekly/Workday 定时器 | [alarm_CN.md](alarm_CN.md) | +| **util** | 工具集 | Buffer/Json/序列化/UUID/Base64 等 17+ 工具 | [util_CN.md](util_CN.md) | +| **mqtt** | MQTT 客户端 | MQTT 协议客户端,支持 TLS 和自动重连 | [mqtt_CN.md](mqtt_CN.md) | +| **flow** | 流程控制 | 多层级状态机与行为树 | [flow_CN.md](flow_CN.md) | +| **jsonrpc** | JSON-RPC | JSON-RPC 2.0 协议实现 | [jsonrpc_CN.md](jsonrpc_CN.md) | +| **trace** | 性能追踪 | 函数级性能追踪与二进制记录 | [trace_CN.md](trace_CN.md) | +| **crypto** | 加密 | MD5 消息摘要与 AES 加密解密 | [crypto_CN.md](crypto_CN.md) | +| **dbus** | D-Bus 集成 | D-Bus 总线与事件循环集成 | [dbus_CN.md](dbus_CN.md) | +| **run** | 模块运行器 | 动态加载业务模块 .so 并运行 | [run_CN.md](run_CN.md) | + +## 快速入门 + +### 最简单的程序 + +```cpp +// app.cpp +#include +#include + +class App : public tbox::main::Module { + public: + App(tbox::main::Context &ctx) : Module("app", ctx) { } + bool onStart() override { LogInfo("started"); return true; } + void onStop() override { LogInfo("stopped"); } +}; + +namespace tbox { namespace main { +void RegisterApps(Module &apps, Context &ctx) { apps.add(new ::App(ctx)); } +std::string GetAppDescribe() { return "my first tbox app"; } +std::string GetAppBuildTime() { return __DATE__ " " __TIME__; } +void GetAppVersion(int &major, int &minor, int &rev, int &build) { major = 0; minor = 1; rev = 0; build = 0; } +}} +``` + +### 编译与运行 + +```bash +# 编译 +g++ -o myapp app.cpp -ltbox_main -ltbox_terminal -ltbox_network \ + -ltbox_eventx -ltbox_event -ltbox_util -ltbox_base -lpthread -ldl + +# 运行 +./myapp # 前端运行,按 Ctrl+C 退出 +./myapp -d # 后台运行 +./myapp -h # 显示帮助 +./myapp -v # 显示版本 +``` + +### 核心概念 + +1. **事件循环 (event::Loop)**:所有异步事件的调度中心 +2. **模块 (main::Module)**:业务逻辑的载体,遵循 initialize → start → stop → cleanup 生命周期 +3. **回调驱动**:所有异步操作通过回调函数通知结果 +4. **单线程模型**:事件循环在单线程中处理所有事件回调,跨线程操作通过 runInLoop() 注入 + +### 推荐学习顺序 + +1. [base_CN](base_CN.md) — 了解日志、ScopeExit 等基础 +2. [event_CN](event_CN.md) — 理解事件循环机制 +3. [main_CN](main_CN.md) — 掌握程序框架和 Module 生命周期 +4. 根据业务需要选择其他模块 + +## 通用模式 + +### 初始化 → 启动 → 停止 → 清理 + +几乎所有 tbox 组件遵循相同的生命周期模式: + +```cpp +Component comp(loop); +comp.initialize(config); //! 初始化配置 +comp.setCallback([] { ... }); //! 设置回调 +comp.start(); // 或 comp.enable() //! 启动/使能 +// ... 正常运行 ... +comp.stop(); // 或 comp.disable() //! 停止/禁用 +comp.cleanup(); //! 清理资源 +``` + +### SetScopeExitAction 资源管理 + +```cpp +auto ptr = new SomeObject; +SetScopeExitAction([ptr] { delete ptr; }); //! 作用域退出时自动释放 +``` + +### 跨线程任务注入 + +```cpp +// 其它线程向 Loop 注入任务 +sp_loop->runInLoop([] { LogInfo("task in loop thread"); }); + +// 不确定线程时自动选择 +sp_loop->run([] { LogInfo("auto route task"); }); +``` + +## 参考图片 + +| 图片 | 说明 | +|------|------| +| ![tbox-loop](../images/0001-tbox-loop.jpg) | 事件循环工作原理 | +| ![main-framework](../images/0008-main-framework.png) | main 模块框架结构 | +| ![modules-dependence](../images/modules-dependence.png) | 模块依赖关系图 | +| ![state-machine](../images/0010-state-machine-graph.png) | 状态机示例 | +| ![action-tree](../images/0010-action-tree-graph.jpg) | 行为树示例 | +| ![trace-view](../images/0011-trace-view.png) | 性能追踪可视化 | diff --git a/documents/modules/alarm.md b/documents/modules/alarm.md new file mode 100644 index 0000000000000000000000000000000000000000..9c0669c5e2a50f267c0b10b8e73578b3f15dde66 --- /dev/null +++ b/documents/modules/alarm.md @@ -0,0 +1,204 @@ +# Alarm Module (alarm) + +## What is it? + +The alarm module provides multiple alarm types: CronAlarm (Linux cron expressions), OneshotAlarm (one-shot), WeeklyAlarm (weekly recurring), and WorkdayAlarm (workdays/holidays). They are implemented based on the TimerEvent of the event module and support independent timezone settings. + +## Why do you need it? + +In service-oriented programs, scheduled tasks are one of the most common requirements. The alarm module provides multiple scheduling strategies to meet different scenarios: execute at a fixed time every day, execute on specific days of the week, flexibly schedule via cron expressions, execute only on workdays, etc. + +## Header Files + +```cpp +#include //! Alarm base class +#include //! Cron expression alarm +#include //! One-shot alarm +#include //! Weekly alarm +#include //! Workday alarm +#include //! Workday calendar +``` + +## Core Classes and Interfaces + +### Alarm — Alarm Base Class + +All alarm types share the following interfaces: + +| Method | Description | +|------|------| +| `Alarm(loop)` | Constructor, specify the event loop | +| `setCallback(cb)` | Set the timer trigger callback | +| `setTimezone(offset_minutes)` | Set timezone offset (east zones are positive, e.g. UTC+8 = 480) | +| `enable()` | Enable the timer | +| `disable()` | Disable the timer | +| `isEnabled()` | Check if the timer is enabled | +| `refresh()` | Refresh (should be called after clock synchronization) | +| `remainSeconds()` | Get remaining seconds | +| `cleanup()` | Clean up resources | + +### Alarm Types Comparison + +| Type | Initialization Parameters | Applicable Scenarios | +|------|------|------| +| **CronAlarm** | cron expression string | Flexible scheduling, e.g. "every minute", "the 1st of each month" | +| **OneshotAlarm** | seconds_of_day | Execute once at a fixed time today or tomorrow | +| **WeeklyAlarm** | seconds_of_day + week_mask | Execute on specific days of the week | +| **WorkdayAlarm** | seconds_of_day + calendar + workday | Execute only on workdays or holidays | + +### CronAlarm — Cron Expression Alarm + +Cron expression format (6 fields): + +``` +second minute hour day month weekday +* * * * * * +``` + +Examples: +- `"18 28 14 * * *"` — Every day at 14:28:18 +- `"0 30 8 * * 1-5"` — Monday through Friday at 8:30:00 +- `"0 0 12 1 * *"` — The 1st of each month at 12:00:00 + +### OneshotAlarm — One-shot Alarm + +The `seconds_of_day` parameter is the number of seconds from local 00:00 to the trigger time. + +``` +08:30 = 8 × 3600 + 30 × 60 = 30600 +``` + +If the specified time has already passed (current time is later than the scheduled time), it will execute tomorrow. + +### WeeklyAlarm — Weekly Alarm + +`week_mask` is a fixed-length 7-character string starting from Sunday. `'1'` means execute, other characters mean skip: + +``` +"0111110" → Monday through Friday +"1111111" → Every day +"1000001" → Only Sunday and Saturday +``` + +### WorkdayCalendar — Workday Calendar + +WorkdayCalendar provides date query functionality to WorkdayAlarm: + +| Method | Description | +|------|------| +| `updateSpecialDays(days)` | Update special holiday/makeup workday schedule | +| `updateWeekMask(mask)` | Modify the default weekly workday mask (default: Monday through Friday) | +| `subscribe(alarm)` | Subscribe an alarm (automatically notified when calendar changes) | +| `unsubscribe(alarm)` | Unsubscribe an alarm | +| `isWorkay(day_index)` | Query whether the specified date is a workday | + +### WorkdayAlarm — Workday Alarm + +The `workday` parameter: true = execute only on workdays, false = execute only on holidays. + +## Usage Examples + +### CronAlarm — Execute at 14:28:18 every day + +> Full example at `examples/alarm/cron_alarm/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + alarm::CronAlarm tmr(sp_loop); + tmr.initialize("18 28 14 * * *"); //! Every day at 14:28:18 + tmr.setCallback([] { LogInfo("time is up"); }); + tmr.enable(); + + sp_loop->runLoop(Loop::Mode::kForever); + + LogOutput_Disable(); + return 0; +} +``` + +### OneshotAlarm — Execute at 8:30 every morning + +> Full example at `examples/alarm/oneshot_alarm/` + +```cpp +alarm::OneshotAlarm tmr(sp_loop); +tmr.initialize(30600); //! 08:30 = 30600 seconds +tmr.setCallback([] { LogInfo("time is up"); }); +tmr.enable(); +``` + +### WeeklyAlarm — Execute at 8:30 Monday through Friday + +> Full example at `examples/alarm/weekly_alarm/` + +```cpp +alarm::WeeklyAlarm tmr(sp_loop); +tmr.initialize(30600, "0111110"); //! Monday through Friday at 08:30 +tmr.setCallback([] { LogInfo("time is up"); }); +tmr.enable(); +``` + +### Setting an Independent Timezone + +```cpp +tmr.setTimezone(480); //! UTC+8 (+8 × 60 = 480 minutes) +//! If not set, the system timezone is used by default +``` + +### WorkdayAlarm — Execute Only on Workdays + +```cpp +alarm::WorkdayCalendar calendar; +//! Mark January 1, 2024 as a holiday +calendar.updateSpecialDays({{19723, false}}); //! day_index = days from 1970-1-1 + +alarm::WorkdayAlarm tmr(sp_loop); +tmr.initialize(30600, &calendar, true); //! true = only workdays +tmr.setCallback([] { LogInfo("workday alarm"); }); +tmr.enable(); + +//! When the calendar is updated, subscribed alarms will automatically refresh +calendar.updateSpecialDays({{19724, true}}); //! Makeup workday +``` + +### refresh() — Refresh After Clock Synchronization + +```cpp +//! Refresh the timer after system clock synchronization to ensure accurate trigger times +tmr.refresh(); +``` + +## Common Scenarios + +1. **Daily execution at a fixed time**: OneshotAlarm with seconds_of_day +2. **Weekly execution on specific days**: WeeklyAlarm with week_mask +3. **Flexible cron scheduling**: CronAlarm with cron expressions +4. **Execute only on workdays**: WorkdayAlarm + WorkdayCalendar +5. **Cross-timezone scheduling**: setTimezone() to set an independent timezone + +## Important Notes + +1. **seconds_of_day calculation**: Counted from local 00:00, e.g. 08:30 = 30600 +2. **OneshotAlarm "tomorrow" behavior**: If the current time has already passed the scheduled time, it will execute tomorrow +3. **WorkdayCalendar lifetime**: The calendar object passed to WorkdayAlarm must live longer than the WorkdayAlarm +4. **week_mask format**: Fixed 7 characters, starting from Sunday, '1' marks execution days +5. **When to call refresh()**: If the system clock is inaccurate when enable() is called, the scheduled task will also be inaccurate; refresh should be called after clock synchronization + +## Related Modules + +- **event**: Alarm is implemented based on TimerEvent for timing +- **base**: Provides logging, ScopeExit, and other infrastructure diff --git a/documents/modules/alarm_CN.md b/documents/modules/alarm_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..8955d44e7b19df6f1b32fa10723c9584480b3ae2 --- /dev/null +++ b/documents/modules/alarm_CN.md @@ -0,0 +1,204 @@ +# 定时闹钟模块 (alarm) + +## 是什么? + +alarm 模块提供了多种定时闹钟类型:CronAlarm(Linux cron 表达式)、OneshotAlarm(一次性)、WeeklyAlarm(每周循环)、WorkdayAlarm(工作日/节假日)。它们基于 event 模块的 TimerEvent 实现,支持独立时区设置。 + +## 为什么需要它? + +在服务型程序中,定时任务是最常见的需求之一。alarm 模块提供了多种定时策略,满足不同场景:每天固定时间执行、每周特定日期执行、按 cron 表达式灵活调度、仅在工作日执行等。 + +## 头文件 + +```cpp +#include //! 闹钟基类 +#include //! Cron 表达式闹钟 +#include //! 一次性闹钟 +#include //! 每周闹钟 +#include //! 工作日闹钟 +#include //! 工作日日历 +``` + +## 核心类与接口 + +### Alarm — 闹钟基类 + +所有闹钟类型共享以下接口: + +| 方法 | 说明 | +|------|------| +| `Alarm(loop)` | 构造,指定事件循环 | +| `setCallback(cb)` | 设置定时触发回调 | +| `setTimezone(offset_minutes)` | 设置时区偏移(东区为正,如东8区=480) | +| `enable()` | 使能定时器 | +| `disable()` | 关闭定时器 | +| `isEnabled()` | 定时器是否已使能 | +| `refresh()` | 刷新(时钟同步后应调用) | +| `remainSeconds()` | 获取剩余秒数 | +| `cleanup()` | 清理资源 | + +### 闹钟类型对比 + +| 类型 | 初始化参数 | 适用场景 | +|------|------|------| +| **CronAlarm** | cron 表达式字符串 | 灵活调度,如"每分钟"、"每月1号" | +| **OneshotAlarm** | seconds_of_day | 每天或明天固定时间执行一次 | +| **WeeklyAlarm** | seconds_of_day + week_mask | 每周特定日期执行 | +| **WorkdayAlarm** | seconds_of_day + calendar + workday | 仅工作日或节假日执行 | + +### CronAlarm — Cron 表达式闹钟 + +Cron 表达式格式(6个字段): + +``` +秒 分 时 日 月 星期 +* * * * * * +``` + +示例: +- `"18 28 14 * * *"` — 每天 14:28:18 +- `"0 30 8 * * 1-5"` — 周一到周五 8:30:00 +- `"0 0 12 1 * *"` — 每月1号 12:00:00 + +### OneshotAlarm — 一次性闹钟 + +`seconds_of_day` 参数是从本地 00:00 起到触发时间的秒数。 + +``` +08:30 = 8 × 3600 + 30 × 60 = 30600 +``` + +如果指定时间已过(当前时间晚于定时时间),将在明天执行。 + +### WeeklyAlarm — 每周闹钟 + +`week_mask` 为固定长度 7 个字符的字符串,星期日开始,`'1'` 表示执行,其他表示不执行: + +``` +"0111110" → 周一到周五执行 +"1111111" → 每天执行 +"1000001" → 仅周日和周六执行 +``` + +### WorkdayCalendar — 工作日日历 + +WorkdayCalendar 用于向 WorkdayAlarm 提供日期查询功能: + +| 方法 | 说明 | +|------|------| +| `updateSpecialDays(days)` | 更新特殊节假日/补班日期表 | +| `updateWeekMask(mask)` | 修改一周默认工作日(默认周一到周五) | +| `subscribe(alarm)` | 订阅闹钟(日历变更时自动通知) | +| `unsubscribe(alarm)` | 取消订阅 | +| `isWorkay(day_index)` | 查询指定日期是否为工作日 | + +### WorkdayAlarm — 工作日闹钟 + +`workday` 参数:true=仅工作日执行,false=仅节假日执行。 + +## 使用示例 + +### CronAlarm — 每天 14:28:18 执行 + +> 完整示例见 `examples/alarm/cron_alarm/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + alarm::CronAlarm tmr(sp_loop); + tmr.initialize("18 28 14 * * *"); //! 每天 14:28:18 + tmr.setCallback([] { LogInfo("time is up"); }); + tmr.enable(); + + sp_loop->runLoop(Loop::Mode::kForever); + + LogOutput_Disable(); + return 0; +} +``` + +### OneshotAlarm — 每天早上 8:30 执行 + +> 完整示例见 `examples/alarm/oneshot_alarm/` + +```cpp +alarm::OneshotAlarm tmr(sp_loop); +tmr.initialize(30600); //! 08:30 = 30600秒 +tmr.setCallback([] { LogInfo("time is up"); }); +tmr.enable(); +``` + +### WeeklyAlarm — 周一到周五 8:30 执行 + +> 完整示例见 `examples/alarm/weekly_alarm/` + +```cpp +alarm::WeeklyAlarm tmr(sp_loop); +tmr.initialize(30600, "0111110"); //! 周一到周五 08:30 +tmr.setCallback([] { LogInfo("time is up"); }); +tmr.enable(); +``` + +### 设置独立时区 + +```cpp +tmr.setTimezone(480); //! 东8区(+8 × 60 = 480分钟) +//! 不设置时默认使用系统时区 +``` + +### WorkdayAlarm — 仅工作日执行 + +```cpp +alarm::WorkdayCalendar calendar; +//! 标注2024年1月1日为节假日 +calendar.updateSpecialDays({{19723, false}}); //! day_index从1970-1-1起的天数 + +alarm::WorkdayAlarm tmr(sp_loop); +tmr.initialize(30600, &calendar, true); //! true=仅工作日 +tmr.setCallback([] { LogInfo("workday alarm"); }); +tmr.enable(); + +//! 日历更新后,订阅了该日历的闹钟会自动刷新 +calendar.updateSpecialDays({{19724, true}}); //! 补班日 +``` + +### refresh() — 时钟同步后刷新 + +```cpp +//! 系统时钟同步后刷新定时器,确保触发时间准确 +tmr.refresh(); +``` + +## 常见场景 + +1. **每天定时执行**:OneshotAlarm 指定 seconds_of_day +2. **每周特定日期执行**:WeeklyAlarm 指定 week_mask +3. **灵活 cron 调度**:CronAlarm 使用 cron 表达式 +4. **仅工作日执行**:WorkdayAlarm + WorkdayCalendar +5. **跨时区定时**:setTimezone() 设置独立时区 + +## 注意事项 + +1. **seconds_of_day 计算**:从本地 00:00 起算,如 08:30 = 30600 +2. **OneshotAlarm 的"明天"行为**:如果当前时间已过定时时间,将在明天执行 +3. **WorkdayCalendar 生命期**:传入 WorkdayAlarm 的 calendar 对象生命期必须比 WorkdayAlarm 长 +4. **week_mask 格式**:固定 7 个字符,星期日开始,'1' 为执行标记 +5. **refresh() 的时机**:系统时钟不准时 enable() 的定时任务也不准,时钟同步后应刷新 + +## 相关模块 + +- **event**:Alarm 基于 TimerEvent 实现定时 +- **base**:提供日志、ScopeExit 等基础设施 diff --git a/documents/modules/base.md b/documents/modules/base.md new file mode 100644 index 0000000000000000000000000000000000000000..eed5c68d16a27a6a6891f3d44316ae2e480de341 --- /dev/null +++ b/documents/modules/base.md @@ -0,0 +1,316 @@ +# Base Module (base) + +## What is it? + +The base module is the lowest-level dependency module of cpp-tbox, providing infrastructure such as log macros, assertions, object pools, lifetime tags, scope exit actions, cabinets, common definitions, and more. All other modules depend on the base module. + +## Why do you need it? + +In C++ project development, logging, resource management, and assertion checking are the most fundamental needs. The base module uniformly encapsulates these common functionalities so that all modules share consistent log output interfaces and resource management patterns, avoiding redundant implementations. + +## Header Files + +```cpp +#include //! Log macros +#include //! Log implementation details +#include //! Log output switch +#include //! Assertion macros +#include //! Common macro definitions (NONCOPYABLE, etc.) +#include //! Scope exit action +#include //! Object pool +#include //! Lifetime tag +#include //! Cabinet +#include //! Cabinet token +#include //! Exception catch +#include //! Memory block +#include //! Recorder +#include //! Call stack backtrace +#include //! JSON library (nlohmann/json) +#include //! JSON forward declaration +#include //! Version info +``` + +## Core Classes and Interfaces + +### Log Macros (log.h) + +Logging is the most commonly used feature in a project. The base module defines 8 log levels and corresponding print macros: + +| Log Level | Macro | Description | +|---------|------|------| +| FATAL (0) | `LogFatal(fmt, ...)` | Program will crash | +| ERROR (1) | `LogErr(fmt, ...)` | Severe problem that the program cannot handle | +| WARN (2) | `LogWarn(fmt, ...)` | Internal anomaly, but the program can handle it | +| NOTICE (3) | `LogNotice(fmt, ...)` | Not very severe but should be noted, such as invalid input | +| IMPORTANT (4) | `LogImportant(fmt, ...)` | Important message | +| INFO (5) | `LogInfo(fmt, ...)` | Normal message | +| DEBUG (6) | `LogDbg(fmt, ...)` | Internal program debug information | +| TRACE (7) | `LogTrace(fmt, ...)` | Temporary debug log | + +Helper macros: + +| Macro | Description | +|------|------| +| `LogTag()` | Prints "==> Run Here <==", marking code execution location | +| `LogUndo()` | Prints "!!! Undo !!!", marking unimplemented functionality | +| `LogErrno(err, fmt, ...)` | Prints errno error code and its meaning | + +> **Note**: `LogDbg` and `LogTrace` can be disabled at compile time via the `STATIC_LOG_LEVEL` compile option, reducing log volume in production environments. + +#### MODULE_ID Definition + +Log output includes a module identifier. You need to define `MODULE_ID` in compile options, e.g. `-DMODULE_ID=alarm`. If not defined, the module name in logs will display as "???". + +#### Log Output Switch (log_output.h) + +```cpp +LogOutput_Enable(); //! Enable log output to stdout +LogOutput_Disable(); //! Disable log output +``` + +> `LogOutput_Enable()` is the simplest way to output logs, printing them to stdout. For richer log configuration, please use the **log module**. + +### Assertion Macros (assert.h) + +```cpp +TBOX_ASSERT(expr); //! In debug mode, if the condition is false, prints LogFatal and abort() +``` + +- In `NDEBUG` mode (Release build), `TBOX_ASSERT` does nothing +- In debug mode, a failed assertion prints an error message and terminates the program + +### Scope Exit Action (scope_exit.hpp) + +The `SetScopeExitAction` macro automatically executes a specified action when a code block exits, similar to Go's defer. + +```cpp +SetScopeExitAction(action); //! Execute action when the current scope exits +``` + +**Typical usage**: managing the release of dynamically allocated resources. + +```cpp +Loop* sp_loop = Loop::New(); +SetScopeExitAction([sp_loop] { delete sp_loop; }); +//! ... use sp_loop ... +//! sp_loop is automatically deleted when the function exits +``` + +> **Note**: The `ScopeExitActionGuard` object created by `SetScopeExitAction` is non-copyable and non-movable (NONCOPYABLE/IMMOVABLE). Execution can be canceled via `cancel()`. + +### Object Pool (object_pool.hpp) + +`ObjectPool` is a template class used to reduce the performance overhead of frequent new/delete operations on objects. It caches freed memory blocks through a free block list, avoiding repeated allocation and deallocation of memory. + +```cpp +//! Create an object pool +ObjectPool op; +//! Or specify the number of free blocks to retain +ObjectPool op(64); + +//! Allocate an object (equivalent to new, but faster) +auto p1 = op.alloc(1, "hello"); //! Supports constructor arguments + +//! Free an object (equivalent to delete) +op.free(p1); + +//! Get statistics +auto stat = op.getStat(); +//! stat.total_alloc_times — Total allocation count +//! stat.total_free_times — Total free count +//! stat.peak_alloc_number — Maximum simultaneous allocations +//! stat.peak_free_number — Maximum free cache count +``` + +> **Important**: Objects allocated via ObjectPool **must be freed using ObjectPool**, not with `delete`. + +### Lifetime Tag (lifetime_tag.hpp) + +`LifetimeTag` is used to mark whether an object's lifetime is valid, solving the risk of a pointer referencing an object that has been prematurely destructed. + +```cpp +struct HostObject { + int value = 0; + LifetimeTag tag; //! Lifetime tag +}; + +HostObject *o = new HostObject; +LifetimeTag::Watcher w = o->tag; //! Create a watcher + +if (w) //! true, object is alive + cout << "value:" << o->value << endl; + +delete o; //! Destruct the object + +if (w) //! false, object has been destructed + cout << "Cannot safely access" << endl; +``` + +> **Note**: LifetimeTag currently does not have lock protection and does not support multithreading. + +### Cabinet (cabinet.hpp) + +`Cabinet` is a secure object container with token-based access. Objects are accessed via a Token; even if an object is deleted, an old Token will not mistakenly retrieve a new object. + +```cpp +Cabinet cab; + +//! Store an object, get a Token +auto token = cab.alloc(new MyClass); + +//! Remove an object +auto p = cab.free(token); //! Removes the record and returns the object pointer + +//! Check if a Token is valid +auto p2 = cab.at(token); //! Does not remove, only looks up +``` + +### Common Definitions (defines.h) + +| Macro | Description | +|------|------| +| `NONCOPYABLE(classname)` | Disable copy constructor and assignment | +| `IMMOVABLE(classname)` | Disable move constructor and assignment | +| `DECLARE_COPY_FUNC(classname)` | Declare copy function (for Variables) | +| `CHECK_DELETE_RESET_OBJ(ptr)` | Delete pointer and set to nullptr | + +## Usage Examples + +### Printing Logs + +> Full example at `examples/base/print_log/` + +```cpp +#include +#include + +#define MODULE_ID "my_app" + +int main() { + LogOutput_Enable(); + + LogInfo("program started"); + LogDbg("debug info: count=%d", 42); + LogWarn("unexpected input: %s", "abc"); + LogErr("file open failed"); + + LogOutput_Disable(); + return 0; +} +``` + +### Assertion Checking + +> Full example at `examples/base/assert/` + +```cpp +#include +#include + +int main() { + LogOutput_Enable(); + + int value = 10; + TBOX_ASSERT(value > 0); //! In debug mode, if the condition holds, execution continues + //! TBOX_ASSERT(value < 0); //! If the condition fails, prints LogFatal and abort + + LogOutput_Disable(); + return 0; +} +``` + +### Object Pool + +> Full example at `examples/base/object_pool/` + +```cpp +#include +#include +#include + +class MyStruct { + public: + MyStruct(int i, const std::string &s) : i_(i), s_(s) { } + void print() { LogInfo("i:%d, s:%s", i_, s_.c_str()); } + private: + int i_; + std::string s_; +}; + +int main() { + LogOutput_Enable(); + + ObjectPool op; + + auto p1 = op.alloc(1, "hello"); //! Equivalent to new MyStruct(1, "hello") + p1->print(); + + op.free(p1); //! Equivalent to delete p1, but the memory block is cached + + //! Re-allocating reuses the previously cached memory block, avoiding malloc + auto p2 = op.alloc(2, "world"); + p2->print(); + op.free(p2); + + auto stat = op.getStat(); + LogInfo("alloc:%zu, free:%zu, peak:%zu", + stat.total_alloc_times, stat.total_free_times, stat.peak_alloc_number); + + LogOutput_Disable(); + return 0; +} +``` + +### Lifetime Tag + +> Full example at `examples/base/lifetime_tag/` + +```cpp +#include +#include +#include + +struct Resource { + int data = 100; + tbox::LifetimeTag tag; +}; + +int main() { + LogOutput_Enable(); + + Resource *res = new Resource; + tbox::LifetimeTag::Watcher watcher = res->tag; + + LogInfo("alive: %d, data: %d", (bool)watcher, res->data); + + delete res; //! Object destructed + + LogInfo("alive: %d", (bool)watcher); //! watcher is false + //! Cannot safely access res->data anymore + + LogOutput_Disable(); + return 0; +} +``` + +## Common Scenarios + +1. **Logging**: All modules uniformly use `LogInfo/LogErr/LogDbg` and other macros to print logs +2. **Automatic resource release**: Use `SetScopeExitAction` to automatically delete new'd objects when a function exits +3. **High-frequency object allocation**: Use `ObjectPool` to reduce the performance overhead of frequent new/delete +4. **Pointer safety checking**: Use `LifetimeTag` + `Watcher` to check whether an object is still alive +5. **Disable copy/move**: Use `NONCOPYABLE/IMMOVABLE` macros to protect class semantic integrity + +## Important Notes + +1. **MODULE_ID must be defined**: If not defined, the module name in logs displays as "???", affecting log identification +2. **ObjectPool free vs delete**: Objects allocated via ObjectPool must only be freed using ObjectPool's `free()`, not with `delete` +3. **LifetimeTag does not support multithreading**: Currently there is no lock protection; it is only suitable for single-threaded or Loop-thread contexts +4. **SetScopeExitAction cannot cross scopes**: Its execution timing depends on the exit of the enclosing code block; be mindful of the lifetime of objects captured by lambdas +5. **TBOX_ASSERT is inactive in Release**: Assertions are ignored under NDEBUG compilation; do not use assertions as a substitute for error handling + +## Related Modules + +- **log**: Log channel implementation based on base/log.h, providing file/stdout/syslog and other output methods +- **event**: Depends on base's Cabinet, ObjectPool, defines, etc. +- **All modules**: base is the foundational dependency of all modules diff --git a/documents/modules/base_CN.md b/documents/modules/base_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..ea0402457605aaaaab3101ff4edf5a6827942c0c --- /dev/null +++ b/documents/modules/base_CN.md @@ -0,0 +1,316 @@ +# 基础组件模块 (base) + +## 是什么? + +base 模块是 cpp-tbox 最底层的依赖模块,提供了日志宏、断言、对象池、生命期标签、作用域退出、储物柜、通用定义等基础设施。所有其他模块都依赖 base 模块。 + +## 为什么需要它? + +在 C++ 项目开发中,日志打印、资源管理、断言检查是最基础的需求。base 模块将这些常用功能统一封装,使得所有模块共享一致的日志输出接口和资源管理模式,避免重复实现。 + +## 头文件 + +```cpp +#include //! 日志宏 +#include //! 日志实现细节 +#include //! 日志输出开关 +#include //! 断言宏 +#include //! 通用宏定义(NONCOPYABLE 等) +#include //! 作用域退出动作 +#include //! 对象池 +#include //! 生命期标签 +#include //! 储物柜 +#include //! 储物柜凭据 +#include //! 异常捕获 +#include //! 内存块 +#include //! 记录器 +#include //! 调用栈回溯 +#include //! JSON 库(nlohmann/json) +#include //! JSON 前置声明 +#include //! 版本信息 +``` + +## 核心组件 + +### 日志宏 (log.h) + +日志是项目中最常用的功能。base 模块定义了 8 个日志级别和对应的打印宏: + +| 日志级别 | 宏 | 说明 | +|---------|------|------| +| FATAL (0) | `LogFatal(fmt, ...)` | 程序将崩溃 | +| ERROR (1) | `LogErr(fmt, ...)` | 严重问题,程序无法处理 | +| WARN (2) | `LogWarn(fmt, ...)` | 内部异常,但程序可处理 | +| NOTICE (3) | `LogNotice(fmt, ...)` | 不大严重但应关注,如无效输入 | +| IMPORTANT (4) | `LogImportant(fmt, ...)` | 重要消息 | +| INFO (5) | `LogInfo(fmt, ...)` | 正常消息 | +| DEBUG (6) | `LogDbg(fmt, ...)` | 程序内部调试信息 | +| TRACE (7) | `LogTrace(fmt, ...)` | 临时调试日志 | + +辅助宏: + +| 宏 | 说明 | +|------|------| +| `LogTag()` | 打印 "==> Run Here <==",标记代码执行位置 | +| `LogUndo()` | 打印 "!!! Undo !!!",标记未实现功能 | +| `LogErrno(err, fmt, ...)` | 打印 errno 错误码及其含义 | + +> **注意**:`LogDbg` 和 `LogTrace` 可通过 `STATIC_LOG_LEVEL` 编译选项在编译时屏蔽,减少生产环境的日志量。 + +#### MODULE_ID 定义 + +日志输出时会附带模块标识。需要在编译选项中定义 `MODULE_ID`,如 `-DMODULE_ID=alarm`。若未定义,日志中模块名显示为 "???"。 + +#### 日志输出开关 (log_output.h) + +```cpp +LogOutput_Enable(); //! 开启日志输出到 stdout +LogOutput_Disable(); //! 关闭日志输出 +``` + +> `LogOutput_Enable()` 是最简单的日志输出方式,将日志打印到 stdout。更丰富的日志配置请使用 **log 模块**。 + +### 断言宏 (assert.h) + +```cpp +TBOX_ASSERT(expr); //! 调试模式下,条件不成立时打印 LogFatal 并 abort() +``` + +- 在 `NDEBUG` 模式下(Release 编译),`TBOX_ASSERT` 不执行任何操作 +- 在调试模式下,断言失败会打印错误信息并终止程序 + +### 作用域退出动作 (scope_exit.hpp) + +`SetScopeExitAction` 宏用于在代码块退出时自动执行指定动作,类似于 Go 的 defer。 + +```cpp +SetScopeExitAction(action); //! 在当前作用域退出时执行 action +``` + +**典型用法**:管理动态分配资源的释放。 + +```cpp +Loop* sp_loop = Loop::New(); +SetScopeExitAction([sp_loop] { delete sp_loop; }); +//! ... 使用 sp_loop ... +//! 函数退出时自动 delete sp_loop +``` + +> **注意**:`SetScopeExitAction` 创建的 `ScopeExitActionGuard` 对象不可复制和移动(NONCOPYABLE/IMMOVABLE)。可通过 `cancel()` 取消执行。 + +### 对象池 (object_pool.hpp) + +`ObjectPool` 是一个模板类,用于减少频繁 new/delete 对象的性能开销。通过空闲块链表缓存已释放的内存块,避免反复分配与释放内存。 + +```cpp +//! 创建对象池 +ObjectPool op; +//! 或指定保留空闲块数量 +ObjectPool op(64); + +//! 分配对象(等价于 new,但更快) +auto p1 = op.alloc(1, "hello"); //! 支持构造参数 + +//! 释放对象(等价于 delete) +op.free(p1); + +//! 获取统计数据 +auto stat = op.getStat(); +//! stat.total_alloc_times — 总分配次数 +//! stat.total_free_times — 总释放次数 +//! stat.peak_alloc_number — 最大同时分配数 +//! stat.peak_free_number — 最大空闲缓存数 +``` + +> **重要**:凡是使用 ObjectPool 分配的对象,**一定要使用 ObjectPool 进行释放**,不可用 `delete`。 + +### 生命期标签 (lifetime_tag.hpp) + +`LifetimeTag` 用于标记对象的生命期是否有效,解决"指针指向的对象被提前析构"的风险问题。 + +```cpp +struct HostObject { + int value = 0; + LifetimeTag tag; //! 生命期标签 +}; + +HostObject *o = new HostObject; +LifetimeTag::Watcher w = o->tag; //! 创建观察器 + +if (w) //! true,对象存活 + cout << "value:" << o->value << endl; + +delete o; //! 析构对象 + +if (w) //! false,对象已析构 + cout << "不能安全访问" << endl; +``` + +> **注意**:目前 LifetimeTag 未做加锁保护,不支持多线程。 + +### 储物柜 (cabinet.hpp) + +`Cabinet` 是一个带凭据(Token)的安全对象容器。通过 Token 存取对象,即使对象被删除,旧的 Token 也不会误取到新对象。 + +```cpp +Cabinet cab; + +//! 存入对象,获取 Token +auto token = cab.alloc(new MyClass); + +//! 取出对象 +auto p = cab.free(token); //! 取出并删除记录,返回对象指针 + +//! 检查 Token 是否有效 +auto p2 = cab.at(token); //! 不取出,仅查看 +``` + +### 通用定义 (defines.h) + +| 宏 | 说明 | +|------|------| +| `NONCOPYABLE(classname)` | 禁止拷贝构造和赋值操作 | +| `IMMOVABLE(classname)` | 禁止移动构造和赋值操作 | +| `DECLARE_COPY_FUNC(classname)` | 声明拷贝函数(供 Variables 使用) | +| `CHECK_DELETE_RESET_OBJ(ptr)` | delete 指针并置 nullptr | + +## 使用示例 + +### 打印日志 + +> 完整示例见 `examples/base/print_log/` + +```cpp +#include +#include + +#define MODULE_ID "my_app" + +int main() { + LogOutput_Enable(); + + LogInfo("program started"); + LogDbg("debug info: count=%d", 42); + LogWarn("unexpected input: %s", "abc"); + LogErr("file open failed"); + + LogOutput_Disable(); + return 0; +} +``` + +### 断言检查 + +> 完整示例见 `examples/base/assert/` + +```cpp +#include +#include + +int main() { + LogOutput_Enable(); + + int value = 10; + TBOX_ASSERT(value > 0); //! 调试模式下,条件成立则正常继续 + //! TBOX_ASSERT(value < 0); //! 条件不成立时,打印 LogFatal 并 abort + + LogOutput_Disable(); + return 0; +} +``` + +### 对象池 + +> 完整示例见 `examples/base/object_pool/` + +```cpp +#include +#include +#include + +class MyStruct { + public: + MyStruct(int i, const std::string &s) : i_(i), s_(s) { } + void print() { LogInfo("i:%d, s:%s", i_, s_.c_str()); } + private: + int i_; + std::string s_; +}; + +int main() { + LogOutput_Enable(); + + ObjectPool op; + + auto p1 = op.alloc(1, "hello"); //! 等价于 new MyStruct(1, "hello") + p1->print(); + + op.free(p1); //! 等价于 delete p1,但内存块被缓存 + + //! 再次分配时复用之前缓存的内存块,避免 malloc + auto p2 = op.alloc(2, "world"); + p2->print(); + op.free(p2); + + auto stat = op.getStat(); + LogInfo("alloc:%zu, free:%zu, peak:%zu", + stat.total_alloc_times, stat.total_free_times, stat.peak_alloc_number); + + LogOutput_Disable(); + return 0; +} +``` + +### 生命期标签 + +> 完整示例见 `examples/base/lifetime_tag/` + +```cpp +#include +#include +#include + +struct Resource { + int data = 100; + tbox::LifetimeTag tag; +}; + +int main() { + LogOutput_Enable(); + + Resource *res = new Resource; + tbox::LifetimeTag::Watcher watcher = res->tag; + + LogInfo("alive: %d, data: %d", (bool)watcher, res->data); + + delete res; //! 对象析构 + + LogInfo("alive: %d", (bool)watcher); //! watcher 为 false + //! 不能再安全访问 res->data + + LogOutput_Disable(); + return 0; +} +``` + +## 常见场景 + +1. **日志打印**:所有模块统一使用 `LogInfo/LogErr/LogDbg` 等宏打印日志 +2. **资源自动释放**:使用 `SetScopeExitAction` 在函数退出时自动 delete/new 的对象 +3. **高频对象分配**:使用 `ObjectPool` 减少频繁 new/delete 的性能开销 +4. **指针安全检查**:使用 `LifetimeTag` + `Watcher` 检查对象是否存活 +5. **禁用拷贝/移动**:使用 `NONCOPYABLE/IMMOVABLE` 宏保护类的语义完整性 + +## 注意事项 + +1. **MODULE_ID 必须定义**:未定义时日志中模块名显示为 "???",影响日志定位 +2. **ObjectPool 的 free vs delete**:通过 ObjectPool 分配的对象只能用 ObjectPool 的 `free()` 释放,不能用 `delete` +3. **LifetimeTag 不支持多线程**:目前未加锁保护,仅适用于单线程或 Loop 线程内 +4. **SetScopeExitAction 不可跨作用域**:它的执行时机取决于所在代码块的退出,注意 lambda 捕获的对象生命周期 +5. **TBOX_ASSERT 在 Release 下无效**:NDEBUG 编译时断言被忽略,不要用断言替代错误处理 + +## 相关模块 + +- **log**:基于 base/log.h 的日志通道实现,提供文件/stdout/syslog 等输出方式 +- **event**:依赖 base 的 Cabinet、ObjectPool、defines 等 +- **所有模块**:base 是所有模块的基础依赖 diff --git a/documents/modules/coroutine.md b/documents/modules/coroutine.md new file mode 100644 index 0000000000000000000000000000000000000000..a5e56eddadaee73c8d82eecf55a5342c60ec087a --- /dev/null +++ b/documents/modules/coroutine.md @@ -0,0 +1,251 @@ +# Coroutine Module (coroutine) + +## What is it? + +The coroutine module is a coroutine library built on the event architecture, helping developers handle asynchronous logic with sequential-style code, avoiding callback hell and complex state machines inherent in event-driven programming. + +## Why do you need it? + +Event-driven programs excel at handling "when event X occurs, perform action Y" logic. If events are independent of each other, this is easy to manage. But when dealing with sequential business logic, such as "first do A, then do B, and if either A or B fails, do C...", the event-driven model requires designing complex state machines, resulting in scattered and hard-to-maintain code. + +Advantages of coroutines: +- **Lightweight**: Only requires allocating a stack for each coroutine, with a configurable stack size (default 8KB) +- **Controllable switching**: Coroutine switching is controlled by the program itself, via explicit yield()/wait() calls +- **No resource preemption**: No need for locks or other synchronization mechanisms + +Compared to threads: +- Threads are heavier, consuming both CPU and memory +- Thread switching is uncontrollable +- Resource preemption is difficult to manage + +## Header Files + +```cpp +#include //! Coroutine scheduler +#include //! Channel (similar to Golang chan) +#include //! Mutex +#include //! Semaphore +#include //! Condition +#include //! Broadcast +``` + +## Core Classes and Interfaces + +### Scheduler — Coroutine Scheduler + +| Method | Description | +|------|------| +| `Scheduler(loop)` | Constructor, specifies the event loop | +| `create(entry, run_now, name, stack_size)` | Creates a coroutine, returns RoutineToken | +| `resume(token)` | Resumes the specified coroutine | +| `cancel(token)` | Cancels a coroutine (sends a cancel request, not an immediate stop) | +| `wait()` | Switches to the main coroutine, waits to be woken up by resume | +| `yield()` | Switches to the main coroutine, continues execution in the next event loop iteration | +| `join(other)` | One coroutine waits for another coroutine to finish | +| `getToken()` | Gets the current coroutine Token | +| `isCanceled()` | Whether the current coroutine has been canceled | +| `getName()` | Gets the current coroutine name | +| `getLoop()` | Gets the event loop | +| `cleanup()` | Forcefully stops and cleans up all coroutines | + +### Channel — Channel + +Similar to Golang's chan, used for passing data between coroutines: + +```cpp +Channel ch(sch); + +//! Sender +ch << 42; + +//! Receiver +int value; +ch >> value; //! Waits if the queue is empty +``` + +### Mutex — Mutex + +Mutual exclusion between coroutines: + +```cpp +Mutex mtx(sch); + +//! Recommended: use Locker for automatic management +{ + Mutex::Locker locker(mtx); //! Auto lock + //! ... critical section operations ... +} //! Auto unlock +``` + +### Semaphore — Semaphore + +```cpp +Semaphore sem(sch, 3); //! Initial count of 3 + +sem.acquire(); //! Request a resource (waits when count is 0) +sem.release(); //! Release a resource +``` + +### Condition — Condition + +Wait for multiple conditions to be all satisfied or any one satisfied: + +```cpp +Condition cond(sch, Condition::Logic::kAll); +cond.add("event_a"); +cond.add("event_b"); + +//! Coroutine A waits +cond.wait(); //! Waits for both event_a and event_b to occur + +//! Coroutine B signals +cond.post("event_a"); +//! Coroutine C signals +cond.post("event_b"); //! Both conditions satisfied, wakes up Coroutine A +``` + +### Broadcast — Broadcast + +Multiple coroutines wait for a single signal; when the signal is posted, all waiting coroutines are woken up: + +```cpp +Broadcast bc(sch); + +//! Multiple coroutines wait +bc.wait(); + +//! Post broadcast +bc.post(); //! All waiting coroutines are woken up +``` + +## Usage Examples + +### Basic Usage + +> See the module README and unit test cases for complete examples + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; +using namespace tbox::coroutine; + +int main() { + LogOutput_Enable(); + + Loop *sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + Scheduler sch(sp_loop); + + //! Define coroutine 1 + int routine1_count = 0; + sch.create( + [&] (Scheduler &sch) { + for (int i = 0; i < 20; ++i) { + ++routine1_count; + sch.yield(); //! Voluntarily yield execution + } + }, true, "routine1" + ); + + //! Define coroutine 2 + int routine2_count = 0; + sch.create( + [&] (Scheduler &sch) { + for (int i = 0; i < 10; ++i) { + ++routine2_count; + sch.yield(); + } + }, true, "routine2" + ); + + sp_loop->exitLoop(std::chrono::seconds(1)); + sp_loop->runLoop(); + + LogInfo("r1=%d, r2=%d", routine1_count, routine2_count); + + LogOutput_Disable(); + return 0; +} +``` + +### Inter-Coroutine Communication — Channel + +```cpp +Scheduler sch(sp_loop); + +Channel ch(sch); + +//! Producer coroutine +sch.create([&] (Scheduler &sch) { + for (int i = 0; i < 5; ++i) { + ch << i; + sch.yield(); + } +}); + +//! Consumer coroutine +sch.create([&] (Scheduler &sch) { + int value; + while (ch >> value) { + LogInfo("received: %d", value); + } +}); +``` + +### Inter-Coroutine Mutual Exclusion — Mutex + +```cpp +Mutex mtx(sch); + +sch.create([&] (Scheduler &sch) { + Mutex::Locker locker(mtx); //! Auto lock + LogInfo("locked, doing work"); + sch.yield(); + //! ... critical section operations ... +}); //! Locker destructor auto unlocks +``` + +### Waiting for a Coroutine to Finish — join + +```cpp +auto other_token = sch.create([&] (Scheduler &sch) { + //! Work of another coroutine + sch.yield(); + sch.yield(); +}); + +sch.create([&] (Scheduler &sch) { + sch.join(other_token); //! Wait for the other_token coroutine to finish + LogInfo("other routine finished"); +}); +``` + +## Common Scenarios + +1. **Sequential business logic**: Write multi-step asynchronous flows as sequential code using coroutines +2. **Producer-Consumer**: Use Channel to pass data between coroutines +3. **Shared resource protection**: Use Mutex to protect critical sections between coroutines +4. **Waiting for conditions**: Use Condition to wait for multiple conditions +5. **Broadcast notification**: Use Broadcast to wake up multiple coroutines simultaneously + +## Important Notes + +1. **yield vs wait**: `yield()` switches to the main coroutine and automatically continues in the next event loop iteration; `wait()` switches to the main coroutine and requires `resume()` to be called before it can continue +2. **Coroutines are single-threaded**: All coroutines are scheduled within the same Loop thread; there is no real concurrency, so atomic operations are not needed +3. **Stack size**: The default stack size is 8KB (`ROUTINE_STACK_DEFAULT_SIZE`); a larger stack can be specified via the create() parameter +4. **cancel is asynchronous**: cancel() only sends a cancel request; the coroutine checks isCanceled() at the next wait/yield and exits +5. **Channel >> return value**: When a coroutine is canceled, the `>>` operation returns false +6. **Condition does not support multiple coroutines waiting simultaneously**: Only one coroutine can wait() on the same Condition at a time + +## Related Modules + +- **event**: Coroutines are scheduled and run based on Loop +- **main**: The framework automatically creates a Scheduler, accessible via `ctx.coroutine()` +- **base**: Provides infrastructure such as Cabinet/Token diff --git a/documents/modules/coroutine_CN.md b/documents/modules/coroutine_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..14ff1f014d15c28dfaa15a40c282bcc819c85c6f --- /dev/null +++ b/documents/modules/coroutine_CN.md @@ -0,0 +1,251 @@ +# 协程模块 (coroutine) + +## 是什么? + +coroutine 模块是基于 event 架构的协程库,帮助开发者用顺序型代码处理异步逻辑,避免事件驱动编程中的回调地狱和复杂状态机。 + +## 为什么需要它? + +基于事件驱动的程序擅长处理 "当发生xx事件,就做yy动作" 的逻辑。如果事件之间相互孤立,很好处理。但一旦遇到顺序型业务逻辑,如"先做A,然后做B,如果A或B失败则做C...",事件驱动模型需要设计复杂的状态机,代码零散难维护。 + +协程的优点: +- **轻量**:只需为每个协程分配一个栈,栈大小可指定(默认 8KB) +- **可控切换**:协程之间切换由程序自行控制,yield()/wait() 主动切换 +- **无资源抢占**:不需要锁等同步机制 + +相比线程: +- 线程较重,占 CPU 且耗内存 +- 线程切换不可控 +- 资源抢占不易管理 + +## 头文件 + +```cpp +#include //! 协程调度器 +#include //! 通道(类似 Golang chan) +#include //! 互斥量 +#include //! 信号量 +#include //! 条件量 +#include //! 广播 +``` + +## 核心类与接口 + +### Scheduler — 协程调度器 + +| 方法 | 说明 | +|------|------| +| `Scheduler(loop)` | 构造,指定事件循环 | +| `create(entry, run_now, name, stack_size)` | 创建协程,返回 RoutineToken | +| `resume(token)` | 恢复指定协程 | +| `cancel(token)` | 取消协程(发送取消请求,非立即停止) | +| `wait()` | 切换到主协程,等待被 resume 唤醒 | +| `yield()` | 切换到主协程,下一个事件循环继续执行 | +| `join(other)` | 一个协程等待另一个协程结束 | +| `getToken()` | 获取当前协程 Token | +| `isCanceled()` | 当前协程是否被取消 | +| `getName()` | 当前协程名称 | +| `getLoop()` | 获取事件循环 | +| `cleanup()` | 强行停止并清理所有协程 | + +### Channel — 通道 + +类似 Golang 的 chan,协程间传递数据: + +```cpp +Channel ch(sch); + +//! 发送端 +ch << 42; + +//! 接收端 +int value; +ch >> value; //! 如果队列空则等待 +``` + +### Mutex — 互斥量 + +协程间互斥访问: + +```cpp +Mutex mtx(sch); + +//! 推荐使用 Locker 自动管理 +{ + Mutex::Locker locker(mtx); //! 自动 lock + //! ... 临界区操作 ... +} //! 自动 unlock +``` + +### Semaphore — 信号量 + +```cpp +Semaphore sem(sch, 3); //! 初始计数为3 + +sem.acquire(); //! 请求资源(计数为0时等待) +sem.release(); //! 释放资源 +``` + +### Condition — 条件量 + +等待多个条件同时满足或任一满足: + +```cpp +Condition cond(sch, Condition::Logic::kAll); +cond.add("event_a"); +cond.add("event_b"); + +//! 协程A等待 +cond.wait(); //! 等待 event_a 和 event_b 都发生 + +//! 协程B发出信号 +cond.post("event_a"); +//! 协程C发出信号 +cond.post("event_b"); //! 两个条件都满足,唤醒协程A +``` + +### Broadcast — 广播 + +多个协程等待一个信号,信号发出时所有等待协程都被唤醒: + +```cpp +Broadcast bc(sch); + +//! 多个协程等待 +bc.wait(); + +//! 发出广播 +bc.post(); //! 所有等待的协程被唤醒 +``` + +## 使用示例 + +### 基础用法 + +> 完整示例见模块 README 和单元测试用例 + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; +using namespace tbox::coroutine; + +int main() { + LogOutput_Enable(); + + Loop *sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + Scheduler sch(sp_loop); + + //! 定义协程1 + int routine1_count = 0; + sch.create( + [&] (Scheduler &sch) { + for (int i = 0; i < 20; ++i) { + ++routine1_count; + sch.yield(); //! 主动让出执行权 + } + }, true, "routine1" + ); + + //! 定义协程2 + int routine2_count = 0; + sch.create( + [&] (Scheduler &sch) { + for (int i = 0; i < 10; ++i) { + ++routine2_count; + sch.yield(); + } + }, true, "routine2" + ); + + sp_loop->exitLoop(std::chrono::seconds(1)); + sp_loop->runLoop(); + + LogInfo("r1=%d, r2=%d", routine1_count, routine2_count); + + LogOutput_Disable(); + return 0; +} +``` + +### 协程间通信 — Channel + +```cpp +Scheduler sch(sp_loop); + +Channel ch(sch); + +//! 生产者协程 +sch.create([&] (Scheduler &sch) { + for (int i = 0; i < 5; ++i) { + ch << i; + sch.yield(); + } +}); + +//! 消费者协程 +sch.create([&] (Scheduler &sch) { + int value; + while (ch >> value) { + LogInfo("received: %d", value); + } +}); +``` + +### 协程间互斥 — Mutex + +```cpp +Mutex mtx(sch); + +sch.create([&] (Scheduler &sch) { + Mutex::Locker locker(mtx); //! 自动加锁 + LogInfo("locked, doing work"); + sch.yield(); + //! ... 临界区操作 ... +}); //! locker 析构时自动解锁 +``` + +### 等待协程结束 — join + +```cpp +auto other_token = sch.create([&] (Scheduler &sch) { + //! 另一个协程的工作 + sch.yield(); + sch.yield(); +}); + +sch.create([&] (Scheduler &sch) { + sch.join(other_token); //! 等待 other_token 协程结束 + LogInfo("other routine finished"); +}); +``` + +## 常见场景 + +1. **顺序型业务逻辑**:将多步骤异步流程用协程写为顺序代码 +2. **生产者-消费者**:使用 Channel 在协程间传递数据 +3. **共享资源保护**:使用 Mutex 保护协程间的临界区 +4. **等待条件满足**:使用 Condition 等待多个条件 +5. **广播通知**:使用 Broadcast 同时唤醒多个协程 + +## 注意事项 + +1. **yield vs wait**:`yield()` 切换到主协程,下一个事件循环自动继续;`wait()` 切换到主协程,需要被 `resume()` 唤醒才能继续 +2. **协程是单线程的**:所有协程在同一个 Loop 线程中调度,不存在真正的并发,不需要原子操作 +3. **栈大小**:默认栈大小 8KB(`ROUTINE_STACK_DEFAULT_SIZE`),可通过 create() 参数指定更大的栈 +4. **cancel 是异步的**:cancel() 只是发送取消请求,协程在下次 wait/yield 时检查 isCanceled() 并退出 +5. **Channel 的 >> 返回值**:当协程被 cancel 时,`>>` 操作返回 false +6. **Condition 不支持多协程同时等**:同一 Condition 只能有一个协程在 wait() + +## 相关模块 + +- **event**:协程基于 Loop 调度运行 +- **main**:框架自动创建 Scheduler,通过 `ctx.coroutine()` 获取 +- **base**:提供 Cabinet/Token 等基础设施 diff --git a/documents/modules/crypto.md b/documents/modules/crypto.md new file mode 100644 index 0000000000000000000000000000000000000000..df06961ca21a305b37fa85e59c79c010db11eaa8 --- /dev/null +++ b/documents/modules/crypto.md @@ -0,0 +1,104 @@ +# Crypto Module (crypto) + +## What is it? + +The crypto module provides implementations of two fundamental cryptographic algorithms: MD5 message digest and AES encryption/decryption. + +## Why do you need it? + +In data security scenarios, MD5 is used for verifying data integrity and generating unique identifiers, while AES is used for data encryption protection. The crypto module provides these two most commonly used cryptographic algorithms with simple and easy-to-use interfaces. + +## Header Files + +```cpp +#include //! MD5 message digest +#include //! AES encryption/decryption +``` + +## Core Classes and Interfaces + +### MD5 — MD5 Message Digest + +| Method | Description | +|------|------| +| `MD5()` | Constructor | +| `update(data, len)` | Feed plaintext data, can be called multiple times | +| `finish(digest)` | Finalize computation, output 16-byte digest | + +> **Note**: After calling `finish()`, you cannot call `update()` again. + +### AES — AES Encryption/Decryption + +AES only implements single 16-byte block operations (AES-128). + +| Method | Description | +|------|------| +| `AES(key)` | Constructor, takes a 16-byte key | +| `setKey(key)` | Set/change the key | +| `cipher(input, output)` | Encrypt a 16-byte block | +| `invcipher(input, output)` | Decrypt a 16-byte block | + +> **Note**: Both input and output are 16 bytes in length, and the key is also 16 bytes. + +## Usage Examples + +> Full examples available in the header file inline examples and unit test cases + +### MD5 Computation + +```cpp +const char *str1 = "cpp-tbox, C++ Treasure Box,"; +const char *str2 = " is an event-based service application development library."; + +crypto::MD5 md5; +md5.update(str1, strlen(str1)); //! Can feed data in segments +md5.update(str2, strlen(str2)); + +uint8_t md5_digest[16]; +md5.finish(md5_digest); //! Get the 16-byte MD5 digest + +//! Convert digest to readable hex string +char hex_str[33]; +for (int i = 0; i < 16; ++i) + snprintf(hex_str + i*2, 3, "%02x", md5_digest[i]); +LogInfo("MD5: %s", hex_str); +``` + +### AES Encryption and Decryption + +```cpp +uint8_t key[16] = {0x01,0x02,...}; //! 16-byte key +uint8_t plain_text[16] = "Hello AES!..."; //! 16-byte plaintext +uint8_t cipher_text[16]; //! Ciphertext output +uint8_t decrypted[16]; //! Decrypted plaintext + +crypto::AES aes(key); + +//! Encrypt +aes.cipher(plain_text, cipher_text); + +//! Decrypt +aes.invcipher(cipher_text, decrypted); + +//! decrypted should match plain_text +``` + +## Common Scenarios + +1. **Data Integrity Verification**: Compute a file's MD5 digest and compare it against a known digest +2. **Unique Identifier Generation**: Use MD5 to generate a unique ID from combined data +3. **Data Encryption Protection**: Use AES to encrypt sensitive data, transmit or store the ciphertext +4. **Key Rotation**: Use setKey() to switch keys without recreating the AES object + +## Important Notes + +1. **MD5 Security**: MD5 is no longer recommended for security authentication scenarios (collision attacks exist); use it only for checksums and identifier generation +2. **AES Single Block Only**: This implementation only handles 16-byte single blocks; for encrypting longer data, you need to implement CBC/CTR or other modes yourself +3. **Key Length**: Only supports 16-byte keys (AES-128); 24/32-byte keys are not supported +4. **finish() Finality**: After MD5's finish() is called, the object state is marked as finalized and update() cannot be called again +5. **Ciphertext Length**: AES encryption output is the same length as input (16 bytes), with no length increase + +## Related Modules + +- **base**: Provides basic type definitions +- **util**: Can be combined with Base64 to encode encryption results as text diff --git a/documents/modules/crypto_CN.md b/documents/modules/crypto_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..55737820a3b3d38936f559edfce1a108aa7a28a1 --- /dev/null +++ b/documents/modules/crypto_CN.md @@ -0,0 +1,104 @@ +# 加密模块 (crypto) + +## 是什么? + +crypto 模块提供了 MD5 消息摘要和 AES 加密/解密两种基础加密算法的实现。 + +## 为什么需要它? + +在数据安全场景中,MD5 用于校验数据完整性和生成唯一标识,AES 用于数据加密保护。crypto 模块提供了这两种最常用的加密算法,接口简洁易用。 + +## 头文件 + +```cpp +#include //! MD5 消息摘要 +#include //! AES 加密/解密 +``` + +## 核心类与接口 + +### MD5 — MD5 消息摘要 + +| 方法 | 说明 | +|------|------| +| `MD5()` | 构造 | +| `update(data, len)` | 喂入明文数据,可多次调用 | +| `finish(digest)` | 结束运算,输出16字节摘要 | + +> **注意**:`finish()` 后不可再调用 `update()`。 + +### AES — AES 加密/解密 + +AES 仅实现单个 16 字节块的运算(AES-128)。 + +| 方法 | 说明 | +|------|------| +| `AES(key)` | 构造,传入16字节密钥 | +| `setKey(key)` | 设置/更换密钥 | +| `cipher(input, output)` | 加密16字节块 | +| `invcipher(input, output)` | 解密16字节块 | + +> **注意**:input/output 均为 16 字节长度,密钥也是 16 字节。 + +## 使用示例 + +> 完整示例见头文件内联示例和单元测试用例 + +### MD5 计算 + +```cpp +const char *str1 = "cpp-tbox, C++ Treasure Box,"; +const char *str2 = " is an event-based service application development library."; + +crypto::MD5 md5; +md5.update(str1, strlen(str1)); //! 可分段喂入 +md5.update(str2, strlen(str2)); + +uint8_t md5_digest[16]; +md5.finish(md5_digest); //! 得到16字节MD5摘要 + +//! 将摘要转为可读字串 +char hex_str[33]; +for (int i = 0; i < 16; ++i) + snprintf(hex_str + i*2, 3, "%02x", md5_digest[i]); +LogInfo("MD5: %s", hex_str); +``` + +### AES 加密与解密 + +```cpp +uint8_t key[16] = {0x01,0x02,...}; //! 16字节密钥 +uint8_t plain_text[16] = "Hello AES!..."; //! 16字节明文 +uint8_t cipher_text[16]; //! 密文输出 +uint8_t decrypted[16]; //! 解密后明文 + +crypto::AES aes(key); + +//! 加密 +aes.cipher(plain_text, cipher_text); + +//! 解密 +aes.invcipher(cipher_text, decrypted); + +//! decrypted 应与 plain_text 一致 +``` + +## 常见场景 + +1. **数据完整性校验**:计算文件的 MD5 摘要,与已知摘要比对 +2. **唯一标识生成**:用 MD5 对组合数据生成唯一 ID +3. **数据加密保护**:使用 AES 加密敏感数据,传输或存储密文 +4. **密钥更换**:使用 setKey() 切换密钥,无需重新创建 AES 对象 + +## 注意事项 + +1. **MD5 安全性**:MD5 已不推荐用于安全认证场景(存在碰撞攻击),建议仅用于校验和标识生成 +2. **AES 仅单块**:本实现仅处理16字节单块,如需加密长数据需自行实现 CBC/CTR 等模式 +3. **密钥长度**:仅支持 16 字节密钥(AES-128),不支持 24/32 字节密钥 +4. **finish() 后不可再用**:MD5 的 finish() 调用后对象状态被标记为已完成,不可再 update() +5. **密文长度**:AES 加密输出与输入等长(16字节),不增加长度 + +## 相关模块 + +- **base**:提供基础类型定义 +- **util**:可配合 Base64 将加密结果编码为文本 diff --git a/documents/modules/dbus.md b/documents/modules/dbus.md new file mode 100644 index 0000000000000000000000000000000000000000..4e4154f4bc0424d5861ee81d8eb1291812754888 --- /dev/null +++ b/documents/modules/dbus.md @@ -0,0 +1,111 @@ +# D-Bus Integration Module (dbus) + +## What is it? + +The dbus module provides integration between D-Bus (the Linux desktop/system inter-process communication bus) and the cpp-tbox event loop, enabling tbox-based service applications to interact with other system services via D-Bus. + +## Why do you need it? + +In Linux systems, D-Bus is the standard mechanism for inter-process communication. Many system services (such as systemd, NetworkManager, Bluetooth services, etc.) expose interfaces through D-Bus. The dbus module allows tbox programs to interact with these services while maintaining an event-driven asynchronous model. + +## Header Files + +```cpp +#include //! D-Bus integration with event::Loop +#include //! D-Bus connection +``` + +## Core Classes and Interfaces + +### Connection — D-Bus Connection + +| Method | Description | +|------|------| +| `Connection(loop)` | Constructor, specifies the event loop | +| `initialize(bus_type)` | Initialize, specifies the bus type (kSession/kSystem/kStarter) | +| `initialize(bus_address)` | Initialize, specifies a custom bus address | +| `cleanup()` | Clean up the connection | + +Bus types: +- `kSession` — Session bus (user-level desktop services) +- `kSystem` — System bus (system-level services) +- `kStarter` — Starter bus + +### AttachLoop / DetachLoop — Attach/Detach Loop + +```cpp +//! Attach an existing DBusConnection object to event::Loop +dbus::AttachLoop(dbus_conn, sp_loop); + +//! Detach from event::Loop +dbus::DetachLoop(dbus_conn); +``` + +Useful for scenarios where you already have a DBusConnection (such as a connection object obtained directly from libdbus). + +## Usage Examples + +### Basic Connection + +> Full example available in `examples/dbus/00-loop/` + +```cpp +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + + dbus::Connection dbus_conn(sp_loop); + dbus_conn.initialize(dbus::Connection::kSession); //! Connect to session bus + + //! From here, dbus_conn can be used for D-Bus method calls, signal reception, etc. + + sp_loop->runLoop(); + dbus_conn.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### Attaching an Existing DBusConnection + +```cpp +DBusConnection *raw_conn = dbus_bus_get(DBUS_BUS_SESSION, nullptr); + +//! Integrate the native D-Bus connection into the tbox event loop +dbus::AttachLoop(raw_conn, sp_loop); + +//! D-Bus event listening is now managed by the tbox Loop + +//! Detach +dbus::DetachLoop(raw_conn); +``` + +## Common Scenarios + +1. **Interacting with System Services**: Call NetworkManager, systemd and other system service interfaces via D-Bus +2. **Desktop Application Integration**: Communicate with GNOME/KDE desktop services +3. **Cross-process Signal Delivery**: Pass events between processes using the D-Bus signal mechanism +4. **Hardware Status Queries**: Query Bluetooth, USB device and other hardware status + +## Important Notes + +1. **Dependency on libdbus**: The dbus-1 library must be linked at compile time +2. **Linux Only**: D-Bus is a Linux-specific IPC mechanism and cannot be used on other platforms +3. **Connection Encapsulation**: The Connection object encapsulates DBusConnection creation and event integration, no manual management needed +4. **Event-driven**: D-Bus event listening is managed by Loop, no additional dbus_watch/dbus_timeout handling required +5. **Cleanup Order**: Clean up Connection before cleaning up Loop when the program exits + +## Related Modules + +- **event**: Manages D-Bus event listening based on Loop +- **base**: Provides Log and other infrastructure diff --git a/documents/modules/dbus_CN.md b/documents/modules/dbus_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..218d3e50e7c0905e2e4e37769c61f58f77c0f717 --- /dev/null +++ b/documents/modules/dbus_CN.md @@ -0,0 +1,111 @@ +# D-Bus 集成模块 (dbus) + +## 是什么? + +dbus 模块提供了 D-Bus(Linux 桌面/系统进程间通信总线)与 cpp-tbox 事件循环的集成能力,让基于 tbox 的服务程序能通过 D-Bus 与其他系统服务交互。 + +## 为什么需要它? + +在 Linux 系统中,D-Bus 是进程间通信的标准机制。许多系统服务(如 systemd、NetworkManager、蓝牙服务等)通过 D-Bus 提供接口。dbus 模块让 tbox 程序能与这些服务交互,同时保持事件驱动的异步模式。 + +## 头文件 + +```cpp +#include //! D-Bus 与 event::Loop 的集成 +#include //! D-Bus 连接 +``` + +## 核心类与接口 + +### Connection — D-Bus 连接 + +| 方法 | 说明 | +|------|------| +| `Connection(loop)` | 构造,指定事件循环 | +| `initialize(bus_type)` | 初始化,指定总线类型(kSession/kSystem/kStarter) | +| `initialize(bus_address)` | 初始化,指定自定义总线地址 | +| `cleanup()` | 清理连接 | + +总线类型: +- `kSession` — 会话总线(用户级桌面服务) +- `kSystem` — 系统总线(系统级服务) +- `kStarter` — 启动总线 + +### AttachLoop / DetachLoop — 挂载/卸载 Loop + +```cpp +//! 将已有的 DBusConnection 对象挂载到 event::Loop +dbus::AttachLoop(dbus_conn, sp_loop); + +//! 从 event::Loop 上卸载 +dbus::DetachLoop(dbus_conn); +``` + +适用于已有 DBusConnection 的场景(如从 libdbus 直接获取的连接对象)。 + +## 使用示例 + +### 基本连接 + +> 完整示例见 `examples/dbus/00-loop/` + +```cpp +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + + dbus::Connection dbus_conn(sp_loop); + dbus_conn.initialize(dbus::Connection::kSession); //! 连接会话总线 + + //! 此后可使用 dbus_conn 进行 D-Bus 方法调用、信号接收等操作 + + sp_loop->runLoop(); + dbus_conn.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### 挂载已有 DBusConnection + +```cpp +DBusConnection *raw_conn = dbus_bus_get(DBUS_BUS_SESSION, nullptr); + +//! 将原生 D-Bus 连接集成到 tbox 事件循环 +dbus::AttachLoop(raw_conn, sp_loop); + +//! 此后 D-Bus 事件监听由 tbox Loop 管理 + +//! 卸载 +dbus::DetachLoop(raw_conn); +``` + +## 常见场景 + +1. **与系统服务交互**:通过 D-Bus 调用 NetworkManager、systemd 等系统服务接口 +2. **桌面应用集成**:与 GNOME/KDE 桌面服务通信 +3. **跨进程信号传递**:通过 D-Bus 信号机制在进程间传递事件 +4. **硬件状态查询**:查询蓝牙、USB 设备等硬件状态 + +## 注意事项 + +1. **依赖 libdbus**:编译时需要链接 dbus-1 库 +2. **仅 Linux**:D-Bus 是 Linux 特有的 IPC 机制,不可用于其他平台 +3. **Connection 封装**:Connection 对象封装了 DBusConnection 的创建和事件集成,无需手动管理 +4. **事件驱动**:D-Bus 事件监听由 Loop 管理,无需额外的 dbus_watch/dbus_timeout 处理 +5. **cleanup 顺序**:程序退出前先 cleanup Connection,再 cleanup Loop + +## 相关模块 + +- **event**:基于 Loop 管理 D-Bus 事件监听 +- **base**:提供 Log 等基础设施 diff --git a/documents/modules/event.md b/documents/modules/event.md new file mode 100644 index 0000000000000000000000000000000000000000..66d894fe1db00af8071faab7c69a409b383462e4 --- /dev/null +++ b/documents/modules/event.md @@ -0,0 +1,223 @@ +# Event-Driven Module (event) + +## What is it? + +The event module is the core foundation of cpp-tbox, providing an event loop (Loop) and three basic event types (FdEvent, TimerEvent, SignalEvent). It is the heart of the entire framework. Almost all other modules depend on the event module to run. + +## Why do you need it? + +In service-type programs, the program needs to respond to multiple events simultaneously: network data arrival, timed task expiration, signal interrupts, etc. If handled with traditional multi-threading, you encounter issues like complex thread synchronization and resource contention. The event-driven model uses a single-threaded event loop to efficiently handle all asynchronous events, avoiding thread switching overhead. + +## Header Files + +```cpp +#include //! Event loop +#include //! File descriptor event +#include //! Timer event +#include //! Signal event +#include //! Event base class +``` + +## Core Classes and Interfaces + +### Loop — Event Loop + +The event loop is the dispatch center for all event handling. The program enters the loop via `runLoop()`, monitoring and dispatching events within the loop. + +| Method | Description | +|--------|-------------| +| `Loop::New()` | Create an event loop of the default type | +| `Loop::New(engine_type)` | Create an event loop with a specified engine type (e.g., "epoll", "poll") | +| `Loop::Engines()` | Get the list of available engines | +| `runLoop(Mode)` | Run the event loop; Mode::kOnce executes once, Mode::kForever runs continuously | +| `exitLoop(wait_time)` | Exit the event loop, optionally specifying a wait time | +| `isInLoopThread()` | Check whether the current thread is the Loop thread | +| `isRunning()` | Check whether the Loop is currently running | +| `newFdEvent(what)` | Create an FdEvent object | +| `newTimerEvent(what)` | Create a TimerEvent object | +| `newSignalEvent(what)` | Create a SignalEvent object | +| `cleanup()` | Clean up Loop resources | + +#### Task Injection Methods + +Loop provides three methods for injecting functions into the loop for execution. Their differences are as follows: + +| Method | Characteristics | Suitable Scenarios | +|--------|-----------------|--------------------| +| `runInLoop(func)` | Uses locking; supports cross-thread and cross-Loop calls | Delegating tasks between different Loops, or other threads dispatching tasks to the Loop thread | +| `runNext(func)` | No locking; does not support cross-thread calls; only callable within the Loop thread | Execute immediately after the current callback completes, e.g., freeing the object itself | +| `run(func)` | Auto-selects: uses runNext within the Loop thread, otherwise uses runInLoop | When unsure which one to use, just pick this one | + +> **Usage tip**: Use `runNext()` when you are certain you are in the Loop thread, use `runInLoop()` when you are certain you are not in the Loop thread, and use `run()` when you are unsure. + +All injection methods return `RunId`, which can be used to cancel unexecuted tasks via `cancel(RunId)`. + +### FdEvent — File Descriptor Event + +Monitors readable, writable, and exception events on a file descriptor (fd). + +| Method | Description | +|--------|-------------| +| `initialize(fd, events, mode)` | Initialize; events is a combination of kReadEvent/kWriteEvent/kExceptEvent | +| `setCallback(cb)` | Set callback function with parameter `short events` | +| `enable()` | Enable monitoring | +| `disable()` | Disable monitoring | + +Event modes: +- `Mode::kPersist` — Persistent monitoring; does not auto-cancel after the event triggers +- `Mode::kOneshot` — One-time monitoring; auto-cancels after the event triggers + +### TimerEvent — Timer Event + +Triggers a callback after a specified time. + +| Method | Description | +|--------|-------------| +| `initialize(time_span, mode)` | Initialize; time_span is the duration in milliseconds | +| `setCallback(cb)` | Set callback function | +| `enable()` | Enable the timer | +| `disable()` | Disable the timer | + +### SignalEvent — Signal Event + +Monitors Linux signals (e.g., SIGINT, SIGTERM). + +| Method | Description | +|--------|-------------| +| `initialize(signum, mode)` | Initialize; monitor a single signal | +| `initialize(sigset, mode)` | Initialize; monitor a signal set | +| `initialize({sig1,sig2,...}, mode)` | Initialize; monitor a signal list | +| `setCallback(cb)` | Set callback function with parameter `int signum` | + +## Usage Examples + +### Timer Example + +> Full example available at `examples/event/02_timer/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + //! Create a periodic timer that triggers every second + auto sp_timer = sp_loop->newTimerEvent("timer"); + SetScopeExitAction([sp_timer] { delete sp_timer; }); + + sp_timer->initialize(std::chrono::seconds(1), Event::Mode::kPersist); + sp_timer->setCallback([] { LogInfo("timer tick"); }); + sp_timer->enable(); + + //! Exit after running for 5 seconds + sp_loop->exitLoop(std::chrono::seconds(5)); + sp_loop->runLoop(); + + LogOutput_Disable(); + return 0; +} +``` + +### Signal Handling Example + +> Full example available at `examples/event/03_signal/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + //! Monitor SIGINT and SIGTERM signals + auto sp_sig = sp_loop->newSignalEvent("signal"); + SetScopeExitAction([sp_sig] { delete sp_sig; }); + + sp_sig->initialize({SIGINT, SIGTERM}, Event::Mode::kPersist); + sp_sig->setCallback( + [sp_loop] (int signum) { + LogInfo("received signal %d", signum); + sp_loop->exitLoop(); + } + ); + sp_sig->enable(); + + sp_loop->runLoop(); + LogOutput_Disable(); + return 0; +} +``` + +### runInLoop Cross-Thread Task Injection + +> Full example available at `examples/event/04_run_in_loop/` + +```cpp +//! Inject a task into the Loop from another thread +std::thread t( + [sp_loop] { + //! Safe cross-thread call + sp_loop->runInLoop([] { LogInfo("task from other thread"); }); + } +); +``` + +### runNext Safe Self-Release + +> Full example available at `examples/event/07_delay_delete/` + +```cpp +//! Safely release the object itself within a callback +void MyClass::onTimeout() { + //! Cannot directly delete this, which would cause accessing a destructed object within the callback + //! Use runNext to perform the release after the callback completes + loop_->runNext([this] { delete this; }); +} +``` + +## Common Scenarios + +1. **Program main loop**: Create a Loop, run `runLoop(Mode::kForever)`, exit via `exitLoop()` +2. **Timed tasks**: Use TimerEvent to create periodic or one-time timers +3. **Signal handling**: Use SignalEvent to capture SIGINT/SIGTERM for graceful program exit +4. **Cross-thread communication**: Other threads inject tasks into the Loop thread via `runInLoop()` +5. **Safe object release**: Use `runNext()` to release objects after callbacks finish + +## Important Notes + +1. **Loop is single-threaded**: All event callbacks execute in the Loop thread; do not perform time-consuming operations in callbacks, as this will block the event loop +2. **runNext is limited to the Loop thread**: Calling `runNext()` across threads is prohibited; cross-thread calls must use `runInLoop()` +3. **Event object lifecycle**: Event objects created through Loop (newFdEvent/newTimerEvent/newSignalEvent) need to be manually deleted; it is recommended to use `SetScopeExitAction` to manage their lifecycle +4. **Oneshot mode**: TimerEvent's kOneshot mode auto-disables after triggering; if you need to trigger again, you must re-enable it +5. **cancel() timing**: Tasks that have already started executing cannot be canceled; only unexecuted tasks can be canceled + +## Related Modules + +- **eventx**: Provides advanced features like thread pools and timer pools based on event +- **base**: Provides infrastructure such as log macros and ScopeExit +- **network**: Implements TCP/UDP/UART communication based on event's FdEvent +- **alarm**: Implements timed alarms based on event's TimerEvent +- **main**: The framework's built-in Loop object, accessible via Context + +## Reference Image + +![tbox-loop](../images/0001-tbox-loop.jpg) diff --git a/documents/modules/event_CN.md b/documents/modules/event_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..b15b4d78f123fe607dc8467b5c1e2a66b309f44d --- /dev/null +++ b/documents/modules/event_CN.md @@ -0,0 +1,223 @@ +# 事件驱动模块 (event) + +## 是什么? + +event 模块是 cpp-tbox 的核心基石,提供了事件循环(Loop)与三种基本事件类型(FdEvent、TimerEvent、SignalEvent),是整个框架运转的心脏。几乎所有其他模块都依赖 event 模块运行。 + +## 为什么需要它? + +在服务型程序中,程序需要同时响应多种事件:网络数据到达、定时任务到期、信号中断等。如果用传统多线程处理,会遇到线程同步复杂、资源抢占等问题。事件驱动模型通过单线程事件循环,高效地处理所有异步事件,避免线程切换开销。 + +## 头文件 + +```cpp +#include //! 事件循环 +#include //! 文件描述符事件 +#include //! 定时器事件 +#include //! 信号事件 +#include //! 事件基类 +``` + +## 核心类与接口 + +### Loop — 事件循环 + +事件循环是所有事件处理的调度中心。程序通过 `runLoop()` 进入循环,在循环中监听和分发事件。 + +| 方法 | 说明 | +|------|------| +| `Loop::New()` | 创建默认类型的事件循环 | +| `Loop::New(engine_type)` | 创建指定引擎类型的事件循环(如 "epoll"、"poll") | +| `Loop::Engines()` | 获取可用的引擎列表 | +| `runLoop(Mode)` | 运行事件循环,Mode::kOnce 执行一次,Mode::kForever 持续运行 | +| `exitLoop(wait_time)` | 退出事件循环,可指定等待时间 | +| `isInLoopThread()` | 判断是否在 Loop 线程内 | +| `isRunning()` | 判断 Loop 是否正在运行 | +| `newFdEvent(what)` | 创建 FdEvent 对象 | +| `newTimerEvent(what)` | 创建 TimerEvent 对象 | +| `newSignalEvent(what)` | 创建 SignalEvent 对象 | +| `cleanup()` | 清理 Loop 资源 | + +#### 任务注入方法 + +Loop 提供三种将函数注入循环执行的方法,它们的区别如下: + +| 方法 | 特点 | 适用场景 | +|------|------|----------| +| `runInLoop(func)` | 有加锁操作,支持跨线程、跨 Loop 调用 | 不同 Loop 之间委派任务,或其它线程向 Loop 线程派任务 | +| `runNext(func)` | 无加锁操作,不支持跨线程,仅 Loop 线程内调用 | 在当前回调完成后立即执行,如释放对象自身 | +| `run(func)` | 自动选择:Loop 线程内用 runNext,否则用 runInLoop | 不确定该用哪个时,选它即可 | + +> **使用建议**:明确在 Loop 线程内的用 `runNext()`,明确不在 Loop 线程内的用 `runInLoop()`,不清楚的用 `run()`。 + +所有注入方法返回 `RunId`,可通过 `cancel(RunId)` 取消未执行的任务。 + +### FdEvent — 文件描述符事件 + +监听文件描述符(fd)上的可读、可写、异常事件。 + +| 方法 | 说明 | +|------|------| +| `initialize(fd, events, mode)` | 初始化,events 为 kReadEvent/kWriteEvent/kExceptEvent 组合 | +| `setCallback(cb)` | 设置回调函数,参数为 `short events` | +| `enable()` | 启用监听 | +| `disable()` | 停用监听 | + +Event 模式: +- `Mode::kPersist` — 持续监听,事件触发后不自动取消 +- `Mode::kOneshot` — 一次性监听,事件触发后自动取消 + +### TimerEvent — 定时器事件 + +在指定时间后触发回调。 + +| 方法 | 说明 | +|------|------| +| `initialize(time_span, mode)` | 初始化,time_span 为毫秒时长 | +| `setCallback(cb)` | 设置回调函数 | +| `enable()` | 启用定时器 | +| `disable()` | 停用定时器 | + +### SignalEvent — 信号事件 + +监听 Linux 信号(如 SIGINT、SIGTERM)。 + +| 方法 | 说明 | +|------|------| +| `initialize(signum, mode)` | 初始化,监听单个信号 | +| `initialize(sigset, mode)` | 初始化,监听信号集合 | +| `initialize({sig1,sig2,...}, mode)` | 初始化,监听信号列表 | +| `setCallback(cb)` | 设置回调函数,参数为 `int signum` | + +## 使用示例 + +### 定时器示例 + +> 完整示例见 `examples/event/02_timer/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + //! 创建周期定时器,每秒触发一次 + auto sp_timer = sp_loop->newTimerEvent("timer"); + SetScopeExitAction([sp_timer] { delete sp_timer; }); + + sp_timer->initialize(std::chrono::seconds(1), Event::Mode::kPersist); + sp_timer->setCallback([] { LogInfo("timer tick"); }); + sp_timer->enable(); + + //! 运行5秒后退出 + sp_loop->exitLoop(std::chrono::seconds(5)); + sp_loop->runLoop(); + + LogOutput_Disable(); + return 0; +} +``` + +### 信号处理示例 + +> 完整示例见 `examples/event/03_signal/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + //! 监听 SIGINT 与 SIGTERM 信号 + auto sp_sig = sp_loop->newSignalEvent("signal"); + SetScopeExitAction([sp_sig] { delete sp_sig; }); + + sp_sig->initialize({SIGINT, SIGTERM}, Event::Mode::kPersist); + sp_sig->setCallback( + [sp_loop] (int signum) { + LogInfo("received signal %d", signum); + sp_loop->exitLoop(); + } + ); + sp_sig->enable(); + + sp_loop->runLoop(); + LogOutput_Disable(); + return 0; +} +``` + +### runInLoop 跨线程任务注入 + +> 完整示例见 `examples/event/04_run_in_loop/` + +```cpp +//! 在其它线程中向 Loop 注入任务 +std::thread t( + [sp_loop] { + //! 跨线程安全调用 + sp_loop->runInLoop([] { LogInfo("task from other thread"); }); + } +); +``` + +### runNext 释放对象自身 + +> 完整示例见 `examples/event/07_delay_delete/` + +```cpp +//! 在回调中安全释放自身对象 +void MyClass::onTimeout() { + //! 不能直接 delete this,会导致回调中访问已析构对象 + //! 使用 runNext 在回调完成后执行释放 + loop_->runNext([this] { delete this; }); +} +``` + +## 常见场景 + +1. **程序主循环**:创建 Loop,运行 `runLoop(Mode::kForever)`,通过 `exitLoop()` 退出 +2. **定时任务**:使用 TimerEvent 创建周期或一次性定时器 +3. **信号处理**:使用 SignalEvent 捕获 SIGINT/SIGTERM,优雅退出程序 +4. **跨线程通信**:其它线程通过 `runInLoop()` 向 Loop 线程注入任务 +5. **安全释放对象**:通过 `runNext()` 在回调结束后释放对象 + +## 注意事项 + +1. **Loop 是单线程的**:所有事件回调都在 Loop 线程中执行,不要在回调中执行耗时操作,否则会阻塞事件循环 +2. **runNext 仅限 Loop 线程**:禁止跨线程调用 `runNext()`,跨线程必须使用 `runInLoop()` +3. **事件对象生命周期**:通过 Loop 创建的事件对象(newFdEvent/newTimerEvent/newSignalEvent)需要手动 delete,建议使用 `SetScopeExitAction` 管理生命周期 +4. **Oneshot 模式**:TimerEvent 的 kOneshot 模式触发后自动 disable,如需再次触发需重新 enable +5. **cancel() 的时机**:已开始执行的任务无法 cancel,只能取消尚未执行的任务 + +## 相关模块 + +- **eventx**:基于 event 提供线程池、定时池等高级功能 +- **base**:提供日志宏、ScopeExit 等基础设施 +- **network**:基于 event 的 FdEvent 实现 TCP/UDP/UART 通信 +- **alarm**:基于 event 的 TimerEvent 实现定时闹钟 +- **main**:框架内置 Loop 对象,通过 Context 获取 + +## 参考图片 + +![tbox-loop](../images/0001-tbox-loop.jpg) diff --git a/documents/modules/eventx.md b/documents/modules/eventx.md new file mode 100644 index 0000000000000000000000000000000000000000..139d486bb8506608621e6d63498264b8d55d5480 --- /dev/null +++ b/documents/modules/eventx.md @@ -0,0 +1,254 @@ +# Event Extension Module (eventx) + +## What is it? + +The eventx module provides advanced asynchronous programming components built on top of the event module: ThreadPool, TimerPool, LoopThread, Async, TimeoutMonitor, and RequestPool. These components make it easier for developers to handle complex scenarios such as multi-thread coordination, timer task management, and request timeouts. + +## Why do you need it? + +The event module provides a single-threaded event loop, but in practice you often need: +- Move time-consuming computation or I/O operations to background threads, then return to the main thread to process results +- Create a large number of timers without having to manage each TimerEvent's lifecycle individually +- Run another event loop in a separate thread +- Convert blocking system calls (such as file read/write) into asynchronous callback form + +eventx is designed to solve these problems. + +## Header Files + +```cpp +#include //! ThreadPool +#include //! TimerPool +#include //! LoopThread +#include //! Async +#include //! RequestPool +#include //! TimeoutMonitor +#include //! ThreadExecutor interface +``` + +## Core Classes and Interfaces + +### ThreadPool + +ThreadPool is used to delegate time-consuming tasks to background threads for execution, and then return to the main thread to execute callbacks upon completion. + +| Method | Description | +|--------|-------------| +| `ThreadPool(main_loop)` | Constructor, specify the main thread's Loop | +| `initialize(min, max)` | Initialize, specify the number of resident threads and maximum threads | +| `execute(task, prio)` | Execute task in a worker thread, prio is priority [-2,2] | +| `execute(task, main_cb, prio)` | Worker thread executes task, then main thread executes main_cb upon completion | +| `execute(task)` | Execute task in a worker thread (no callback) | +| `execute(task, main_cb)` | Worker thread executes task, then main thread executes main_cb upon completion | +| `getTaskStatus(token)` | Get task status | +| `cancel(token)` | Cancel a task | +| `cleanup()` | Clean up resources, wait for all worker threads to finish | +| `snapshot()` | Get thread pool snapshot (thread count, idle count, task count, etc.) | + +### TimerPool + +TimerPool allows developers to easily create timed tasks without worrying about TimerEvent lifecycle management. + +| Method | Description | +|--------|-------------| +| `TimerPool(loop)` | Constructor, specify the Loop | +| `doEvery(msec, cb)` | Periodic timed task, returns TimerToken | +| `doAfter(msec, cb)` | One-shot delayed task, returns TimerToken | +| `doAt(time_point, cb)` | Execute at a specified time point, returns TimerToken | +| `cancel(token)` | Cancel a timed task | +| `cleanup()` | Clean up all timers | + +> **Note**: When using `cancel()` to cancel a timer, ensure that objects held by the callback function are still alive, to avoid lifetime inversion issues. + +### LoopThread + +Runs an event loop in a separate thread, commonly used in scenarios where events need to be processed in another thread. + +| Method | Description | +|--------|-------------| +| `LoopThread(run_now, name)` | Constructor, specify whether to run immediately and the Loop name | +| `start()` | Start the thread | +| `stop()` | Stop the thread | +| `isRunning()` | Whether the thread is running | +| `loop()` | Return the Loop object | + +> **Note**: During runtime, you can only inject tasks into LoopThread via `loop()->runInLoop()` or `loop()->run()`. Do not call other Loop methods directly. Do not delete the Loop object externally. + +### Async + +Converts blocking system calls into asynchronous callback form, utilizing a thread pool to execute in a background thread, then returning to the main thread for callback upon completion. + +| Method | Description | +|--------|-------------| +| `Async(thread_pool)` | Constructor, specify the thread pool | +| `readFile(filename, cb)` | Asynchronously read a file, cb returns (errcode, content) | +| `readFileLines(filename, cb)` | Asynchronously read file lines list | +| `writeFile(filename, content, sync, cb)` | Asynchronously write a file | +| `appendFile(filename, content, sync, cb)` | Asynchronously append to a file | +| `removeFile(filename, cb)` | Asynchronously delete a file | +| `executeCmd(cmd, cb)` | Asynchronously execute a command, cb returns (errcode) | + +### RequestPool + +RequestPool is used to manage context data for asynchronous requests, automatically handling timeout responses. + +```cpp +template +class RequestPool { + //! Initialize + bool initialize(check_interval, check_times); + //! Set timeout callback + void setTimeoutAction(action); + //! Create a new request, return Token + Token newRequest(T *req_ctx = nullptr); + //! Update request context + bool updateRequest(token, T *req_ctx); + //! Take away request context (remove record) + T* removeRequest(token); + //! Cleanup + void cleanup(); +}; +``` + +## Usage Examples + +### ThreadPool Basic Usage + +> Full example see `examples/eventx/thread_pool/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + eventx::ThreadPool tp(sp_loop); + tp.initialize(2, 4); //! Minimum 2 threads, maximum 4 + + //! Background thread executes time-consuming operation, then main thread processes result + tp.execute( + [] { //! Worker thread: execute time-consuming computation + LogInfo("computing in worker thread..."); + // ... time-consuming operation ... + }, + [] { //! Main thread: process result + LogInfo("result received in main thread"); + // ... use computation result ... + } + ); + + //! Exit after 5 seconds + sp_loop->exitLoop(std::chrono::seconds(5)); + sp_loop->runLoop(); + + tp.cleanup(); + LogOutput_Disable(); + return 0; +} +``` + +### TimerPool + +> Full example see `examples/eventx/timer_fd/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + eventx::TimerPool timer_pool(sp_loop); + + //! Execute every second + auto token_every = timer_pool.doEvery(std::chrono::seconds(1), + [] { LogInfo("periodic tick"); }); + + //! Execute once after 3 seconds + auto token_after = timer_pool.doAfter(std::chrono::seconds(3), + [] { LogInfo("one-shot timeout"); }); + + //! Cancel all timers and exit after 5 seconds + timer_pool.doAfter(std::chrono::seconds(5), + [&] { + timer_pool.cancel(token_every); + timer_pool.cancel(token_after); + sp_loop->exitLoop(); + }); + + sp_loop->runLoop(); + timer_pool.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### Asynchronous File Operations + +```cpp +#include + +//! Using Async in the main module +class App : public tbox::main::Module { + public: + App(Context &ctx) : Module("app", ctx), async_(ctx.thread_pool()) { } + + bool onStart() override { + //! Asynchronously read file without blocking the main thread + async_.readFile("/data/config.json", + [](int errcode, std::string &content) { + if (errcode == 0) { + LogInfo("file content: %s", content.c_str()); + } else { + LogErr("read file failed, errcode=%d", errcode); + } + }); + return true; + } + + private: + eventx::Async async_; +}; +``` + +## Common Scenarios + +1. **Time-consuming computation**: Put complex computation into ThreadPool, then use the result in the main thread upon completion +2. **File I/O**: Use Async for asynchronous file read/write without blocking the event loop +3. **Large number of timers**: Use TimerPool to manage timed tasks in bulk without manually managing TimerEvent lifetimes +4. **Multi-Loop coordination**: Use LoopThread to run another event loop in a separate thread +5. **Request timeout management**: Use RequestPool to automatically handle request timeout responses + +## Important Notes + +1. **ThreadPool callback threading**: The `main_cb` callback executes in the main thread (Loop thread), and `backend_task` executes in a worker thread +2. **TimerPool lifetime inversion**: If a timer callback holds a pointer to a short-lifetime object, the timer must be canceled before that object is destructed +3. **LoopThread restrictions**: During runtime, you can only interact with LoopThread via `loop()->runInLoop()` or `loop()->run()` +4. **ThreadPool cleanup**: Calling `cleanup()` will wait for all worker threads to finish, ensure it is called before program exit +5. **Async errcode**: errcode=0 indicates success, non-zero indicates failure (such as file not found, insufficient permissions, etc.) + +## Related Modules + +- **event**: Provides the base Loop, which is the underlying dependency of eventx +- **main**: The framework automatically creates ThreadPool/TimerPool/Async and provides them through Context +- **base**: Provides infrastructure such as Cabinet/Token/defines diff --git a/documents/modules/eventx_CN.md b/documents/modules/eventx_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..a30ec3f6497fc35ae400f6c4e1d89abd6c2325a0 --- /dev/null +++ b/documents/modules/eventx_CN.md @@ -0,0 +1,254 @@ +# 事件扩展模块 (eventx) + +## 是什么? + +eventx 模块基于 event 模块提供高级异步编程组件:线程池(ThreadPool)、定时池(TimerPool)、独立 Loop 线程(LoopThread)、异步操作(Async)、超时监控(TimeoutMonitor)和请求池(RequestPool)。这些组件让开发者更方便地处理多线程协作、定时任务管理、请求超时等复杂场景。 + +## 为什么需要它? + +event 模块提供了单线程事件循环,但在实际应用中常需要: +- 将耗时的计算或 I/O 操作放到后台线程执行,完成后回到主线程处理结果 +- 创建大量定时器但不想逐个管理 TimerEvent 的生命周期 +- 在独立线程中运行另一个事件循环 +- 将阻塞性的系统调用(如文件读写)转换为异步回调形式 + +eventx 正是为了解决这些问题而设计的。 + +## 头文件 + +```cpp +#include //! 线程池 +#include //! 定时池 +#include //! 独立 Loop 线程 +#include //! 异步操作 +#include //! 请求池 +#include //! 超时监控 +#include //! 线程执行器接口 +``` + +## 核心类与接口 + +### ThreadPool — 线程池 + +线程池用于将耗时任务委派给后台线程执行,并在完成后回到主线程执行回调。 + +| 方法 | 说明 | +|------|------| +| `ThreadPool(main_loop)` | 构造,指定主线程的 Loop | +| `initialize(min, max)` | 初始化,指定常驻线程数与最大线程数 | +| `execute(task, prio)` | 在 worker 线程执行任务,prio 为优先级 [-2,2] | +| `execute(task, main_cb, prio)` | worker 线程执行 task,完成后主线程执行 main_cb | +| `execute(task)` | 在 worker 线程执行任务(无回调) | +| `execute(task, main_cb)` | worker 线程执行 task,完成后主线程执行 main_cb | +| `getTaskStatus(token)` | 获取任务状态 | +| `cancel(token)` | 取消任务 | +| `cleanup()` | 清理资源,等待所有 worker 线程结束 | +| `snapshot()` | 获取线程池快照(线程数、空闲数、任务数等) | + +### TimerPool — 定时池 + +定时池让开发者轻松创建定时任务,无需关心 TimerEvent 的生命周期管理。 + +| 方法 | 说明 | +|------|------| +| `TimerPool(loop)` | 构造,指定 Loop | +| `doEvery(msec, cb)` | 周期性定时任务,返回 TimerToken | +| `doAfter(msec, cb)` | 一次性延迟任务,返回 TimerToken | +| `doAt(time_point, cb)` | 在指定时间点执行,返回 TimerToken | +| `cancel(token)` | 取消定时任务 | +| `cleanup()` | 清理所有定时器 | + +> **注意**:使用 `cancel()` 取消定时器时,要确保回调函数中持有的对象仍然存活,避免生命期倒挂问题。 + +### LoopThread — 独立 Loop 线程 + +在独立线程中运行一个事件循环,常用于需要在另一个线程处理事件的场景。 + +| 方法 | 说明 | +|------|------| +| `LoopThread(run_now, name)` | 构造,指定是否立即运行和 Loop 名称 | +| `start()` | 启动线程 | +| `stop()` | 停止线程 | +| `isRunning()` | 线程是否正在运行 | +| `loop()` | 返回 Loop 对象 | + +> **注意**:运行过程中,只能通过 `loop()->runInLoop()` 或 `loop()->run()` 向 LoopThread 注入任务,不能直接调用其他 Loop 方法。不可在外部 delete Loop 对象。 + +### Async — 异步操作 + +将阻塞性的系统调用转换为异步回调形式,利用线程池在后台线程执行,完成后回到主线程回调。 + +| 方法 | 说明 | +|------|------| +| `Async(thread_pool)` | 构造,指定线程池 | +| `readFile(filename, cb)` | 异步读取文件,cb 返回 (errcode, content) | +| `readFileLines(filename, cb)` | 异步读取文件行列表 | +| `writeFile(filename, content, sync, cb)` | 异步写文件 | +| `appendFile(filename, content, sync, cb)` | 异步追加文件 | +| `removeFile(filename, cb)` | 异步删除文件 | +| `executeCmd(cmd, cb)` | 异步执行命令,cb 返回 (errcode) | + +### RequestPool — 请求池 + +请求池用于管理异步请求的上下文数据,自动处理超时回复。 + +```cpp +template +class RequestPool { + //! 初始化 + bool initialize(check_interval, check_times); + //! 设置超时回调 + void setTimeoutAction(action); + //! 创建新请求,返回 Token + Token newRequest(T *req_ctx = nullptr); + //! 更新请求上下文 + bool updateRequest(token, T *req_ctx); + //! 取走请求上下文(移除记录) + T* removeRequest(token); + //! 清理 + void cleanup(); +}; +``` + +## 使用示例 + +### 线程池基本用法 + +> 完整示例见 `examples/eventx/thread_pool/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + eventx::ThreadPool tp(sp_loop); + tp.initialize(2, 4); //! 最少2个线程,最多4个 + + //! 后台线程执行耗时操作,完成后主线程处理结果 + tp.execute( + [] { //! worker 线程:执行耗时计算 + LogInfo("computing in worker thread..."); + // ... 耗时操作 ... + }, + [] { //! 主线程:处理结果 + LogInfo("result received in main thread"); + // ... 使用计算结果 ... + } + ); + + //! 5秒后退出 + sp_loop->exitLoop(std::chrono::seconds(5)); + sp_loop->runLoop(); + + tp.cleanup(); + LogOutput_Disable(); + return 0; +} +``` + +### 定时池 + +> 完整示例见 `examples/eventx/timer_fd/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + eventx::TimerPool timer_pool(sp_loop); + + //! 每秒执行一次 + auto token_every = timer_pool.doEvery(std::chrono::seconds(1), + [] { LogInfo("periodic tick"); }); + + //! 3秒后执行一次 + auto token_after = timer_pool.doAfter(std::chrono::seconds(3), + [] { LogInfo("one-shot timeout"); }); + + //! 5秒后取消所有定时器并退出 + timer_pool.doAfter(std::chrono::seconds(5), + [&] { + timer_pool.cancel(token_every); + timer_pool.cancel(token_after); + sp_loop->exitLoop(); + }); + + sp_loop->runLoop(); + timer_pool.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### 异步文件操作 + +```cpp +#include + +//! 在 main 模块中使用 Async +class App : public tbox::main::Module { + public: + App(Context &ctx) : Module("app", ctx), async_(ctx.thread_pool()) { } + + bool onStart() override { + //! 异步读取文件,不阻塞主线程 + async_.readFile("/data/config.json", + [](int errcode, std::string &content) { + if (errcode == 0) { + LogInfo("file content: %s", content.c_str()); + } else { + LogErr("read file failed, errcode=%d", errcode); + } + }); + return true; + } + + private: + eventx::Async async_; +}; +``` + +## 常见场景 + +1. **耗时计算**:将复杂计算放到 ThreadPool,完成后在主线程使用结果 +2. **文件 I/O**:使用 Async 异步读写文件,不阻塞事件循环 +3. **大量定时器**:使用 TimerPool 批量管理定时任务,无需手动管理 TimerEvent 生命期 +4. **多 Loop 协作**:使用 LoopThread 在独立线程运行另一个事件循环 +5. **请求超时管理**:使用 RequestPool 自动处理请求超时回复 + +## 注意事项 + +1. **ThreadPool 回调线程**:`main_cb` 回调在主线程(Loop 线程)中执行,`backend_task` 在 worker 线程中执行 +2. **TimerPool 生命期倒挂**:如果定时器回调持有了短生命期对象的指针,该对象析构前必须 cancel 定时器 +3. **LoopThread 限制**:运行过程中只能通过 `loop()->runInLoop()` 或 `loop()->run()` 与 LoopThread 交互 +4. **ThreadPool cleanup**:调用 `cleanup()` 会等待所有 worker 线程结束,确保在程序退出前调用 +5. **Async errcode**:errcode=0 表示成功,非0表示失败(如文件不存在、权限不足等) + +## 相关模块 + +- **event**:提供基础的 Loop,是 eventx 的底层依赖 +- **main**:框架自动创建 ThreadPool/TimerPool/Async,通过 Context 提供 +- **base**:提供 Cabinet/Token/defines 等基础设施 diff --git a/documents/modules/flow.md b/documents/modules/flow.md new file mode 100644 index 0000000000000000000000000000000000000000..a442687e0fa01e5b4097305194c88409b8399a3b --- /dev/null +++ b/documents/modules/flow.md @@ -0,0 +1,190 @@ +# Flow Control Module (flow) + +## What is it? + +The flow module provides two types of flow control tools: multi-level finite state machine (StateMachine) and behavior tree (Action series). StateMachine is used for state-driven business logic, while Action is used for composite complex flows. + +## Why do you need it? + +In event-driven programming, complex business flows need to manage a large number of states and conditional judgments. StateMachine provides clear state definitions and transition rules, while Action provides composite flow control (sequential, parallel, conditional selection, etc.), separating complex control logic from business code. + +![state-machine](../images/0010-state-machine-graph.png) + +![action-tree](../images/0010-action-tree-graph.jpg) + +## Header Files + +```cpp +#include //! State machine +#include //! Action base class +#include //! Action executor +#include //! Event definitions +#include //! Event publisher +#include //! Event subscriber +#include //! Export Graphviz diagram +``` + +## Core Classes and Interfaces + +### StateMachine — Multi-level Finite State Machine + +| Method | Description | +|--------|-------------| +| `newState(state_id, enter_action, exit_action, label)` | Create a state | +| `addRoute(from, event, to, guard, action, label)` | Add a state transition route | +| `addEvent(state_id, event_id, action)` | Add an in-state event handler | +| `setInitState(state_id)` | Set the initial state | +| `setSubStateMachine(state_id, sub_sm)` | Set a sub state machine (hierarchical nesting) | +| `setStateChangedCallback(cb)` | Set state change callback | +| `start()` | Start the state machine | +| `stop()` | Stop the state machine | +| `run(event)` | Run the state machine (pass in an event) | +| `currentState()` | Get the current state | +| `lastState()` | Get the previous state | +| `nextState()` | Get the next state (valid during transition) | +| `isRunning()` | Whether it is running | +| `isTerminated()` | Whether it has terminated | + +#### State Machine Key Concepts + +- **StateID**: State number, 0 is the terminated state, -1 is the invalid state +- **EventID**: Event number, 0 means any event +- **Route**: Defines a conditional route that transitions from one state to another upon receiving a specific event +- **GuardFunc**: Condition judgment function, returns true to indicate the condition is satisfied and the transition can proceed +- **EventFunc**: Event handler function, returns >=0 to indicate a transition to the specified state is needed +- **Sub State Machine**: StateMachine supports nesting; inner state machines can independently manage sub-states + +### Action — Behavior Tree Action Base Class + +Action is the base node of the behavior tree, providing unified lifecycle management: + +| Method | Description | +|--------|-------------| +| `start()` | Start execution | +| `pause()` | Pause | +| `resume()` | Resume | +| `stop()` | Stop | +| `reset()` | Reset to initial state | +| `isReady()` | Whether it is ready (needs subclass implementation) | +| `setFinishCallback(cb)` | Set finish callback | +| `setBlockCallback(cb)` | Set block callback | +| `setTimeout(ms)` | Set timeout duration | +| `finish(is_succ, why, trace)` | Manually finish | +| `block(why, trace)` | Manually pause | + +Action states: kIdle (idle) → kRunning (running) → kFinished (finished)/kStoped (stopped)/kPause (paused) + +Action results: kUnsure (unknown) → kSuccess (success)/kFail (failure) + +#### Action Subclass Types + +Action has various composite and functional subclasses (see unit test cases in `modules/flow/` for complete examples): + +- **Sequential composition**: SequentialAction (execute multiple sub-actions in order) +- **Parallel composition**: ParallelAction (execute multiple sub-actions simultaneously) +- **Conditional selection**: IfElseAction (conditional branch), SwitchAction (multi-way selection) +- **Loop control**: LoopAction (loop execution), WhileAction (conditional loop) +- **Decorators**: RetryAction (retry on failure), TimeoutAction (timeout control), DelayAction (delayed start) +- **Transaction**: TransactionAction (commit on all success, rollback on any failure) + +### Event — Event Definition + +```cpp +struct Event { + using ID = int; + ID id = 0; + const void *extra = nullptr; //! Attached data pointer +}; +``` + +Supports creation from enum types: `Event(MyEvent::kTimeout, &data)` + +## Usage Examples + +### State Machine — Simple Switch + +> See unit test cases in `modules/flow/` for complete examples + +```cpp +enum State { kOff = 1, kOn = 2 }; +enum Event { kToggle = 10 }; + +StateMachine sm; + +//! Create two states +sm.newState(kOff, [](Event) { LogInfo("enter OFF"); }, [](Event) { LogInfo("exit OFF"); }); +sm.newState(kOn, [](Event) { LogInfo("enter ON"); }, [](Event) { LogInfo("exit ON"); }); + +//! Add transition routes: switch to the other state upon receiving Toggle event from any state +sm.addRoute(kOff, kToggle, kOn, nullptr, nullptr); +sm.addRoute(kOn, kToggle, kOff, nullptr, nullptr); + +sm.start(); //! Start from the first state kOff +sm.run(Event(kToggle)); //! OFF → ON +sm.run(Event(kToggle)); //! ON → OFF +``` + +### State Machine — Conditional Route + +```cpp +//! Route with condition judgment: transition only when guard returns true +sm.addRoute(kIdle, kRequest, kBusy, + [](Event ev) { return /* some condition */; }, + nullptr +); +``` + +### Sub State Machine Nesting + +```cpp +StateMachine outer_sm; +StateMachine inner_sm; + +//! Inner state machine definition +inner_sm.newState(kInnerA, ...); +inner_sm.newState(kInnerB, ...); + +//! Attach the inner state machine to a state of the outer state machine +outer_sm.newState(kOuterState, ...); +outer_sm.setSubStateMachine(kOuterState, &inner_sm); +``` + +### Behavior Tree — Sequential Execution + +> See unit test cases in `modules/flow/` for complete examples + +```cpp +//! SequentialAction: execute multiple sub-actions in order +//! Sub-action A completes then automatically starts B, B completes then starts C +//! Any failure causes the overall action to fail +``` + +### Export Graphviz Diagram + +```cpp +Json js; +sm.toJson(js); //! Export state machine as JSON, can be used for visualization +//! Use to_graphviz.h to convert to Graphviz format +``` + +## Common Scenarios + +1. **Device state management**: e.g., IoT device idle → running → fault → recovery state transitions +2. **Protocol state machine**: e.g., TCP connection states, HTTP request processing states +3. **Business flow orchestration**: e.g., order creation → payment → shipping → completion, rollback on failure +4. **Conditional branching**: Select different execution paths based on sensor data +5. **Retry mechanism**: Use RetryAction to automatically retry on failure + +## Important Notes + +1. **StateID 0 is the terminated state**: The state machine automatically terminates upon reaching state 0, no additional handling needed +2. **Sub state machine lifetime**: The sub state machine passed to setSubStateMachine() must have a longer lifetime than the parent state machine +3. **Action finish/block**: finish() indicates normal completion (success or failure), block() indicates pausing to wait for an external condition +4. **Event.extra pointer**: The data pointed to by the extra pointer must remain valid during event handling +5. **toJson export**: The state machine can be exported as JSON for debugging and visualization + +## Related Modules + +- **event**: Run state machines and actions based on Loop +- **util**: Action uses Variables to store variables +- **base**: Provides Json, Log and other infrastructure diff --git a/documents/modules/flow_CN.md b/documents/modules/flow_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..3bcd85fc0c83773947d094fadf10bbca4f0d1ad4 --- /dev/null +++ b/documents/modules/flow_CN.md @@ -0,0 +1,190 @@ +# 流程控制模块 (flow) + +## 是什么? + +flow 模块提供了两类流程控制工具:多层级有限状态机(StateMachine)和行为树(Action 系列)。StateMachine 用于状态驱动的业务逻辑,Action 用于组合型复杂流程。 + +## 为什么需要它? + +在事件驱动编程中,复杂业务流程需要管理大量状态和条件判断。StateMachine 提供清晰的状态定义和转换规则,Action 提供组合型流程控制(顺序、并发、条件选择等),将复杂的控制逻辑从业务代码中分离出来。 + +![state-machine](../images/0010-state-machine-graph.png) + +![action-tree](../images/0010-action-tree-graph.jpg) + +## 头文件 + +```cpp +#include //! 状态机 +#include //! 动作基类 +#include //! 动作执行器 +#include //! 事件定义 +#include //! 事件发布器 +#include //! 事件订阅器 +#include //! 导出 Graphviz 图 +``` + +## 核心类与接口 + +### StateMachine — 多层级有限状态机 + +| 方法 | 说明 | +|------|------| +| `newState(state_id, enter_action, exit_action, label)` | 创建状态 | +| `addRoute(from, event, to, guard, action, label)` | 添加状态转换路由 | +| `addEvent(state_id, event_id, action)` | 添加状态内事件处理 | +| `setInitState(state_id)` | 设置起始状态 | +| `setSubStateMachine(state_id, sub_sm)` | 设置子状态机(层级嵌套) | +| `setStateChangedCallback(cb)` | 设置状态变更回调 | +| `start()` | 启动状态机 | +| `stop()` | 停止状态机 | +| `run(event)` | 运行状态机(传入事件) | +| `currentState()` | 获取当前状态 | +| `lastState()` | 获取上一个状态 | +| `nextState()` | 获取下一个状态(转换中有效) | +| `isRunning()` | 是否运行中 | +| `isTerminated()` | 是否已终止 | + +#### 状态机关键概念 + +- **StateID**:状态编号,0 为终止状态,-1 为无效状态 +- **EventID**:事件编号,0 表示任意事件 +- **Route**:定义从某状态收到某事件后转换到另一状态的条件路由 +- **GuardFunc**:条件判定函数,返回 true 表示条件成立可转换 +- **EventFunc**:事件处理函数,返回 >=0 表示需要转换到指定状态 +- **子状态机**:StateMachine 支持嵌套,内部状态机可以独立管理子状态 + +### Action — 行为树动作基类 + +Action 是行为树的基础节点,提供统一的生命周期管理: + +| 方法 | 说明 | +|------|------| +| `start()` | 开始执行 | +| `pause()` | 暂停 | +| `resume()` | 恢复 | +| `stop()` | 停止 | +| `reset()` | 重置到初始状态 | +| `isReady()` | 是否准备就绪(需子类实现) | +| `setFinishCallback(cb)` | 设置完成回调 | +| `setBlockCallback(cb)` | 设置阻塞回调 | +| `setTimeout(ms)` | 设置超时时间 | +| `finish(is_succ, why, trace)` | 主动结束 | +| `block(why, trace)` | 主动暂停 | + +Action 状态:kIdle(空闲)→ kRunning(运行)→ kFinished(完成)/kStoped(停止)/kPause(暂停) + +Action 结果:kUnsure(未知)→ kSuccess(成功)/kFail(失败) + +#### Action 子类类型 + +Action 有多种组合和功能子类(完整示例见单元测试用例 `modules/flow/`): + +- **顺序组合**:SequentialAction(按顺序执行多个子动作) +- **并发组合**:ParallelAction(同时执行多个子动作) +- **条件选择**:IfElseAction(条件分支)、SwitchAction(多路选择) +- **循环控制**:LoopAction(循环执行)、WhileAction(条件循环) +- **装饰器**:RetryAction(失败重试)、TimeoutAction(超时控制)、DelayAction(延迟启动) +- **事务**:TransactionAction(全部成功则提交,任一失败则回滚) + +### Event — 事件定义 + +```cpp +struct Event { + using ID = int; + ID id = 0; + const void *extra = nullptr; //! 附带数据指针 +}; +``` + +支持从枚举类型创建:`Event(MyEvent::kTimeout, &data)` + +## 使用示例 + +### 状态机 — 简单开关 + +> 完整示例见单元测试用例 `modules/flow/` + +```cpp +enum State { kOff = 1, kOn = 2 }; +enum Event { kToggle = 10 }; + +StateMachine sm; + +//! 创建两个状态 +sm.newState(kOff, [](Event) { LogInfo("enter OFF"); }, [](Event) { LogInfo("exit OFF"); }); +sm.newState(kOn, [](Event) { LogInfo("enter ON"); }, [](Event) { LogInfo("exit ON"); }); + +//! 添加转换路由:任何状态收到 Toggle 事件都切换到另一个状态 +sm.addRoute(kOff, kToggle, kOn, nullptr, nullptr); +sm.addRoute(kOn, kToggle, kOff, nullptr, nullptr); + +sm.start(); //! 从第一个状态 kOff 开始 +sm.run(Event(kToggle)); //! OFF → ON +sm.run(Event(kToggle)); //! ON → OFF +``` + +### 状态机 — 条件路由 + +```cpp +//! 带条件判断的路由:仅当 guard 返回 true 时才转换 +sm.addRoute(kIdle, kRequest, kBusy, + [](Event ev) { return /* 某条件 */; }, + nullptr +); +``` + +### 子状态机嵌套 + +```cpp +StateMachine outer_sm; +StateMachine inner_sm; + +//! 内部状态机定义 +inner_sm.newState(kInnerA, ...); +inner_sm.newState(kInnerB, ...); + +//! 将内部状态机挂到外部状态机的某个状态上 +outer_sm.newState(kOuterState, ...); +outer_sm.setSubStateMachine(kOuterState, &inner_sm); +``` + +### 行为树 — 顺序执行 + +> 完整示例见单元测试用例 `modules/flow/` + +```cpp +//! SequentialAction:按顺序执行多个子动作 +//! 子动作A完成后自动启动B,B完成后启动C +//! 任一失败则整体失败 +``` + +### 导出 Graphviz 图 + +```cpp +Json js; +sm.toJson(js); //! 导出状态机为 JSON,可用于可视化 +//! 使用 to_graphviz.h 可转换为 Graphviz 格式 +``` + +## 常见场景 + +1. **设备状态管理**:如 IoT 设备的空闲→运行→故障→恢复状态流转 +2. **协议状态机**:如 TCP 连接状态、HTTP 请求处理状态 +3. **业务流程编排**:如订单创建→支付→发货→完成,失败则回滚 +4. **条件分支**:根据传感器数据选择不同的执行路径 +5. **重试机制**:使用 RetryAction 在失败时自动重试 + +## 注意事项 + +1. **StateID 0 是终止状态**:状态机到达 0 号状态自动终止,不需要额外处理 +2. **子状态机生命期**:setSubStateMachine() 传入的子状态机生命期需比父状态机长 +3. **Action 的 finish/block**:finish() 表示正常结束(成功或失败),block() 表示暂停等待外部条件 +4. **Event.extra 指针**:extra 指针指向的数据生命期需在事件处理期间有效 +5. **toJson 导出**:状态机可导出为 JSON 用于调试和可视化 + +## 相关模块 + +- **event**:基于 Loop 运行状态机和动作 +- **util**:Action 使用 Variables 存储变量 +- **base**:提供 Json、Log 等基础设施 diff --git a/documents/modules/http.md b/documents/modules/http.md new file mode 100644 index 0000000000000000000000000000000000000000..c7377f707423d0545705e7889984abec17ad2abf --- /dev/null +++ b/documents/modules/http.md @@ -0,0 +1,235 @@ +# HTTP Service Module (http) + +## What is it? + +The http module provides lightweight HTTP server and client implementations, designed with reference to the Node.js Express middleware pattern. It features a concise interface and ease of use. It is intended to supplement service programs with the ability to expose RESTful APIs, rather than to replace mature HTTP servers like Apache/Nginx. + +## Why do you need it? + +In embedded devices or small service programs, you may need to provide simple HTTP APIs or web pages without introducing a heavyweight HTTP server. The http module allows C++ programs to serve HTTP directly, supporting middleware chain processing, route dispatching, file downloading, form uploading, and more. + +## Header Files + +```cpp +#include //! HTTP server +#include //! Request context +#include //! Middleware base class +#include //! Router middleware +#include //! File downloader middleware +#include //! Form data middleware +#include //! HTTP client +#include //! HTTP request +#include //! HTTP response +#include //! HTTP common definitions +#include //! URL parsing +``` + +## Core Classes and Interfaces + +### Server — HTTP Server + +| Method | Description | +|------|------| +| `Server(loop)` | Constructor | +| `initialize(bind_addr, backlog)` | Initialize with bind address | +| `use(handler)` | Add a request handler function | +| `use(middleware)` | Add a middleware | +| `start()` | Start the server | +| `stop()` | Stop the server | +| `cleanup()` | Cleanup | +| `setContextLogEnable(enable)` | Enable/disable detailed send/receive logs (for debugging) | + +### Context — Request Context + +| Method | Description | +|------|------| +| `ctx.req()` | Get the Request object | +| `ctx.res()` | Get the Respond object (cannot be used after done) | + +### Request / Respond Structures + +```cpp +struct Request { + Method method; //! GET/POST/PUT/DELETE etc. + HttpVer http_ver; //! HTTP version + Url::Path url; //! Request path + Headers headers; //! Request headers + std::string body; //! Request body +}; + +struct Respond { + HttpVer http_ver; + StatusCode status_code; //! 200/404/500 etc. + Headers headers; + std::string body; +}; +``` + +### Middleware — Middleware Base Class + +Middleware is the core of the Express pattern. Each middleware receives a Context and a NextFunc, and can either handle the request or call next() to pass it to the next middleware. + +```cpp +class Middleware { + virtual void handle(ContextSptr ctx, const NextFunc &next) = 0; +}; +``` + +### RouterMiddleware — Router Middleware + +Provides route dispatching similar to Express Router: + +```cpp +RouterMiddleware router; +router.get("/", handler); //! GET request +router.post("/api", handler); //! POST request +router.put("/data", handler); //! PUT request +router.del("/item", handler); //! DELETE request +``` + +### FileDownloaderMiddleware — File Downloader Middleware + +Supports file downloading, Range requests, ETag caching, and CORS, compatible with iOS AVPlayer video playback. + +## Usage Examples + +### Simplest HTTP Service + +> Full example in `examples/http/server/simple/` + +```cpp +#include +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; +using namespace tbox::http; +using namespace tbox::http::server; + +int main() { + LogOutput_Enable(); + + auto sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + Server srv(sp_loop); + srv.initialize(network::SockAddr::FromString("0.0.0.0:12345"), 1); + srv.start(); + + //! Add request handler + srv.use( + [&](ContextSptr ctx, const NextFunc &next) { + ctx->res().status_code = StatusCode::k200_OK; + ctx->res().body = "Hello!"; + } + ); + + //! Listen for exit signal + auto sp_sig = sp_loop->newSignalEvent(); + SetScopeExitAction([sp_sig] { delete sp_sig; }); + sp_sig->initialize(SIGINT, Event::Mode::kPersist); + sp_sig->enable(); + sp_sig->setCallback([&] (int) { srv.stop(); sp_loop->exitLoop(); }); + + sp_loop->runLoop(); + srv.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### Route Dispatching + +> Full example in `examples/http/server/router/` + +```cpp +RouterMiddleware router; +srv.use(&router); + +router + .get("/", [](ContextSptr ctx, const NextFunc &next) { + ctx->res().status_code = StatusCode::k200_OK; + ctx->res().headers["Content-Type"] = "text/html; charset=UTF-8"; + ctx->res().body = "

Home

"; + }) + .get("/api/data", [](ContextSptr ctx, const NextFunc &next) { + ctx->res().status_code = StatusCode::k200_OK; + ctx->res().headers["Content-Type"] = "application/json"; + ctx->res().body = "{\"status\":\"ok\"}"; + }) + .post("/api/upload", [](ContextSptr ctx, const NextFunc &next) { + //! Handle POST upload + ctx->res().status_code = StatusCode::k200_OK; + }); +``` + +### Asynchronous Response + +> Full example in `examples/http/server/async_respond/` + +```cpp +srv.use( + [&](ContextSptr ctx, const NextFunc &next) { + //! Do not reply immediately, process asynchronously later + ctx->res().status_code = StatusCode::k200_OK; + + //! Reply after completion in another thread + tp.execute( + [] { /* Background time-consuming operation */ }, + [ctx] { ctx->res().body = "async result"; /* Reply */ } + ); + } +); +``` + +### File Downloading + +> Full example in `examples/http/server/file_download/` + +```cpp +FileDownloaderMiddleware file_dl; +file_dl.setRootPath("/data/files"); //! Set file root directory +srv.use(&file_dl); +``` + +### Form Upload + +> Full example in `examples/http/server/form_data/` + +```cpp +FormDataMiddleware form_data; +srv.use(&form_data); + +router.post("/upload", [](ContextSptr ctx, const NextFunc &next) { + //! Get uploaded file data + auto files = ctx->req().headers; //! Data processed by FormDataMiddleware +}); +``` + +## Common Scenarios + +1. **RESTful API**: Use RouterMiddleware to dispatch GET/POST/PUT/DELETE requests +2. **Static file serving**: Use FileDownloaderMiddleware to provide file downloads +3. **Asynchronous processing**: Receive a request without replying immediately, then respond asynchronously after background thread processing completes +4. **Middleware chain**: Multiple middleware process requests in sequence (e.g., logging -> authentication -> business logic) +5. **Video streaming**: FileDownloaderMiddleware supports Range requests, compatible with iOS AVPlayer + +## Important Notes + +1. **Middleware invocation order**: Middleware added via `use()` executes in the order it was added +2. **NextFunc**: Calling `next()` in a middleware passes the request to the next middleware; not calling next terminates the chain +3. **Context's res()**: You cannot use `res()` after calling `done()` +4. **File download security**: FileDownloaderMiddleware must be configured with the correct root directory to prevent path traversal attacks +5. **Thread safety**: HTTP request handling runs in the Loop thread; asynchronous operations must use runInLoop to return to the main thread + +## Related Modules + +- **event**: Server runs based on Loop +- **network**: HTTP connection management implemented via TcpServer/TcpAcceptor +- **eventx**: Asynchronous responses require ThreadPool +- **base**: Provides StatusCode, Method, and other definitions diff --git a/documents/modules/http_CN.md b/documents/modules/http_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..ae71fd9fd8de05e795e76740cd92500950326657 --- /dev/null +++ b/documents/modules/http_CN.md @@ -0,0 +1,235 @@ +# HTTP 服务模块 (http) + +## 是什么? + +http 模块提供了轻量级 HTTP 服务器和客户端实现,设计参考了 Node.js Express 的中间件模式,接口简洁,使用方便。它旨在补全服务程序对外提供 RESTful API 的能力,而非取代 Apache/Nginx 等成熟 HTTP 服务器。 + +## 为什么需要它? + +在嵌入式设备或小型服务程序中,需要提供简单的 HTTP API 或 Web 页面,但不想引入重量级 HTTP 服务器。http 模块让 C++ 程序能直接提供 HTTP 服务,支持中间件链式处理、路由分发、文件下载、表单上传等功能。 + +## 头文件 + +```cpp +#include //! HTTP 服务端 +#include //! 请求上下文 +#include //! 中间件基类 +#include //! 路由中间件 +#include //! 文件下载中间件 +#include //! 表单数据中间件 +#include //! HTTP 客户端 +#include //! HTTP 请求 +#include //! HTTP 响应 +#include //! HTTP 公共定义 +#include //! URL 解析 +``` + +## 核心类与接口 + +### Server — HTTP 服务端 + +| 方法 | 说明 | +|------|------| +| `Server(loop)` | 构造 | +| `initialize(bind_addr, backlog)` | 初始化绑定地址 | +| `use(handler)` | 添加请求处理函数 | +| `use(middleware)` | 添加中间件 | +| `start()` | 启动服务 | +| `stop()` | 停止服务 | +| `cleanup()` | 清理 | +| `setContextLogEnable(enable)` | 启用/禁用详细收发日志(调试用) | + +### Context — 请求上下文 + +| 方法 | 说明 | +|------|------| +| `ctx.req()` | 获取 Request 对象 | +| `ctx.res()` | 获取 Respond 对象(done 后不可再使用) | + +### Request / Respond 结构 + +```cpp +struct Request { + Method method; //! GET/POST/PUT/DELETE 等 + HttpVer http_ver; //! HTTP 版本 + Url::Path url; //! 请求路径 + Headers headers; //! 请求头 + std::string body; //! 请求体 +}; + +struct Respond { + HttpVer http_ver; + StatusCode status_code; //! 200/404/500 等 + Headers headers; + std::string body; +}; +``` + +### Middleware — 中间件基类 + +中间件是 Express 模式的核心。每个中间件接收 Context 和 NextFunc,可以选择处理请求或调用 next() 传递给下一个中间件。 + +```cpp +class Middleware { + virtual void handle(ContextSptr ctx, const NextFunc &next) = 0; +}; +``` + +### RouterMiddleware — 路由中间件 + +提供类似 Express Router 的路由分发: + +```cpp +RouterMiddleware router; +router.get("/", handler); //! GET 请求 +router.post("/api", handler); //! POST 请求 +router.put("/data", handler); //! PUT 请求 +router.del("/item", handler); //! DELETE 请求 +``` + +### FileDownloaderMiddleware — 文件下载中间件 + +支持文件下载、Range 请求、ETag 缓存和 CORS,适配 iOS AVPlayer 视频播放。 + +## 使用示例 + +### 最简单的 HTTP 服务 + +> 完整示例见 `examples/http/server/simple/` + +```cpp +#include +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; +using namespace tbox::http; +using namespace tbox::http::server; + +int main() { + LogOutput_Enable(); + + auto sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + Server srv(sp_loop); + srv.initialize(network::SockAddr::FromString("0.0.0.0:12345"), 1); + srv.start(); + + //! 添加请求处理 + srv.use( + [&](ContextSptr ctx, const NextFunc &next) { + ctx->res().status_code = StatusCode::k200_OK; + ctx->res().body = "Hello!"; + } + ); + + //! 监听退出信号 + auto sp_sig = sp_loop->newSignalEvent(); + SetScopeExitAction([sp_sig] { delete sp_sig; }); + sp_sig->initialize(SIGINT, Event::Mode::kPersist); + sp_sig->enable(); + sp_sig->setCallback([&] (int) { srv.stop(); sp_loop->exitLoop(); }); + + sp_loop->runLoop(); + srv.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### 路由分发 + +> 完整示例见 `examples/http/server/router/` + +```cpp +RouterMiddleware router; +srv.use(&router); + +router + .get("/", [](ContextSptr ctx, const NextFunc &next) { + ctx->res().status_code = StatusCode::k200_OK; + ctx->res().headers["Content-Type"] = "text/html; charset=UTF-8"; + ctx->res().body = "

Home

"; + }) + .get("/api/data", [](ContextSptr ctx, const NextFunc &next) { + ctx->res().status_code = StatusCode::k200_OK; + ctx->res().headers["Content-Type"] = "application/json"; + ctx->res().body = "{\"status\":\"ok\"}"; + }) + .post("/api/upload", [](ContextSptr ctx, const NextFunc &next) { + //! 处理 POST 上传 + ctx->res().status_code = StatusCode::k200_OK; + }); +``` + +### 异步响应 + +> 完整示例见 `examples/http/server/async_respond/` + +```cpp +srv.use( + [&](ContextSptr ctx, const NextFunc &next) { + //! 不立即回复,稍后异步处理 + ctx->res().status_code = StatusCode::k200_OK; + + //! 在其它线程完成后回复 + tp.execute( + [] { /* 后台耗时操作 */ }, + [ctx] { ctx->res().body = "async result"; /* 回复 */ } + ); + } +); +``` + +### 文件下载 + +> 完整示例见 `examples/http/server/file_download/` + +```cpp +FileDownloaderMiddleware file_dl; +file_dl.setRootPath("/data/files"); //! 设置文件根目录 +srv.use(&file_dl); +``` + +### 表单上传 + +> 完整示例见 `examples/http/server/form_data/` + +```cpp +FormDataMiddleware form_data; +srv.use(&form_data); + +router.post("/upload", [](ContextSptr ctx, const NextFunc &next) { + //! 获取上传的文件数据 + auto files = ctx->req().headers; //! 通过 FormDataMiddleware 处理后的数据 +}); +``` + +## 常见场景 + +1. **RESTful API**:使用 RouterMiddleware 分发 GET/POST/PUT/DELETE 请求 +2. **静态文件服务**:使用 FileDownloaderMiddleware 提供文件下载 +3. **异步处理**:收到请求后不立即回复,在后台线程处理完成后异步响应 +4. **中间件链**:多个中间件依次处理请求(如日志→认证→业务) +5. **视频流**:FileDownloaderMiddleware 支持 Range 请求,适配 iOS AVPlayer + +## 注意事项 + +1. **中间件调用顺序**:`use()` 添加的中间件按添加顺序执行 +2. **NextFunc**:中间件中调用 `next()` 才会将请求传递给下一个中间件;不调用 next 则终止链 +3. **Context 的 res()**:调用 `done()` 后不可再使用 `res()` +4. **文件下载安全性**:FileDownloaderMiddleware 需正确设置根目录,防止路径遍历攻击 +5. **线程安全**:HTTP 请求处理在 Loop 线程中执行,异步操作需通过 runInLoop 回到主线程 + +## 相关模块 + +- **event**:Server 基于 Loop 运行 +- **network**:基于 TcpServer/TcpAcceptor 实现 HTTP 连接管理 +- **eventx**:异步响应需要 ThreadPool +- **base**:提供 StatusCode、Method 等定义 diff --git a/documents/modules/jsonrpc.md b/documents/modules/jsonrpc.md new file mode 100644 index 0000000000000000000000000000000000000000..122f2399baf2f36fed26c09999ebce093f6fa455 --- /dev/null +++ b/documents/modules/jsonrpc.md @@ -0,0 +1,147 @@ +# JSON-RPC Module (jsonrpc) + +## What is it? + +The jsonrpc module provides an implementation of the JSON-RPC 2.0 protocol, supporting request/notification/response message interaction patterns, and can be used with custom transport layers (such as TCP, WebSocket). + +## Why do you need it? + +In scenarios requiring Remote Procedure Calls (RPC), JSON-RPC is a lightweight and easy-to-implement protocol. The jsonrpc module encapsulates message encoding/decoding, request timeout management, asynchronous response, and other mechanisms, allowing developers to focus only on service method implementation. + +## Header Files + +```cpp +#include //! RPC core class +#include //! Protocol abstract base class +#include //! Type definitions +``` + +## Core Classes and Interfaces + +### Rpc — RPC Core + +| Method | Description | +|--------|-------------| +| `Rpc(loop, id_type)` | Construct, specifying ID type (kInt or kString) | +| `initialize(proto, timeout_sec)` | Initialize protocol and timeout | +| `addService(method, cb)` | Register a method service | +| `removeService(method)` | Remove a method service | +| `request(method, params, cb)` | Send a request (requires response) | +| `request(method, cb)` | Send a request (no params) | +| `notify(method, params)` | Send a notification (no response needed) | +| `notify(method)` | Send a notification (no params) | +| `respondResult(int_id, result)` | Asynchronous response with success result | +| `respondError(int_id, errcode, message)` | Asynchronous response with error | +| `clear()` | Clear cached data | + +### ServiceCallback — Method Callback + +```cpp +using ServiceCallback = std::function; +``` + +- Return `true`: synchronous response — the function automatically responds based on response after returning +- Return `false`: asynchronous response — manually respond later via `respondResult/respondError` + +### Proto — Protocol Transport Layer + +Proto is the transport layer abstraction of the protocol. Users need to implement the concrete transport method (e.g., based on TcpConnection): + +| Method | Description | +|--------|-------------| +| `setRecvCallback(req_cb, rsp_cb)` | Set receive callbacks | +| `setSendCallback(send_cb)` | Set send callback | +| `onRecvData(data, size)` | Process received data (must be implemented by subclass) | + +## Usage Examples + +### Request Side (Ping) + +> Full example at `examples/jsonrpc/req_rsp/ping/` + +```cpp +Rpc rpc(sp_loop, IdType::kInt); +rpc.initialize(proto, 30); //! Timeout 30 seconds + +//! Send request, wait for response +rpc.request("ping", Json::object{{"data", "hello"}}, + [](const Response &rsp) { + if (rsp.errcode == 0) + LogInfo("result: %s", rsp.result.dump().c_str()); + else + LogErr("error: %d, %s", rsp.errcode, rsp.message.c_str()); + } +); +``` + +### Service Side (Pong) + +> Full example at `examples/jsonrpc/req_rsp/pong/` + +```cpp +Rpc rpc(sp_loop, IdType::kInt); +rpc.initialize(proto, 30); + +//! Register service method +rpc.addService("ping", + [](int int_id, const Json ¶ms, Response &response) { + //! Synchronous response + response.errcode = 0; + response.result = Json::object{{"echo", params["data"]}}; + return true; + } +); +``` + +### Asynchronous Response + +```cpp +rpc.addService("async_query", + [](int int_id, const Json ¶ms, Response &response) { + //! Asynchronous processing: do not respond immediately, respond later via respondResult + //! Return false to indicate no automatic response + thread_pool.execute( + [int_id, params] { /* background query */ }, + [int_id, &rpc] { + rpc.respondResult(int_id, Json::object{{"status", "ok"}}); + } + ); + return false; + } +); +``` + +### Notification (No Response Needed) + +```cpp +//! Send notification +rpc.notify("event", Json::object{{"type", "alert"}}); +``` + +### Message Communication (Ping/Pong One-way) + +> Full example at `examples/jsonrpc/message/ping/` and `pong/` + +Suitable for simple message passing scenarios without the request-response pattern. + +## Common Scenarios + +1. **Request-Response**: Client sends a request, server responds with a result +2. **Asynchronous Processing**: Server receives a request, processes asynchronously, and responds later +3. **Event Notification**: One side sends a notification message, the other side only receives without responding +4. **Timeout Management**: Requests that exceed the timeout automatically trigger a timeout callback + +## Important Notes + +1. **ID Type**: Int ID auto-increment is simple and efficient; String ID (e.g., UUID) is more secure but has higher overhead +2. **Proto Implementation**: You must implement the Proto's `onRecvData()` method yourself to parse transport layer data +3. **Timeout**: Requests sent via `request` that do not receive a response within the timeout period will trigger a timeout callback (errcode != 0) +4. **int_id for Asynchronous Response**: When responding asynchronously, you must save the `int_id` and use it later to respond +5. **clear() Resets State**: Resets the RPC object's cached data, restoring it to a state where no data has been sent or received + +## Related Modules + +- **event**: Runs based on Loop +- **eventx**: Uses TimeoutMonitor for request timeout management +- **network**: Can implement Proto transport layer based on TcpConnection +- **base**: Provides infrastructure such as Json, Cabinet/Token diff --git a/documents/modules/jsonrpc_CN.md b/documents/modules/jsonrpc_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..ed97ee260a8dc56a34ec67164d0ac852ef44a819 --- /dev/null +++ b/documents/modules/jsonrpc_CN.md @@ -0,0 +1,147 @@ +# JSON-RPC 模块 (jsonrpc) + +## 是什么? + +jsonrpc 模块提供了 JSON-RPC 2.0 协议的实现,支持请求/通知/回复的消息交互模式,可配合自定义传输层(如 TCP、WebSocket)使用。 + +## 为什么需要它? + +在需要远程过程调用(RPC)的场景中,JSON-RPC 是一种轻量级且易于实现的协议。jsonrpc 模块封装了消息编解码、请求超时管理、异步回复等机制,让开发者只需关注服务方法的实现。 + +## 头文件 + +```cpp +#include //! RPC 核心类 +#include //! 协议抽象基类 +#include //! 类型定义 +``` + +## 核心类与接口 + +### Rpc — RPC 核心 + +| 方法 | 说明 | +|------|------| +| `Rpc(loop, id_type)` | 构造,指定 ID 类型(kInt 或 kString) | +| `initialize(proto, timeout_sec)` | 初始化协议和超时时间 | +| `addService(method, cb)` | 注册方法服务 | +| `removeService(method)` | 删除方法服务 | +| `request(method, params, cb)` | 发送请求(需回复) | +| `request(method, cb)` | 发送请求(无参数) | +| `notify(method, params)` | 发送通知(不需要回复) | +| `notify(method)` | 发送通知(无参数) | +| `respondResult(int_id, result)` | 异步回复成功结果 | +| `respondError(int_id, errcode, message)` | 异步回复错误 | +| `clear()` | 清除缓存数据 | + +### ServiceCallback — 方法回调 + +```cpp +using ServiceCallback = std::function; +``` + +- 返回 `true`:同步回复,函数返回后自动根据 response 进行回复 +- 返回 `false`:异步回复,后续通过 `respondResult/respondError` 手动回复 + +### Proto — 协议传输层 + +Proto 是协议的传输层抽象,需要用户实现具体的传输方式(如基于 TcpConnection): + +| 方法 | 说明 | +|------|------| +| `setRecvCallback(req_cb, rsp_cb)` | 设置接收回调 | +| `setSendCallback(send_cb)` | 设置发送回调 | +| `onRecvData(data, size)` | 处理收到的数据(需子类实现) | + +## 使用示例 + +### 请求端 (Ping) + +> 完整示例见 `examples/jsonrpc/req_rsp/ping/` + +```cpp +Rpc rpc(sp_loop, IdType::kInt); +rpc.initialize(proto, 30); //! 超时30秒 + +//! 发送请求,等待回复 +rpc.request("ping", Json::object{{"data", "hello"}}, + [](const Response &rsp) { + if (rsp.errcode == 0) + LogInfo("result: %s", rsp.result.dump().c_str()); + else + LogErr("error: %d, %s", rsp.errcode, rsp.message.c_str()); + } +); +``` + +### 服务端 (Pong) + +> 完整示例见 `examples/jsonrpc/req_rsp/pong/` + +```cpp +Rpc rpc(sp_loop, IdType::kInt); +rpc.initialize(proto, 30); + +//! 注册服务方法 +rpc.addService("ping", + [](int int_id, const Json ¶ms, Response &response) { + //! 同步回复 + response.errcode = 0; + response.result = Json::object{{"echo", params["data"]}}; + return true; + } +); +``` + +### 异步回复 + +```cpp +rpc.addService("async_query", + [](int int_id, const Json ¶ms, Response &response) { + //! 异步处理:不立即回复,稍后通过 respondResult 回复 + //! 返回 false 表示不自动回复 + thread_pool.execute( + [int_id, params] { /* 后台查询 */ }, + [int_id, &rpc] { + rpc.respondResult(int_id, Json::object{{"status", "ok"}}); + } + ); + return false; + } +); +``` + +### 通知(不需要回复) + +```cpp +//! 发送通知 +rpc.notify("event", Json::object{{"type", "alert"}}); +``` + +### 消息通信 (Ping/Pong 单向) + +> 完整示例见 `examples/jsonrpc/message/ping/` 和 `pong/` + +适用于简单的消息传递场景,无需请求-回复模式。 + +## 常见场景 + +1. **请求-回复**:客户端发送请求,服务端回复结果 +2. **异步处理**:服务端收到请求后异步处理,稍后回复 +3. **事件通知**:一方发送通知消息,另一方仅接收不回复 +4. **超时管理**:请求超时自动触发超时回调 + +## 注意事项 + +1. **ID 类型**:Int ID 自增分配简单高效;String ID(如 UUID)更安全但开销更大 +2. **Proto 实现**:需要自行实现 Proto 的 onRecvData() 方法,解析传输层数据 +3. **超时时间**:request 发出的请求如果在超时时间内没有收到回复,将触发超时回调(errcode != 0) +4. **异步回复的 int_id**:异步回复时需要保存 int_id,后续用它回复 +5. **clear() 清除状态**:重置 RPC 对象的缓存数据,恢复到未收发数据的状态 + +## 相关模块 + +- **event**:基于 Loop 运行 +- **eventx**:使用 TimeoutMonitor 实现请求超时管理 +- **network**:可基于 TcpConnection 实现 Proto 传输层 +- **base**:提供 Json、Cabinet/Token 等基础设施 diff --git a/documents/modules/log.md b/documents/modules/log.md new file mode 100644 index 0000000000000000000000000000000000000000..5a0bcdf7260a6946a1b1e7d92724bc44f89a0f72 --- /dev/null +++ b/documents/modules/log.md @@ -0,0 +1,150 @@ +# Log Sink Module (log) + +## What is it? + +The log module provides multiple implementations of log output sinks, routing log data to destinations such as files, standard output, and the system log. It builds a complete logging system on top of the log macros (LogInfo/LogErr, etc.) from the base module. + +## Why do you need it? + +base/log.h only defines the log printing macros and the `LogPrintfFunc` declaration, but does not implement output functionality. The log module provides multiple Sink implementations that, when registered into the logging system, output log data to different destinations at specified levels and formats. + +## Header Files + +```cpp +#include //! Sink base class +#include //! Async Sink base class +#include //! Async file Sink +#include //! Async stdout Sink +#include //! Async syslog Sink +#include //! Sync stdout Sink +``` + +## Core Classes and Interfaces + +### Sink Class Hierarchy + +``` +Sink (base class) + ├── AsyncSink (async base class, uses AsyncPipe) + │ ├── AsyncFileSink → output to file + │ ├── AsyncStdoutSink → output to stdout + │ └── AsyncSyslogSink → output to syslog + └── SyncStdoutSink → sync output to stdout +``` + +### Sink Base Class Methods + +| Method | Description | +|--------|-------------| +| `setLevel(level)` | Set the default log level filter | +| `setLevel(module, level)` | Set the log level for a specific module | +| `unsetLevel(module)` | Remove the level setting for a specific module | +| `enableColor(enable)` | Enable/disable colored output | +| `enable()` | Enable the Sink | +| `disable()` | Disable the Sink | + +### AsyncStdoutSink — Async Standard Output + +The most commonly used log Sink, which outputs logs asynchronously to standard output. The async mode does not block the logging thread. + +```cpp +log::AsyncStdoutSink stdout_sink; +stdout_sink.enable(); +//! After this, LogInfo/LogErr and other logs will output to stdout +``` + +### AsyncFileSink — Async File Output + +Writes logs asynchronously to a file, with automatic date-based file splitting. + +```cpp +log::AsyncFileSink file_sink; +file_sink.setFilePathPrefix("/data/logs/myapp"); //! File path prefix +file_sink.enable(); +``` + +### SyncStdoutSink — Sync Standard Output + +Used in simple scenarios where logs are output directly to stdout without an async pipeline. Suitable for small test programs. + +> `LogOutput_Enable()` / `LogOutput_Disable()` essentially creates/destroys a SyncStdoutSink. + +## Usage Examples + +### Basic Log Output (Simplest Approach) + +```cpp +#include +#include + +int main() { + LogOutput_Enable(); //! Enable log output to stdout + + LogInfo("program started"); + LogErr("some error occurred"); + + LogOutput_Disable(); //! Disable log output + return 0; +} +``` + +### Configuring Log Level Filtering + +```cpp +//! Only output logs at WARN level and above +sink.setLevel(TBOX_LOG_LEVEL_WARN); + +//! Set different levels for specific modules +sink.setLevel("network", TBOX_LOG_LEVEL_DEBUG); //! network module outputs DEBUG level +sink.setLevel("alarm", TBOX_LOG_LEVEL_INFO); //! alarm module only outputs INFO and above +``` + +### Async File Logging + +```cpp +#include +#include + +log::AsyncFileSink file_sink; +file_sink.setFilePathPrefix("/data/logs/myapp"); +file_sink.enable(); + +//! After this, logs will be written to files whose names automatically include the date and process ID +//! e.g.: /data/logs/myapp.20240530_123456.12345/ +``` + +### Multi-Sink Composition + +You can enable multiple Sinks simultaneously, outputting logs to multiple destinations at the same time: + +```cpp +log::AsyncStdoutSink stdout_sink; +stdout_sink.enable(); //! Output to stdout + +log::AsyncFileSink file_sink; +file_sink.setFilePathPrefix("/data/logs/app"); +file_sink.enable(); //! Output to file + +//! Logs now output to both stdout and file +``` + +## Common Scenarios + +1. **Development debugging**: Use AsyncStdoutSink to output to the terminal, set level to DEBUG +2. **Production deployment**: Use AsyncFileSink to write to files, set level to INFO +3. **Combined output**: Enable both stdout + file simultaneously; view real-time logs on the terminal and store history in files +4. **Module-level control**: Set DEBUG level for key modules and INFO level for other modules + +## Important Notes + +1. **Async vs Sync**: AsyncSink uses a dedicated pipe thread to process logs and does not block business threads; SyncStdoutSink outputs directly in the calling thread +2. **Level filtering**: The default level is MAX (outputs all logs), which can be adjusted as needed +3. **Colored output**: enableColor(true) displays colored logs in terminals that support ANSI colors +4. **File splitting**: AsyncFileSink automatically splits log files by date +5. **Cleanup**: Call cleanup() before program exit to ensure all buffered logs are fully written + +## Related Modules + +- **base**: Provides log macros (LogInfo/LogErr, etc.) and the LogPrintfFunc declaration +- **main**: The framework automatically configures the logging system +- **util**: AsyncPipe is the underlying pipeline component used by AsyncSink diff --git a/documents/modules/log_CN.md b/documents/modules/log_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..25d443893087efa942dbc5a7442899a7771df259 --- /dev/null +++ b/documents/modules/log_CN.md @@ -0,0 +1,150 @@ +# 日志通道模块 (log) + +## 是什么? + +log 模块提供了日志输出通道(Sink)的多种实现,将日志数据输出到文件、标准输出、系统日志等目标。它基于 base 模块的日志宏(LogInfo/LogErr 等)构建完整的日志系统。 + +## 为什么需要它? + +base/log.h 只定义了日志打印宏和 `LogPrintfFunc` 声明,但没有实现输出功能。log 模块提供了多种 Sink 实现,通过注册到日志系统,将日志数据按指定级别和格式输出到不同目标。 + +## 头文件 + +```cpp +#include //! Sink 基类 +#include //! 异步 Sink 基类 +#include //! 异步文件 Sink +#include //! 异步 stdout Sink +#include //! 异步 syslog Sink +#include //! 同步 stdout Sink +``` + +## 核心类与接口 + +### Sink 类继承层次 + +``` +Sink(基类) + ├── AsyncSink(异步基类,使用 AsyncPipe) + │ ├── AsyncFileSink → 输出到文件 + │ ├── AsyncStdoutSink → 输出到 stdout + │ └── AsyncSyslogSink → 输出到 syslog + └── SyncStdoutSink → 同步输出到 stdout +``` + +### Sink 基类方法 + +| 方法 | 说明 | +|------|------| +| `setLevel(level)` | 设置默认日志级别过滤 | +| `setLevel(module, level)` | 设置指定模块的日志级别 | +| `unsetLevel(module)` | 取消指定模块的级别设置 | +| `enableColor(enable)` | 启用/禁用彩色输出 | +| `enable()` | 启用 Sink | +| `disable()` | 禁用 Sink | + +### AsyncStdoutSink — 异步标准输出 + +最常用的日志 Sink,将日志异步输出到标准输出。异步模式不会阻塞日志线程。 + +```cpp +log::AsyncStdoutSink stdout_sink; +stdout_sink.enable(); +//! 此后 LogInfo/LogErr 等日志将输出到 stdout +``` + +### AsyncFileSink — 异步文件输出 + +将日志异步写入文件,支持按日期自动分割文件。 + +```cpp +log::AsyncFileSink file_sink; +file_sink.setFilePathPrefix("/data/logs/myapp"); //! 文件路径前缀 +file_sink.enable(); +``` + +### SyncStdoutSink — 同步标准输出 + +简单场景下使用,日志直接输出到 stdout,没有异步管道。适合小型测试程序。 + +> `LogOutput_Enable()` / `LogOutput_Disable()` 实际上就是创建/销毁一个 SyncStdoutSink。 + +## 使用示例 + +### 基本日志输出(最简单方式) + +```cpp +#include +#include + +int main() { + LogOutput_Enable(); //! 开启日志输出到 stdout + + LogInfo("program started"); + LogErr("some error occurred"); + + LogOutput_Disable(); //! 关闭日志输出 + return 0; +} +``` + +### 配置日志级别过滤 + +```cpp +//! 只输出 WARN 及以上级别的日志 +sink.setLevel(TBOX_LOG_LEVEL_WARN); + +//! 为指定模块设置不同的级别 +sink.setLevel("network", TBOX_LOG_LEVEL_DEBUG); //! network 模块输出 DEBUG 级别 +sink.setLevel("alarm", TBOX_LOG_LEVEL_INFO); //! alarm 模块只输出 INFO 及以上 +``` + +### 异步文件日志 + +```cpp +#include +#include + +log::AsyncFileSink file_sink; +file_sink.setFilePathPrefix("/data/logs/myapp"); +file_sink.enable(); + +//! 此后日志将写入文件,文件名自动包含日期和进程号 +//! 如:/data/logs/myapp.20240530_123456.12345/ +``` + +### 多 Sink 组合 + +可同时启用多个 Sink,日志同时输出到多个目标: + +```cpp +log::AsyncStdoutSink stdout_sink; +stdout_sink.enable(); //! 输出到 stdout + +log::AsyncFileSink file_sink; +file_sink.setFilePathPrefix("/data/logs/app"); +file_sink.enable(); //! 输出到文件 + +//! 日志同时输出到 stdout 和文件 +``` + +## 常见场景 + +1. **开发调试**:使用 AsyncStdoutSink 输出到终端,级别设为 DEBUG +2. **生产运行**:使用 AsyncFileSink 写入文件,级别设为 INFO +3. **组合输出**:同时启用 stdout + file,终端看实时日志,文件存历史 +4. **模块级别控制**:为关键模块设 DEBUG 级别,其他模块设 INFO 级别 + +## 注意事项 + +1. **异步 vs 同步**:AsyncSink 使用独立管道线程处理日志,不会阻塞业务线程;SyncStdoutSink 直接在调用线程输出 +2. **级别过滤**:默认级别为 MAX(输出所有日志),可根据需要设置 +3. **彩色输出**:enableColor(true) 在支持 ANSI 颜色的终端中显示彩色日志 +4. **文件分割**:AsyncFileSink 自动按日期分割日志文件 +5. **cleanup**:程序退出前调用 cleanup() 确保所有缓冲日志写入完成 + +## 相关模块 + +- **base**:提供日志宏(LogInfo/LogErr 等)和 LogPrintfFunc 声明 +- **main**:框架自动配置日志系统 +- **util**:AsyncPipe 是 AsyncSink 的底层管道组件 diff --git a/documents/modules/main.md b/documents/modules/main.md new file mode 100644 index 0000000000000000000000000000000000000000..93eec6f178ebf5cc6638cda803a9d5ef897bc63d --- /dev/null +++ b/documents/modules/main.md @@ -0,0 +1,249 @@ +# Application Framework Module (main) + +## What is it? + +The main module is the application startup framework. It provides a unified and complete encapsulation of the program startup process, allowing developers to focus only on business logic without worrying about startup procedures. It automatically creates common components such as the event loop, thread pool, timer pool, and coroutine scheduler, and provides them to business modules through the Context object. + +## Why do you need it? + +When developing service-type programs, you typically need to repeatedly write the following procedures: creating an event loop, initializing logging, configuring a thread pool, handling command-line arguments, responding to exit signals, etc. The main module encapsulates all these procedures uniformly, so developers only need to implement four steps for their business modules: initialize, start, stop, and cleanup. + +![main-framework](../images/0008-main-framework.png) + +## Header Files + +```cpp +#include //! Main entry function and registration interface +#include //! Module base class +#include //! Process context +#include //! Command-line argument parser +#include //! Logging related +#include //! Tracing related +``` + +## Core Classes and Interfaces + +### Main / Start / Stop — Start and Stop + +| Function | Description | +|----------|-------------| +| `Main(argc, argv)` | Run the tbox::main framework in the foreground, blocking until a stop signal is received | +| `Start(argc, argv)` | Run the tbox::main framework in the backend, non-blocking | +| `Stop()` | Stop the backend-running tbox::main framework | +| `RaiseStopSignal()` | Send a stop request to itself | + +### Module — Business Module Base Class + +The Module lifecycle follows this process: + +``` +Construct → Initialize → Start → .Running. → Stop → Cleanup → Destruct +``` + +Using setting up a computer as an analogy: +1. **Construct** — Place the devices one by one +2. **Initialize** — Plug in power, connect cables +3. **Start** — Turn on each device +4. ... Normal operation ... +5. **Stop** — Turn off each device +6. **Cleanup** — Disconnect cables +7. **Destruct** — Remove the devices one by one + +| Method | Description | +|--------|-------------| +| `Module(name, ctx)` | Constructor, name is the module name, ctx is the process context | +| `add(child, required)` | Add a child module. When required=true, failure of the child module's initialization/start will cause the entire program startup to fail | +| `addAs(child, name, required)` | Add a child module and rename it | +| `name()` | Get the module name | +| `ctx()` | Get the process context | +| `state()` | Get the module state (kNone/kInited/kRunning) | + +Virtual functions to override: + +| Virtual Function | Description | +|------------------|-------------| +| `onFillDefaultConfig(Json)` | Fill default configuration parameters (Note: the logging system is not available at this stage) | +| `onInit(const Json &cfg)` | Initialize, read configuration, establish object connections | +| `onStart()` | Start the module, make objects begin working | +| `onStop()` | Stop the module, the reverse operation of onStart() | +| `onCleanup()` | Cleanup the module, the reverse operation of onInit() | + +### Context — Process Context + +Context provides the common components created by the framework, which business modules access via `ctx()`: + +| Interface | Description | +|-----------|-------------| +| `ctx.loop()` | Event loop object | +| `ctx.thread_pool()` | Thread pool object | +| `ctx.timer_pool()` | Timer pool object | +| `ctx.async()` | Async operation object | +| `ctx.terminal()` | Interactive terminal object | +| `ctx.coroutine()` | Coroutine scheduler object | +| `ctx.running_time()` | Program running duration | +| `ctx.start_time_point()` | Program start time point | +| `ctx.args()` | Command-line argument list | + +### Required Functions + +Developers must implement the following functions for the framework to call: + +| Function | Description | +|----------|-------------| +| `RegisterApps(Module &apps, Context &ctx)` | Register application modules | +| `GetAppDescribe()` | Return application description (displayed when executing -h) | +| `GetAppBuildTime()` | Return build time (displayed when executing -v), typically returns `__DATE__ " " __TIME__` | +| `GetAppVersion(major, minor, rev, build)` | Set application version number | + +## Usage Examples + +### Single Application + +> Full example at `examples/main/01_one_app/` + +**Step 1**: Inherit from the Module class + +```cpp +// app.h +#include + +class App : public tbox::main::Module +{ + public: + App(tbox::main::Context &ctx); + ~App(); + + protected: + virtual bool onInit(const tbox::Json &cfg) override; + virtual bool onStart() override; + virtual void onStop() override; + virtual void onCleanup() override; +}; +``` + +```cpp +// app.cpp +#include "app.h" +#include + +App::App(tbox::main::Context &ctx) : Module("app", ctx) +{ + LogTag(); +} + +bool App::onInit(const tbox::Json &cfg) { LogTag(); return true; } +bool App::onStart() { LogTag(); return true; } +void App::onStop() { LogTag(); } +void App::onCleanup() { LogTag(); } +``` + +**Step 2**: Implement the registration functions + +```cpp +// main.cpp +#include +#include "app.h" + +namespace tbox { +namespace main { + +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new ::App(ctx)); +} + +std::string GetAppDescribe() { return "One app sample"; } +std::string GetAppBuildTime() { return __DATE__ " " __TIME__; } + +void GetAppVersion(int &major, int &minor, int &rev, int &build) { + major = 0; minor = 0; rev = 1; build = 0; +} + +}} +``` + +**Step 3**: Add dependency libraries in Makefile + +```makefile +LDFLAGS += -L.. \ + -ltbox_main \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl +``` + +### Multiple Application Modules + +> Full example at `examples/main/02_more_than_one_apps/` + +```cpp +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new App1(ctx)); //! Required startup module + apps.add(new App2(ctx), false); //! Non-required startup module, failure does not affect other modules +} +``` + +### Submodule Nesting + +Module supports a tree-like nesting structure. The parent module automatically manages the lifecycle of child modules: + +```cpp +class ParentApp : public tbox::main::Module { + public: + ParentApp(Context &ctx) : Module("parent", ctx) { + add(new SubModuleA(ctx)); //! Added as a child module + add(new SubModuleB(ctx)); + } +}; + +//! The initialize/start/stop/cleanup of child modules are automatically called by the parent module +//! Do not manually delete or call lifecycle methods of child modules +``` + +### Backend Running Mode + +> Full example at `examples/main/06_run_in_backend/` + +When you need to integrate the tbox::main framework into an existing program framework, you can use the backend running mode: + +```cpp +int main(int argc, char **argv) { + if (!tbox::main::Start(argc, argv)) + return 0; + + //! The existing program framework continues running + while (true) { + // ... + } + + tbox::main::Stop(); + return 0; +} +``` + +## Common Scenarios + +1. **Standard service programs**: Use `Main()` to run in the foreground, business modules obtain common components via Context +2. **Embedded integration**: Use `Start()/Stop()` to integrate the framework into an existing program without affecting the original architecture +3. **Modular development**: Different functional modules independently inherit from Module, composed via `RegisterApps` +4. **Optional modules**: Use `add(child, false)` to add non-required modules; failure does not affect the main flow + +## Important Notes + +1. **Module lifecycle order**: Must follow the Construct→initialize→start→stop→cleanup→destruct order; no skipping allowed +2. **Do not manually manage child modules**: After add(), the child module's lifecycle is managed by the parent module; do not manually delete or call lifecycle methods +3. **Logging is unavailable in onFillDefaultConfig**: The logging system is not yet initialized at this stage; do not use LogInfo and similar macros +4. **Impact of the required parameter**: A child module with required=true that fails onInit/onStart will cause the entire program startup to fail +5. **RegisterApps function signature**: Must be placed in the `tbox::main` namespace, otherwise the framework cannot find it + +## Related Modules + +- **event**: The framework automatically creates a Loop, accessible via `ctx.loop()` +- **eventx**: The framework automatically creates ThreadPool/TimerPool/Async, accessible via `ctx.thread_pool()/ctx.timer_pool()/ctx.async()` +- **terminal**: The framework automatically creates Terminal, accessible via `ctx.terminal()` +- **coroutine**: The framework automatically creates Scheduler, accessible via `ctx.coroutine()` +- **base**: Provides logging, ScopeExit, and other basic components +- **log**: The framework automatically configures the logging system diff --git a/documents/modules/main_CN.md b/documents/modules/main_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..7dd924cf1042b648c713528328d3a415565dc4f1 --- /dev/null +++ b/documents/modules/main_CN.md @@ -0,0 +1,249 @@ +# 应用框架模块 (main) + +## 是什么? + +main 模块是应用程序的启动框架,对程序启动过程进行了统一完备的封装,让开发者只需关心业务逻辑,不必关心启动流程。它自动创建事件循环、线程池、定时池、协程调度器等公共组件,并通过 Context 对象提供给业务模块使用。 + +## 为什么需要它? + +开发服务型程序时,通常需要重复编写以下流程:创建事件循环、初始化日志、配置线程池、处理命令行参数、响应退出信号等。main 模块将这些流程统一封装,开发者只需实现业务模块的初始化、启动、停止、清理四个步骤即可。 + +![main-framework](../images/0008-main-framework.png) + +## 头文件 + +```cpp +#include //! 主入口函数与注册接口 +#include //! 模块基类 +#include //! 进程上下文 +#include //! 命令行参数解析器 +#include //! 日志相关 +#include //! 追踪相关 +``` + +## 核心类与接口 + +### Main / Start / Stop — 启动与停止 + +| 函数 | 说明 | +|------|------| +| `Main(argc, argv)` | 在前端运行 tbox::main 框架,阻塞直到收到停止信号 | +| `Start(argc, argv)` | 在后端运行 tbox::main 框架,不阻塞 | +| `Stop()` | 停止后端运行的 tbox::main 框架 | +| `RaiseStopSignal()` | 给自身发送停止请求 | + +### Module — 业务模块基类 + +Module 的生命周期遵循以下过程: + +``` +构造 → 初始化 → 启动 → .运行中. → 停止 → 清理 → 析构 +``` + +以使用一台电脑为类比: +1. **构造** — 将设备逐一布置好 +2. **初始化 (initialize)** — 插好电源,连接线缆 +3. **启动 (start)** — 启动各个设备 +4. ... 正常工作 ... +5. **停止 (stop)** — 关闭各个设备 +6. **清理 (cleanup)** — 断开连接线缆 +7. **析构** — 将设备逐一撤走 + +| 方法 | 说明 | +|------|------| +| `Module(name, ctx)` | 构造函数,name 为模块名,ctx 为进程上下文 | +| `add(child, required)` | 添加子模块。required=true 时子模块初始化/启动失败会导致整个程序启动失败 | +| `addAs(child, name, required)` | 添加子模块并重新命名 | +| `name()` | 获取模块名 | +| `ctx()` | 获取进程上下文 | +| `state()` | 获取模块状态(kNone/kInited/kRunning) | + +需要重写的虚函数: + +| 虚函数 | 说明 | +|------|------| +| `onFillDefaultConfig(Json)` | 填充默认配置参数(注意:此阶段日志系统不可用) | +| `onInit(const Json &cfg)` | 初始化,读取配置、建立对象连接 | +| `onStart()` | 启动模块,令对象开始工作 | +| `onStop()` | 停止模块,对应 onStart() 的逆操作 | +| `onCleanup()` | 清理模块,对应 onInit() 的逆操作 | + +### Context — 进程上下文 + +Context 提供了框架创建的公共组件,业务模块通过 `ctx()` 获取: + +| 接口 | 说明 | +|------|------| +| `ctx.loop()` | 事件循环对象 | +| `ctx.thread_pool()` | 线程池对象 | +| `ctx.timer_pool()` | 定时池对象 | +| `ctx.async()` | 异步操作对象 | +| `ctx.terminal()` | 交互终端对象 | +| `ctx.coroutine()` | 协程调度器对象 | +| `ctx.running_time()` | 程序运行时长 | +| `ctx.start_time_point()` | 程序启动时间点 | +| `ctx.args()` | 命令行参数列表 | + +### 必须实现的函数 + +开发者需要实现以下函数供框架调用: + +| 函数 | 说明 | +|------|------| +| `RegisterApps(Module &apps, Context &ctx)` | 注册应用模块 | +| `GetAppDescribe()` | 返回应用描述(执行 -h 时显示) | +| `GetAppBuildTime()` | 返回编译时间(执行 -v 时显示),通常返回 `__DATE__ " " __TIME__` | +| `GetAppVersion(major, minor, rev, build)` | 设置应用版本号 | + +## 使用示例 + +### 单一应用 + +> 完整示例见 `examples/main/01_one_app/` + +**第一步**:继承 Module 类 + +```cpp +// app.h +#include + +class App : public tbox::main::Module +{ + public: + App(tbox::main::Context &ctx); + ~App(); + + protected: + virtual bool onInit(const tbox::Json &cfg) override; + virtual bool onStart() override; + virtual void onStop() override; + virtual void onCleanup() override; +}; +``` + +```cpp +// app.cpp +#include "app.h" +#include + +App::App(tbox::main::Context &ctx) : Module("app", ctx) +{ + LogTag(); +} + +bool App::onInit(const tbox::Json &cfg) { LogTag(); return true; } +bool App::onStart() { LogTag(); return true; } +void App::onStop() { LogTag(); } +void App::onCleanup() { LogTag(); } +``` + +**第二步**:实现注册函数 + +```cpp +// main.cpp +#include +#include "app.h" + +namespace tbox { +namespace main { + +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new ::App(ctx)); +} + +std::string GetAppDescribe() { return "One app sample"; } +std::string GetAppBuildTime() { return __DATE__ " " __TIME__; } + +void GetAppVersion(int &major, int &minor, int &rev, int &build) { + major = 0; minor = 0; rev = 1; build = 0; +} + +}} +``` + +**第三步**:在 Makefile 中添加依赖库 + +```makefile +LDFLAGS += -L.. \ + -ltbox_main \ + -ltbox_terminal \ + -ltbox_network \ + -ltbox_eventx \ + -ltbox_event \ + -ltbox_util \ + -ltbox_base \ + -lpthread -ldl +``` + +### 多个应用模块 + +> 完整示例见 `examples/main/02_more_than_one_apps/` + +```cpp +void RegisterApps(Module &apps, Context &ctx) { + apps.add(new App1(ctx)); //! 必须启动模块 + apps.add(new App2(ctx), false); //! 非必须启动模块,失败不影响其他模块 +} +``` + +### 子模块嵌套 + +Module 支持树状嵌套结构,父模块自动管理子模块的生命周期: + +```cpp +class ParentApp : public tbox::main::Module { + public: + ParentApp(Context &ctx) : Module("parent", ctx) { + add(new SubModuleA(ctx)); //! 作为子模块添加 + add(new SubModuleB(ctx)); + } +}; + +//! 子模块的 initialize/start/stop/cleanup 由父模块自动调用 +//! 不可私自 delete 或手动调用子模块的生命周期方法 +``` + +### 后端运行模式 + +> 完整示例见 `examples/main/06_run_in_backend/` + +当需要将 tbox::main 框架集成到已有程序框架时,可使用后端运行模式: + +```cpp +int main(int argc, char **argv) { + if (!tbox::main::Start(argc, argv)) + return 0; + + //! 原有程序框架继续运行 + while (true) { + // ... + } + + tbox::main::Stop(); + return 0; +} +``` + +## 常见场景 + +1. **标准服务程序**:使用 `Main()` 在前端运行,业务模块通过 Context 获取公共组件 +2. **嵌入式集成**:使用 `Start()/Stop()` 将框架集成到已有程序,不影响原有架构 +3. **模块化开发**:不同功能模块独立继承 Module,通过 `RegisterApps` 组合 +4. **可选模块**:使用 `add(child, false)` 添加非必需模块,失败不影响主流程 + +## 注意事项 + +1. **Module 生命期顺序**:必须按 构造→initialize→start→stop→cleanup→析构 顺序,不可跳跃 +2. **子模块不要手动管理**:add() 后子模块生命期由父模块管控,不可私自 delete 或调用生命周期方法 +3. **onFillDefaultConfig 中日志不可用**:该阶段日志系统尚未初始化,不要使用 LogInfo 等宏 +4. **required 参数的影响**:required=true 的子模块 onInit/onStart 失败会导致整个程序启动失败 +5. **RegisterApps 函数签名**:必须放在 `tbox::main` namespace 中,否则框架找不到 + +## 相关模块 + +- **event**:框架自动创建 Loop,通过 `ctx.loop()` 获取 +- **eventx**:框架自动创建 ThreadPool/TimerPool/Async,通过 `ctx.thread_pool()/ctx.timer_pool()/ctx.async()` 获取 +- **terminal**:框架自动创建 Terminal,通过 `ctx.terminal()` 获取 +- **coroutine**:框架自动创建 Scheduler,通过 `ctx.coroutine()` 获取 +- **base**:提供日志、ScopeExit 等基础组件 +- **log**:框架自动配置日志系统 diff --git a/documents/modules/mqtt.md b/documents/modules/mqtt.md new file mode 100644 index 0000000000000000000000000000000000000000..abf4ed6a4b122aad6cc8e52bfb723ce5ef789e5c --- /dev/null +++ b/documents/modules/mqtt.md @@ -0,0 +1,198 @@ +# MQTT Client Module (mqtt) + +## What is it? + +The mqtt module provides an MQTT protocol client implementation, built on top of the libmosquitto library and seamlessly integrated with cpp-tbox's event loop. It supports TLS encrypted connections, will message configuration, automatic reconnection, and more. + +## Why do you need it? + +In IoT and message middleware scenarios, MQTT is the most commonly used lightweight messaging protocol. The mqtt module enables C++ service programs to easily connect to an MQTT Broker, subscribe to/publish topics, while enjoying the event-driven asynchronous callback model. + +## Header Files + +```cpp +#include +``` + +## Core Classes and Interfaces + +### Client — MQTT Client + +| Method | Description | +|------|------| +| `Client(loop)` | Constructor | +| `initialize(config, callbacks)` | Initialize configuration and callbacks | +| `start()` | Start connection | +| `stop()` | Stop connection | +| `subscribe(topic, mid, qos)` | Subscribe to a topic | +| `unsubscribe(topic, mid)` | Unsubscribe from a topic | +| `publish(topic, payload, size, qos, retain, mid)` | Publish a message | +| `cleanup()` | Cleanup | +| `getState()` | Get current state | + +### State Transitions + +``` +kNone → kInited → kConnecting → kTcpConnected → kMqttConnected + ↓ (disconnected) + kReconnWaiting → kConnecting (auto reconnect) + ↓ (no reconnect needed) + kEnd +``` + +### Config — Configuration + +```cpp +mqtt::Client::Config conf; + +//! Basic configuration +conf.base.broker.domain = "broker.emqx.io"; //! Broker address +conf.base.broker.port = 1883; //! Broker port +conf.base.client_id = "my_client"; //! Client ID +conf.base.username = "user"; //! Username +conf.base.passwd = "pass"; //! Password +conf.base.keepalive = 60; //! Keepalive interval (seconds) + +//! TLS configuration (optional) +conf.tls.enabled = true; +conf.tls.ca_file = "./ca.pem"; + +//! Will message configuration (optional) +conf.will.enabled = true; +conf.will.topic = "/device/offline"; +conf.will.payload = Memblock("offline", 7); + +//! Auto reconnect configuration +conf.auto_reconnect_enable = true; +conf.auto_reconnect_wait_sec_gen_func = [](int fail_count) { + return 1 << std::min(fail_count, 4); //! Exponential backoff: 1, 2, 4, 8, 16... +}; +``` + +### Callbacks — Callback Functions + +```cpp +mqtt::Client::Callbacks cbs; + +cbs.connected = [] { LogInfo("MQTT connected"); }; +cbs.connect_fail = [] { LogErr("MQTT connect fail"); }; +cbs.disconnected = [] { LogInfo("MQTT disconnected"); }; +cbs.message_recv = [](int mid, const std::string &topic, + const void *payload, int size, int qos, bool retain) { + LogInfo("recv topic:%s, data:%.*s", topic.c_str(), size, (char*)payload); +}; +cbs.message_pub = [](int mid) { LogInfo("published, mid=%d", mid); }; +cbs.subscribed = [](int mid, int qos, const int *granted_qos) { LogInfo("subscribed"); }; +cbs.unsubscribed = [](int mid) { LogInfo("unsubscribed"); }; +cbs.state_changed = [](mqtt::Client::State state) { LogInfo("state: %d", state); }; +``` + +## Usage Examples + +### Connecting to an MQTT Broker + +> Full example in `examples/mqtt/conn/` + +```cpp +#include +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + mqtt::Client mqtt(sp_loop); + + mqtt::Client::Config conf; + conf.auto_reconnect_enable = true; + conf.auto_reconnect_wait_sec_gen_func = [](int fail_count) { + return 1 << std::min(fail_count, 4); //! Exponential backoff reconnect + }; + + if (!mqtt.initialize(conf, mqtt::Client::Callbacks())) { + LogErr("init mqtt fail"); + return 0; + } + + mqtt.start(); + + //! Listen for exit signal + auto stop_ev = sp_loop->newSignalEvent(); + SetScopeExitAction([stop_ev] { delete stop_ev; }); + stop_ev->initialize(SIGINT, Event::Mode::kOneshot); + stop_ev->enable(); + stop_ev->setCallback([sp_loop, &mqtt] (int) { + mqtt.stop(); + sp_loop->exitLoop(); + }); + + sp_loop->runLoop(Loop::Mode::kForever); + mqtt.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### Subscribing to a Topic + +> Full example in `examples/mqtt/sub/` + +```cpp +mqtt::Client::Callbacks cbs; +cbs.connected = [&mqtt] { + LogInfo("connected, subscribing..."); + mqtt.subscribe("/sensor/temperature", nullptr, 1); //! QoS=1 +}; +cbs.message_recv = [](int mid, const std::string &topic, + const void *payload, int size, int qos, bool retain) { + LogInfo("topic:%s, payload:%.*s", topic.c_str(), size, (const char*)payload); +}; + +mqtt.initialize(conf, cbs); +``` + +### Publishing a Message + +> Full example in `examples/mqtt/pub/` + +```cpp +mqtt::Client::Callbacks cbs; +cbs.connected = [&mqtt] { + LogInfo("connected, publishing..."); + mqtt.publish("/sensor/temperature", "25.6", 4, 1, false); //! QoS=1, retain=false +}; + +mqtt.initialize(conf, cbs); +``` + +## Common Scenarios + +1. **IoT data reporting**: Devices connect to the Broker and periodically publish sensor data +2. **Message subscription**: Server-side subscribes to topics to receive data reported by devices +3. **Device status monitoring**: Using the Will message mechanism, devices automatically publish offline messages when disconnected +4. **TLS secure connection**: Enable TLS encryption for scenarios with high security requirements +5. **Automatic reconnection on disconnection**: Enable auto_reconnect with an exponential backoff strategy + +## Important Notes + +1. **Auto reconnect strategy**: Use exponential backoff (`1 << min(fail_count, 4)`) to avoid frequent reconnects that waste resources +2. **Memblock will message**: Will message payload uses the `Memblock` type, which supports binary data +3. **Callbacks run in the Loop thread**: All callbacks execute in the main Loop thread — do not perform time-consuming operations in callbacks +4. **Initialization order**: Call initialize() first, then start(); call cleanup() only after stop() +5. **Depends on libmosquitto**: The mosquitto library must be linked at compile time + +## Related Modules + +- **event**: Implements socket I/O and timers based on Loop +- **base**: Provides Memblock, Log macros, etc. +- **network**: Implements underlying connections using SocketFd/TcpConnection diff --git a/documents/modules/mqtt_CN.md b/documents/modules/mqtt_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..cd91589834997c072bffb9ebb9ae7bd15a7590b2 --- /dev/null +++ b/documents/modules/mqtt_CN.md @@ -0,0 +1,198 @@ +# MQTT 客户端模块 (mqtt) + +## 是什么? + +mqtt 模块提供了 MQTT 协议客户端实现,基于 libmosquitto 库封装,与 cpp-tbox 的事件循环无缝集成,支持 TLS 加密连接、遗言配置、自动重连等特性。 + +## 为什么需要它? + +在 IoT 和消息中间件场景中,MQTT 是最常用的轻量级消息协议。mqtt 模块让 C++ 服务程序能方便地连接 MQTT Broker,订阅/发布主题,同时享受事件驱动的异步回调模式。 + +## 头文件 + +```cpp +#include +``` + +## 核心类与接口 + +### Client — MQTT 客户端 + +| 方法 | 说明 | +|------|------| +| `Client(loop)` | 构造 | +| `initialize(config, callbacks)` | 初始化配置和回调 | +| `start()` | 开始连接 | +| `stop()` | 停止连接 | +| `subscribe(topic, mid, qos)` | 订阅主题 | +| `unsubscribe(topic, mid)` | 取消订阅 | +| `publish(topic, payload, size, qos, retain, mid)` | 发布消息 | +| `cleanup()` | 清理 | +| `getState()` | 获取当前状态 | + +### 状态流转 + +``` +kNone → kInited → kConnecting → kTcpConnected → kMqttConnected + ↓ (断连) + kReconnWaiting → kConnecting (自动重连) + ↓ (不需重连) + kEnd +``` + +### Config — 配置 + +```cpp +mqtt::Client::Config conf; + +//! 基础配置 +conf.base.broker.domain = "broker.emqx.io"; //! Broker 地址 +conf.base.broker.port = 1883; //! Broker 端口 +conf.base.client_id = "my_client"; //! 客户端 ID +conf.base.username = "user"; //! 用户名 +conf.base.passwd = "pass"; //! 密码 +conf.base.keepalive = 60; //! 心跳时长(秒) + +//! TLS 配置(可选) +conf.tls.enabled = true; +conf.tls.ca_file = "./ca.pem"; + +//! 遗言配置(可选) +conf.will.enabled = true; +conf.will.topic = "/device/offline"; +conf.will.payload = Memblock("offline", 7); + +//! 自动重连配置 +conf.auto_reconnect_enable = true; +conf.auto_reconnect_wait_sec_gen_func = [](int fail_count) { + return 1 << std::min(fail_count, 4); //! 指数退避:1, 2, 4, 8, 16... +}; +``` + +### Callbacks — 回调函数 + +```cpp +mqtt::Client::Callbacks cbs; + +cbs.connected = [] { LogInfo("MQTT connected"); }; +cbs.connect_fail = [] { LogErr("MQTT connect fail"); }; +cbs.disconnected = [] { LogInfo("MQTT disconnected"); }; +cbs.message_recv = [](int mid, const std::string &topic, + const void *payload, int size, int qos, bool retain) { + LogInfo("recv topic:%s, data:%.*s", topic.c_str(), size, (char*)payload); +}; +cbs.message_pub = [](int mid) { LogInfo("published, mid=%d", mid); }; +cbs.subscribed = [](int mid, int qos, const int *granted_qos) { LogInfo("subscribed"); }; +cbs.unsubscribed = [](int mid) { LogInfo("unsubscribed"); }; +cbs.state_changed = [](mqtt::Client::State state) { LogInfo("state: %d", state); }; +``` + +## 使用示例 + +### 连接 MQTT Broker + +> 完整示例见 `examples/mqtt/conn/` + +```cpp +#include +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + mqtt::Client mqtt(sp_loop); + + mqtt::Client::Config conf; + conf.auto_reconnect_enable = true; + conf.auto_reconnect_wait_sec_gen_func = [](int fail_count) { + return 1 << std::min(fail_count, 4); //! 指数退避重连 + }; + + if (!mqtt.initialize(conf, mqtt::Client::Callbacks())) { + LogErr("init mqtt fail"); + return 0; + } + + mqtt.start(); + + //! 监听退出信号 + auto stop_ev = sp_loop->newSignalEvent(); + SetScopeExitAction([stop_ev] { delete stop_ev; }); + stop_ev->initialize(SIGINT, Event::Mode::kOneshot); + stop_ev->enable(); + stop_ev->setCallback([sp_loop, &mqtt] (int) { + mqtt.stop(); + sp_loop->exitLoop(); + }); + + sp_loop->runLoop(Loop::Mode::kForever); + mqtt.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### 订阅主题 + +> 完整示例见 `examples/mqtt/sub/` + +```cpp +mqtt::Client::Callbacks cbs; +cbs.connected = [&mqtt] { + LogInfo("connected, subscribing..."); + mqtt.subscribe("/sensor/temperature", nullptr, 1); //! QoS=1 +}; +cbs.message_recv = [](int mid, const std::string &topic, + const void *payload, int size, int qos, bool retain) { + LogInfo("topic:%s, payload:%.*s", topic.c_str(), size, (const char*)payload); +}; + +mqtt.initialize(conf, cbs); +``` + +### 发布消息 + +> 完整示例见 `examples/mqtt/pub/` + +```cpp +mqtt::Client::Callbacks cbs; +cbs.connected = [&mqtt] { + LogInfo("connected, publishing..."); + mqtt.publish("/sensor/temperature", "25.6", 4, 1, false); //! QoS=1, retain=false +}; + +mqtt.initialize(conf, cbs); +``` + +## 常见场景 + +1. **IoT 数据上报**:设备连接 Broker,定期 publish 传感器数据 +2. **消息订阅**:服务端 subscribe 主题,接收设备上报数据 +3. **设备状态监控**:利用遗言(Will)机制,设备离线时自动发布离线消息 +4. **TLS 安全连接**:启用 TLS 加密,适用于安全要求高的场景 +5. **断线自动重连**:启用 auto_reconnect,配合指数退避策略 + +## 注意事项 + +1. **自动重连策略**:建议使用指数退避(`1 << min(fail_count, 4)`),避免频繁重连消耗资源 +2. **Memblock 遗言**:遗言 payload 使用 `Memblock` 类型,支持二进制数据 +3. **回调在 Loop 线程执行**:所有回调在主 Loop 线程中执行,不要在回调中做耗时操作 +4. **初始化顺序**:先 initialize(),再 start();stop() 后才能 cleanup() +5. **依赖 libmosquitto**:编译时需要链接 mosquitto 库 + +## 相关模块 + +- **event**:基于 Loop 实现 socket 读写和定时器 +- **base**:提供 Memblock、Log 宏等 +- **network**:使用 SocketFd/TcpConnection 实现底层连接 diff --git a/documents/modules/network.md b/documents/modules/network.md new file mode 100644 index 0000000000000000000000000000000000000000..be02b20d974a061bfb3f45354a85b153752dbb4d --- /dev/null +++ b/documents/modules/network.md @@ -0,0 +1,258 @@ +# Network Communication Module (network) + +## What is it? + +The network module provides TCP/UDP/UART communication capabilities based on the event module, including server-side (TcpServer/TcpAcceptor), client-side (TcpClient/TcpConnector), UDP communication (UdpSocket), serial communication (Uart), and byte stream abstraction (ByteStream). + +## Why do you need it? + +In service-oriented programs, network communication is the most fundamental requirement. However, traditional socket programming requires handling a large amount of detail: fd management, event listening, data buffering, connection management, and more. The network module encapsulates all of these into object-oriented interfaces that integrate seamlessly with the event loop, allowing developers to focus solely on business logic. + +## Header Files + +```cpp +#include //! TCP server +#include //! TCP connection acceptor +#include //! TCP connector (with auto-reconnect) +#include //! TCP client (encapsulates connection + communication) +#include //! TCP connection (low-level) +#include //! UDP socket +#include //! Serial communication +#include //! Byte stream abstract interface +#include //! Buffered fd +#include //! Address wrapper +#include //! Socket fd +#include //! IP address +#include //! DNS request +#include //! Domain name resolution +#include //! Network interface +#include //! Standard I/O stream +``` + +## Core Classes and Interfaces + +### TCP Class Comparison + +Different TCP classes are suited for different scenarios: + +| Class | Use Case | Characteristics | +|------|------|------| +| **TcpServer** | Server side, accepting multiple client connections | Encapsulates Acceptor + multiple Connections, provides client management interface by Token | +| **TcpAcceptor** | Server side, only accepting connections | Only responsible for accepting connections; after obtaining a TcpConnection, you manage it yourself | +| **TcpConnector** | Client side, with auto-reconnect | Supports reconnect strategies and attempt count limits | +| **TcpClient** | Client side, full encapsulation | Encapsulates Connector + Connection, provides ByteStream interface | + +### TcpServer — TCP Server + +| Method | Description | +|------|------| +| `TcpServer(loop)` | Constructor | +| `initialize(bind_addr, backlog)` | Initialize bind address | +| `setConnectedCallback(cb)` | Set new connection callback | +| `setDisconnectedCallback(cb)` | Set disconnect callback | +| `setReceiveCallback(cb, threshold)` | Set receive callback and data threshold | +| `setSendCompleteCallback(cb)` | Set send complete callback | +| `start()` | Start service | +| `send(client, data, size)` | Send data to specified client | +| `disconnect(client)` | Disconnect specified client | +| `stop()` | Stop service | +| `cleanup()` | Clean up resources | + +### TcpClient — TCP Client + +| Method | Description | +|------|------| +| `TcpClient(loop)` | Constructor | +| `initialize(server_addr)` | Initialize server address | +| `setConnectedCallback(cb)` | Set connection success callback | +| `setDisconnectedCallback(cb)` | Set disconnect callback | +| `setAutoReconnect(enable)` | Set auto-reconnect | +| `start()` | Start connection | +| `send(data, size)` | Send data (ByteStream interface) | +| `bind(receiver)` | Bind receiver (pipeline mode) | +| `stop()` | Stop/disconnect | +| `cleanup()` | Clean up | + +### UdpSocket — UDP Socket + +| Method | Description | +|------|------| +| `UdpSocket(loop, broadcast)` | Constructor, specify whether to enable broadcast | +| `bind(addr)` | Bind address | +| `connect(addr)` | Connect to target address | +| `setRecvCallback(cb)` | Set receive callback `(data, size, from_addr)` | +| `send(data, size, to_addr)` | Send data to specified address | +| `send(data, size)` | Send data (requires prior connect) | +| `enable()` / `disable()` | Enable/disable receiving | + +> **Note**: `bind()` and `connect()` cannot be used together. + +### Uart — Serial Communication + +| Method | Description | +|------|------| +| `Uart(loop)` | Constructor | +| `initialize(dev, mode_str)` | Initialize, dev is the device path such as "/dev/ttyS0", mode_str such as "115200 8n1" | +| `initialize(dev, mode)` | Initialize, using Mode struct | +| `send(data, size)` | Send data (ByteStream interface) | +| `bind(receiver)` | Bind receiver | +| `enable()` / `disable()` | Enable/disable | + +Mode struct fields: `baudrate` (default 115200), `data_bit` (k8bits), `parity` (kNoEnd), `stop_bit` (k1bits) + +### SockAddr — Address Wrapper + +```cpp +//! Create address from string +SockAddr addr = SockAddr::FromString("127.0.0.1:12345"); +SockAddr addr = SockAddr::FromString("0.0.0.0:80"); +``` + +### ByteStream — Byte Stream Abstraction + +ByteStream is a unified stream interface that both TcpClient and Uart implement. It supports binding two ByteStreams together to form a data pipeline: + +```cpp +//! Bind UART with TCP Client, serial data is forwarded directly to TCP +uart->bind(tcp_client); +tcp_client->bind(uart); +``` + +## Usage Examples + +### TCP Echo Server + +> Full example at `examples/network/tcp_server/tcp_echo/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; +using namespace tbox::network; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + TcpServer srv(sp_loop); + srv.initialize(SockAddr::FromString("127.0.0.1:12345"), 2); + + //! Echo: send received data back as-is + srv.setReceiveCallback( + [&srv] (const TcpServer::ConnToken &client, Buffer &buff) { + srv.send(client, buff.readableBegin(), buff.readableSize()); + buff.hasReadAll(); + }, 0 + ); + + srv.start(); + + //! Listen for exit signal + auto sp_sig = sp_loop->newSignalEvent(); + SetScopeExitAction([sp_sig] { delete sp_sig; }); + sp_sig->initialize(SIGINT, Event::Mode::kOneshot); + sp_sig->enable(); + sp_sig->setCallback([&] (int) { srv.stop(); sp_loop->exitLoop(); }); + + sp_loop->runLoop(); + srv.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### TCP Client + +> Full example at `examples/network/tcp_client/tcp_echo/` + +```cpp +TcpClient client(sp_loop); +client.initialize(SockAddr::FromString("127.0.0.1:12345")); + +client.setReceiveCallback( + [] (Buffer &buff) { + LogInfo("received: %.*s", (int)buff.readableSize(), (char*)buff.readableBegin()); + buff.hasReadAll(); + }, 0 +); + +client.start(); +client.send("hello", 5); +``` + +### UDP Ping-Pong + +> Full example at `examples/network/udp_socket/ping_pong/` + +```cpp +UdpSocket udp(sp_loop); +udp.bind(SockAddr::FromString("0.0.0.0:12345")); + +udp.setRecvCallback( + [&udp] (const void *data, size_t size, const SockAddr &from) { + LogInfo("recv from %s", from.toString().c_str()); + udp.send("pong", 4, from); //! Reply to sender + } +); +udp.enable(); +``` + +### Serial Communication + +> Full example at `examples/network/uart/uart_tool/` + +```cpp +Uart uart(sp_loop); +uart.initialize("/dev/ttyS0", "115200 8n1"); //! 115200 baud rate, 8 data bits, no parity, 1 stop bit + +uart.setReceiveCallback( + [] (Buffer &buff) { + LogInfo("uart recv: %.*s", (int)buff.readableSize(), (char*)buff.readableBegin()); + buff.hasReadAll(); + }, 0 +); + +uart.enable(); +uart.send("AT\r\n", 4); +``` + +### UART to TCP Bridge + +> Full example at `examples/network/uart/uart_to_uart/` + +```cpp +//! Bidirectional binding: serial data is automatically forwarded to TCP, and vice versa +uart->bind(tcp_client); +tcp_client->bind(uart); +``` + +## Common Scenarios + +1. **Echo Service**: TcpServer receives data and sends it back as-is +2. **Interactive Client**: TcpClient + StdioStream implements an interactive TCP client +3. **UART Bridge**: ByteStream bind forwards serial data to TCP +4. **UDP Communication**: UdpSocket implements broadcast or point-to-point UDP +5. **Auto-reconnect Client**: TcpClient + setAutoReconnect implements automatic reconnect on disconnection + +## Important Notes + +1. **Data threshold**: In `setReceiveCallback(cb, threshold)`, threshold specifies the minimum data amount to trigger the callback; 0 means any received data triggers it immediately +2. **Buffer hasReadAll**: In callbacks, use `buff.hasReadAll()` to mark that all data has been read; otherwise, old data will be received again in the next callback +3. **TcpServer ConnToken**: Clients are identified by Token; the Token becomes invalid after the connection is disconnected +4. **TcpClient auto-reconnect**: After enabling `setAutoReconnect(true)`, disconnection will automatically attempt to reconnect +5. **UDP bind vs connect**: bind and connect cannot be used together; if you need to specify a target address, pass it in send() + +## Related Modules + +- **event**: Implements socket event listening based on FdEvent +- **http**: Implements HTTP service based on TcpServer/TcpAcceptor +- **mqtt**: Implements MQTT protocol based on TcpConnection +- **base**: Provides foundational infrastructure such as Buffer (i.e., util::Buffer), ScopeExit, etc. diff --git a/documents/modules/network_CN.md b/documents/modules/network_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..e4cd133ac562716c9f81933a184779809efbe83c --- /dev/null +++ b/documents/modules/network_CN.md @@ -0,0 +1,258 @@ +# 网络通信模块 (network) + +## 是什么? + +network 模块基于 event 模块提供了 TCP/UDP/UART 通信能力,包括服务端(TcpServer/TcpAcceptor)、客户端(TcpClient/TcpConnector)、UDP 通信(UdpSocket)、串口通信(Uart)以及字节流抽象(ByteStream)。 + +## 为什么需要它? + +在服务型程序中,网络通信是最基础的需求。但传统 socket 编程需要处理大量细节:fd 管理、事件监听、数据缓冲、连接管理等。network 模块将这些封装为面向对象的接口,与事件循环无缝集成,开发者只需关注业务逻辑。 + +## 头文件 + +```cpp +#include //! TCP 服务端 +#include //! TCP 连接接收器 +#include //! TCP 连接器(带自动重连) +#include //! TCP 客户端(封装连接+通信) +#include //! TCP 连接(底层) +#include //! UDP 套接字 +#include //! 串口通信 +#include //! 字节流抽象接口 +#include //! 带缓冲的 fd +#include //! 地址封装 +#include //! 套接字 fd +#include //! IP 地址 +#include //! DNS 请求 +#include //! 域名解析 +#include //! 网络接口 +#include //! 标准 I/O 流 +``` + +## 核心类与接口 + +### TCP 类对比 + +不同的 TCP 类适用于不同场景: + +| 类 | 适用场景 | 特点 | +|------|------|------| +| **TcpServer** | 服务端,接受多个客户端连接 | 封装 Acceptor + 多个 Connection,提供按 Token 管理客户端的接口 | +| **TcpAcceptor** | 服务端,仅接受连接 | 只负责接受连接,得到 TcpConnection 后自行管理 | +| **TcpConnector** | 客户端,带自动重连 | 支持重连策略、尝试次数限制 | +| **TcpClient** | 客户端,完整封装 | 封装 Connector + Connection,提供 ByteStream 接口 | + +### TcpServer — TCP 服务端 + +| 方法 | 说明 | +|------|------| +| `TcpServer(loop)` | 构造 | +| `initialize(bind_addr, backlog)` | 初始化绑定地址 | +| `setConnectedCallback(cb)` | 设置新连接回调 | +| `setDisconnectedCallback(cb)` | 设置断开回调 | +| `setReceiveCallback(cb, threshold)` | 设置接收回调与数据阈值 | +| `setSendCompleteCallback(cb)` | 设置发送完成回调 | +| `start()` | 启动服务 | +| `send(client, data, size)` | 向指定客户端发送数据 | +| `disconnect(client)` | 断开指定客户端 | +| `stop()` | 停止服务 | +| `cleanup()` | 清理资源 | + +### TcpClient — TCP 客户端 + +| 方法 | 说明 | +|------|------| +| `TcpClient(loop)` | 构造 | +| `initialize(server_addr)` | 初始化服务端地址 | +| `setConnectedCallback(cb)` | 设置连接成功回调 | +| `setDisconnectedCallback(cb)` | 设置断开回调 | +| `setAutoReconnect(enable)` | 设置自动重连 | +| `start()` | 开始连接 | +| `send(data, size)` | 发送数据(ByteStream 接口) | +| `bind(receiver)` | 绑定接收端(流水线模式) | +| `stop()` | 停止/断开连接 | +| `cleanup()` | 清理 | + +### UdpSocket — UDP 奆接字 + +| 方法 | 说明 | +|------|------| +| `UdpSocket(loop, broadcast)` | 构造,指定是否启用广播 | +| `bind(addr)` | 绑定地址 | +| `connect(addr)` | 连接目标地址 | +| `setRecvCallback(cb)` | 设置接收回调 `(data, size, from_addr)` | +| `send(data, size, to_addr)` | 发送数据到指定地址 | +| `send(data, size)` | 发送数据(需先 connect) | +| `enable()` / `disable()` | 启用/停用接收 | + +> **注意**:`bind()` 与 `connect()` 不能一起使用。 + +### Uart — 串口通信 + +| 方法 | 说明 | +|------|------| +| `Uart(loop)` | 构造 | +| `initialize(dev, mode_str)` | 初始化,dev 为设备路径,如 "/dev/ttyS0",mode_str 如 "115200 8n1" | +| `initialize(dev, mode)` | 初始化,使用 Mode 结构体 | +| `send(data, size)` | 发送数据(ByteStream 接口) | +| `bind(receiver)` | 绑定接收端 | +| `enable()` / `disable()` | 启用/停用 | + +Mode 结构体字段:`baudrate`(默认115200)、`data_bit`(k8bits)、`parity`(kNoEnd)、`stop_bit`(k1bits) + +### SockAddr — 地址封装 + +```cpp +//! 从字符串创建地址 +SockAddr addr = SockAddr::FromString("127.0.0.1:12345"); +SockAddr addr = SockAddr::FromString("0.0.0.0:80"); +``` + +### ByteStream — 字节流抽象 + +ByteStream 是一个统一的流接口,TcpClient、Uart 都实现了它。支持将两个 ByteStream 绑定形成数据流水线: + +```cpp +//! 将 UART 与 TCP Client 绑定,串口数据直接转发到 TCP +uart->bind(tcp_client); +tcp_client->bind(uart); +``` + +## 使用示例 + +### TCP Echo 服务端 + +> 完整示例见 `examples/network/tcp_server/tcp_echo/` + +```cpp +#include +#include +#include +#include +#include + +using namespace tbox; +using namespace tbox::event; +using namespace tbox::network; + +int main() { + LogOutput_Enable(); + + Loop* sp_loop = Loop::New(); + SetScopeExitAction([sp_loop] { delete sp_loop; }); + + TcpServer srv(sp_loop); + srv.initialize(SockAddr::FromString("127.0.0.1:12345"), 2); + + //! 收到数据后原样回发(Echo) + srv.setReceiveCallback( + [&srv] (const TcpServer::ConnToken &client, Buffer &buff) { + srv.send(client, buff.readableBegin(), buff.readableSize()); + buff.hasReadAll(); + }, 0 + ); + + srv.start(); + + //! 监听退出信号 + auto sp_sig = sp_loop->newSignalEvent(); + SetScopeExitAction([sp_sig] { delete sp_sig; }); + sp_sig->initialize(SIGINT, Event::Mode::kOneshot); + sp_sig->enable(); + sp_sig->setCallback([&] (int) { srv.stop(); sp_loop->exitLoop(); }); + + sp_loop->runLoop(); + srv.cleanup(); + + LogOutput_Disable(); + return 0; +} +``` + +### TCP 客户端 + +> 完整示例见 `examples/network/tcp_client/tcp_echo/` + +```cpp +TcpClient client(sp_loop); +client.initialize(SockAddr::FromString("127.0.0.1:12345")); + +client.setReceiveCallback( + [] (Buffer &buff) { + LogInfo("received: %.*s", (int)buff.readableSize(), (char*)buff.readableBegin()); + buff.hasReadAll(); + }, 0 +); + +client.start(); +client.send("hello", 5); +``` + +### UDP Ping-Pong + +> 完整示例见 `examples/network/udp_socket/ping_pong/` + +```cpp +UdpSocket udp(sp_loop); +udp.bind(SockAddr::FromString("0.0.0.0:12345")); + +udp.setRecvCallback( + [&udp] (const void *data, size_t size, const SockAddr &from) { + LogInfo("recv from %s", from.toString().c_str()); + udp.send("pong", 4, from); //! 回复给发送方 + } +); +udp.enable(); +``` + +### 串口通信 + +> 完整示例见 `examples/network/uart/uart_tool/` + +```cpp +Uart uart(sp_loop); +uart.initialize("/dev/ttyS0", "115200 8n1"); //! 115200波特率,8数据位,无校验,1停止位 + +uart.setReceiveCallback( + [] (Buffer &buff) { + LogInfo("uart recv: %.*s", (int)buff.readableSize(), (char*)buff.readableBegin()); + buff.hasReadAll(); + }, 0 +); + +uart.enable(); +uart.send("AT\r\n", 4); +``` + +### UART 转 TCP 桥接 + +> 完整示例见 `examples/network/uart/uart_to_uart/` + +```cpp +//! 双向绑定,串口数据自动转发到 TCP,反之亦然 +uart->bind(tcp_client); +tcp_client->bind(uart); +``` + +## 常见场景 + +1. **Echo 服务**:TcpServer 接收数据后原样回发 +2. **命令行客户端**:TcpClient + StdioStream 实现交互式 TCP 客户端 +3. **UART 桥接**:ByteStream bind 将串口数据转发到 TCP +4. **UDP 通信**:UdpSocket 实现广播或点对点 UDP +5. **自动重连客户端**:TcpClient + setAutoReconnect 实现断线自动重连 + +## 注意事项 + +1. **数据阈值 (threshold)**:`setReceiveCallback(cb, threshold)` 中 threshold 指定触发回调的最小数据量,0 表示收到任意数据即触发 +2. **Buffer 的 hasReadAll**:回调中使用 `buff.hasReadAll()` 标记已读完数据,否则下次回调会重复收到旧数据 +3. **TcpServer ConnToken**:通过 Token 标识客户端,Token 在连接断开后失效 +4. **TcpClient 断线重连**:`setAutoReconnect(true)` 启用后,断线会自动尝试重连 +5. **UDP bind vs connect**:bind 与 connect 不能一起使用,如需指定目标地址请在 send() 中传入 + +## 相关模块 + +- **event**:基于 FdEvent 实现 socket 事件监听 +- **http**:基于 TcpServer/TcpAcceptor 实现 HTTP 服务 +- **mqtt**:基于 TcpConnection 实现 MQTT 协议 +- **base**:提供 Buffer(即 util::Buffer)、ScopeExit 等基础设施 diff --git a/documents/modules/run.md b/documents/modules/run.md new file mode 100644 index 0000000000000000000000000000000000000000..1f5f455169a376c3a8a8918c6f8a2cd529775309 --- /dev/null +++ b/documents/modules/run.md @@ -0,0 +1,172 @@ +# Module Runner (run) + +## What Is It? + +The run module is a dynamic loader that loads and runs business modules compiled as shared libraries (.so). Business modules only need to export the `RegisterApps` symbol, and the run program specifies which module shared libraries to load via the `-l` or `--load` parameter. + +## Why Do You Need It? + +The run module separates business modules from the startup framework. Business modules are independently compiled into .so files and dynamically loaded and combined through the run program. This allows: +- Different business modules can be independently compiled and deployed +- No need to write a separate main function for each business scenario +- Flexibly combine multiple modules to run together + +## Header File + +The run module itself does not need a header file; it is a startup program implemented in `main.cpp`. Business modules use the header files from the main module. + +```cpp +//! Business modules need: +#include +#include +#include +``` + +## Core Mechanism + +### Running Method + +```bash +# Load a single module +./tbox_run -l echo_server.so + +# Load multiple modules +./tbox_run -l echo_server.so -l nc_client.so + +# Specify module path +./tbox_run --load /path/to/module.so + +# Show help +./tbox_run -h + +# Show version +./tbox_run -v +``` + +### Business Module Export Requirements + +Business .so files need to export the following symbols (consistent with the RegisterApps mechanism of the main module): + +```cpp +//! Must-export symbol +extern "C" +void RegisterApps(tbox::main::Module &apps, tbox::main::Context &ctx) { + apps.add(new MyModule(ctx)); +} + +//! Optional export symbols +std::string GetAppDescribe() { return "echo server module"; } +std::string GetAppBuildTime() { return __DATE__ " " __TIME__; } +void GetAppVersion(int &major, int &minor, int &rev, int &build) { + major = 0; minor = 0; rev = 1; build = 0; +} +``` + +> **Important**: RegisterApps must be exported using `extern "C"`, otherwise dlsym will not be able to find the symbol. + +### Workflow + +```mermaid +flowchart TD + A[Parse -l/--load parameters] --> B[dlopen load .so] + B --> C[dlsym find RegisterApps] + C --> D[Call RegisterApps to register modules] + D --> E[tbox::main::Main run framework] + E --> F[dlclose on program exit] +``` + +## Usage Example + +### Writing a Business Module + +> Complete example at `examples/run/echo_server/` + +```cpp +// echo_server.cpp +#include "echo_server.h" +#include + +namespace echo_server { + +App::App(Context &ctx) : + Module("echo_server", ctx), + server_(new TcpServer(ctx.loop())) +{ } + +App::~App() { CHECK_DELETE_RESET_OBJ(server_); } + +void App::onFillDefaultConfig(Json &cfg) const { + cfg["bind"] = "127.0.0.1:12345"; +} + +bool App::onInit(const tbox::Json &cfg) { + auto js_bind = cfg["bind"]; + if (!js_bind.is_string()) return false; + if (!server_->initialize(SockAddr::FromString(js_bind.get()), 2)) + return false; + server_->setReceiveCallback( + [this] (const TcpServer::ConnToken &client, Buffer &buff) { + server_->send(client, buff.readableBegin(), buff.readableSize()); + buff.hasReadAll(); + }, 0 + ); + return true; +} + +bool App::onStart() { return server_->start(); } +void App::onStop() { server_->stop(); } +void App::onCleanup() { server_->cleanup(); } + +} + +//! Export RegisterApps symbol +extern "C" +void RegisterApps(tbox::main::Module &apps, tbox::main::Context &ctx) { + apps.add(new echo_server::App(ctx)); +} +``` + +### Compiling as a Shared Library + +```makefile +# Makefile example +CXXFLAGS += -fPIC -shared # Compile as shared library +LDFLAGS += -ltbox_network -ltbox_eventx -ltbox_event -ltbox_util -ltbox_base +``` + +### Running + +```bash +# Compile +make + +# Run +./tbox_run -l echo_server.so +``` + +### Other Examples + +- `examples/run/nc_client/` — Command-line TCP client module +- `examples/run/timer_event/` — Timer event module + +## Common Scenarios + +1. **Modular Deployment**: Different business modules are independently compiled into .so files and loaded on demand +2. **Dynamic Extension**: Add new modules at runtime via `-l` parameter without recompiling the main program +3. **Independent Development**: Each module team develops independently, without depending on the main program code +4. **Test Execution**: Load test modules to verify functionality + +## Notes + +1. **extern "C" Export**: RegisterApps must be exported using `extern "C"`, otherwise dlsym cannot find the C++ mangled symbol name +2. **.so Compilation Options**: Must add `-fPIC -shared` compilation options +3. **Library Dependencies**: The .so module needs to link the tbox libraries it depends on (network/eventx/event/util/base etc.) +4. **Load Failure Handling**: The run program prints a warning on load failure but does not terminate; it continues to attempt loading other modules +5. **Module Unloading**: All loaded .so files are automatically dlclosed when the program exits + +## Related Modules + +- **main**: run uses the Main() function of the main module to run the framework; business modules use the Module base class +- **util**: Uses ArgumentParser to parse -l/--load parameters +- **network**: Common business modules use TcpServer/TcpClient and other network components +- **base**: Provides Log, Json and other infrastructure diff --git a/documents/modules/run_CN.md b/documents/modules/run_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..9c4a59ab04567250d1804111442bf9674e829a6f --- /dev/null +++ b/documents/modules/run_CN.md @@ -0,0 +1,172 @@ +# 模块运行器 (run) + +## 是什么? + +run 模块是一个动态加载器,它将编译为动态库(.so)的业务模块加载并运行。业务模块只需导出 `RegisterApps` 符号,run 程序通过 `-l` 或 `--load` 参数指定要加载的模块动态库。 + +## 为什么需要它? + +run 模块让业务模块与启动框架分离。业务模块独立编译为 .so 文件,通过 run 程序动态加载组合。这样: +- 不同业务模块可以独立编译和部署 +- 不需要为每个业务场景编写独立的 main 函数 +- 可以灵活组合多个模块运行 + +## 头文件 + +run 模块本身不需要头文件,它是 `main.cpp` 实现的启动程序。业务模块使用 main 模块的头文件。 + +```cpp +//! 业务模块需要: +#include +#include +#include +``` + +## 核心机制 + +### 运行方式 + +```bash +# 加载单个模块 +./tbox_run -l echo_server.so + +# 加载多个模块 +./tbox_run -l echo_server.so -l nc_client.so + +# 指定模块路径 +./tbox_run --load /path/to/module.so + +# 查看帮助 +./tbox_run -h + +# 查看版本 +./tbox_run -v +``` + +### 业务模块导出要求 + +业务 .so 文件需要导出以下符号(与 main 模块的 RegisterApps 机制一致): + +```cpp +//! 必须导出的符号 +extern "C" +void RegisterApps(tbox::main::Module &apps, tbox::main::Context &ctx) { + apps.add(new MyModule(ctx)); +} + +//!可选导出的符号 +std::string GetAppDescribe() { return "echo server module"; } +std::string GetAppBuildTime() { return __DATE__ " " __TIME__; } +void GetAppVersion(int &major, int &minor, int &rev, int &build) { + major = 0; minor = 0; rev = 1; build = 0; +} +``` + +> **重要**:RegisterApps 必须使用 `extern "C"` 导出,否则 dlsym 无法找到符号。 + +### 工作流程 + +```mermaid +flowchart TD + A[解析 -l/--load 参数] --> B[dlopen 加载 .so] + B --> C[dlsym 查找 RegisterApps] + C --> D[调用 RegisterApps 注册模块] + D --> E[tbox::main::Main 运行框架] + E --> F[程序退出时 dlclose] +``` + +## 使用示例 + +### 编写业务模块 + +> 完整示例见 `examples/run/echo_server/` + +```cpp +// echo_server.cpp +#include "echo_server.h" +#include + +namespace echo_server { + +App::App(Context &ctx) : + Module("echo_server", ctx), + server_(new TcpServer(ctx.loop())) +{ } + +App::~App() { CHECK_DELETE_RESET_OBJ(server_); } + +void App::onFillDefaultConfig(Json &cfg) const { + cfg["bind"] = "127.0.0.1:12345"; +} + +bool App::onInit(const tbox::Json &cfg) { + auto js_bind = cfg["bind"]; + if (!js_bind.is_string()) return false; + if (!server_->initialize(SockAddr::FromString(js_bind.get()), 2)) + return false; + server_->setReceiveCallback( + [this] (const TcpServer::ConnToken &client, Buffer &buff) { + server_->send(client, buff.readableBegin(), buff.readableSize()); + buff.hasReadAll(); + }, 0 + ); + return true; +} + +bool App::onStart() { return server_->start(); } +void App::onStop() { server_->stop(); } +void App::onCleanup() { server_->cleanup(); } + +} + +//! 导出 RegisterApps 符号 +extern "C" +void RegisterApps(tbox::main::Module &apps, tbox::main::Context &ctx) { + apps.add(new echo_server::App(ctx)); +} +``` + +### 编译为动态库 + +```makefile +# Makefile 示例 +CXXFLAGS += -fPIC -shared # 编译为共享库 +LDFLAGS += -ltbox_network -ltbox_eventx -ltbox_event -ltbox_util -ltbox_base +``` + +### 运行 + +```bash +# 编译 +make + +# 运行 +./tbox_run -l echo_server.so +``` + +### 其他示例 + +- `examples/run/nc_client/` — 命令行 TCP 客户端模块 +- `examples/run/timer_event/` — 定时器事件模块 + +## 常见场景 + +1. **模块化部署**:不同业务模块独立编译为 .so,按需加载组合 +2. **动态扩展**:运行时通过 `-l` 参数添加新模块,无需重新编译主程序 +3. **独立开发**:各模块团队独立开发,互不依赖主程序代码 +4. **测试运行**:加载测试模块验证功能 + +## 注意事项 + +1. **extern "C" 导出**:RegisterApps 必须使用 `extern "C"` 导出,否则 dlsym 找不到 C++ 编译后的符号名 +2. **.so 编译选项**:需添加 `-fPIC -shared` 编译选项 +3. **库依赖**:.so 模块需要链接它所依赖的 tbox 库(network/eventx/event/util/base 等) +4. **加载失败处理**:run 程序对加载失败打印警告但不终止,继续尝试加载其他模块 +5. **模块卸载**:程序退出时自动 dlclose 所有已加载的 .so + +## 相关模块 + +- **main**:run 使用 main 模块的 Main() 函数运行框架,业务模块使用 Module 基类 +- **util**:使用 ArgumentParser 解析 -l/--load 参数 +- **network**:常见业务模块使用 TcpServer/TcpClient 等网络组件 +- **base**:提供 Log、Json 等基础设施 diff --git a/documents/modules/terminal.md b/documents/modules/terminal.md new file mode 100644 index 0000000000000000000000000000000000000000..93f303ffd2858a309770f51a7b5e0498de86ffa4 --- /dev/null +++ b/documents/modules/terminal.md @@ -0,0 +1,153 @@ +# Interactive Terminal Module (terminal) + +## What is it? + +The terminal module provides a shell-like interactive command terminal for running programs. Developers or operations personnel can log in via telnet and use commands to instruct the program to execute specified functions, enabling runtime debugging, parameter adjustment, status inspection, and more. + +## Why do you need it? + +Service programs at runtime typically only expose their execution process through log output, with no direct interaction. However, the following scenarios strongly require interactive capability: +- During development, you want the program to perform an action but haven't yet implemented a complete UI +- When the program behaves abnormally, you want to print key information to troubleshoot the issue +- Under unexpected conditions, ops personnel want to adjust runtime parameters without stopping the service + +The terminal design mimics Bash, with commands organized like a filesystem directory tree: + +``` +# tree +|-- dir1 +| |-- dir1_1 +| | |-- async* +| | `-- root(R) +| `-- dir1_2 +| `-- sync* +|-- dir2 +`-- sync* +``` + +It supports common commands such as cd, ls, tree, pwd, history, !n, !-n, !!; it also supports UP/DOWN/LEFT/RIGHT/DELETE/HOME/END key actions. + +## Header Files + +```cpp +#include //! Terminal main class +#include //! Node management interface +#include //! Interaction interface +#include //! Helper functions +#include //! Session management +#include //! Connection management +#include //! Type definitions +``` + +## Core Classes and Interfaces + +### Terminal + +Terminal inherits from TerminalInteract and TerminalNodes, providing both interaction capability and node management capability. + +| Method | Description | +|--------|-------------| +| `Terminal(loop)` | Constructor | +| `createFuncNode(func, help)` | Create a function node | +| `createDirNode(help)` | Create a directory node | +| `deleteNode(token)` | Delete a node | +| `rootNode()` | Get the root node | +| `findNode(path)` | Find a node by path | +| `mountNode(parent, child, name)` | Mount a child node onto a parent directory | +| `umountNode(parent, name)` | Unmount a child node | +| `setWelcomeText(text)` | Set welcome text | + +### Func Callback Type + +```cpp +using Func = std::function &args)>; +``` + +The function node callback receives a Session and an argument list. Through Session you can output text to the client. + +### Verified Telnet Clients + +| Client | Description | +|--------|-------------| +| Windows telnet | Built-in command | +| Linux telnet | Terminal command | +| Putty | telnet connection | +| XShell | telnet connection | +| Tabby | telnet profile (Input mode must be set to Normal) | + +## Usage Examples + +### Using Terminal in a main Module + +> See `examples/terminal/telnetd/` for a complete example + +```cpp +class App : public tbox::main::Module { + public: + App(Context &ctx) : Module("app", ctx) { } + + bool onInit(const Json &cfg) override { + auto term = ctx.terminal(); + + //! Create a function node + auto func_node = term->createFuncNode( + [](const terminal::Session &s, const std::vector &args) { + s.send("Hello from terminal!\r\n"); + }, "say hello" + ); + + //! Create a directory node + auto dir_node = term->createDirNode("demo commands"); + + //! Mount nodes into the directory tree + term->mountNode(term->rootNode(), dir_node, "demo"); + term->mountNode(dir_node, func_node, "hello"); + + //! Set welcome text + term->setWelcomeText("Welcome to my app terminal!\r\n"); + + return true; + } +}; +``` + +### Starting a Telnet Service via Terminal + +> See `examples/terminal/telnetd/` for a complete example + +terminal is typically used together with TcpAcceptor to provide a telnet service: + +```cpp +//! In the main module, Terminal is automatically created by the framework +//! Just create a TcpAcceptor to listen on a port and handle connections +``` + +### Stdio Terminal + +> See `examples/terminal/stdio/` for a complete example + +You can also interact with terminal via standard input/output, without needing telnet: + +```cpp +//! Use StdioStream to connect stdin/stdout to Terminal +``` + +## Common Scenarios + +1. **Runtime debugging**: Create function nodes to print key variable values +2. **Parameter adjustment**: Create function nodes to modify runtime parameters (e.g., log level, timer interval) +3. **Status inspection**: Create function nodes that return system status information +4. **Remote ops**: Connect remotely via telnet and adjust without downtime + +## Important Notes + +1. **Terminal is created by the main framework**: When used in a main module, obtain it via `ctx.terminal()`; no need to create it manually +2. **Node path naming**: Use meaningful English names for ease of command-line input +3. **Callback thread safety**: Terminal callbacks execute in the Loop thread, on the same thread as business logic +4. **Session in Func callbacks**: Use Session.send() to output text to the client; include `\r\n` for line breaks + +## Related Modules + +- **event**: Terminal runs on Loop +- **network**: Provides telnet service connections via TcpAcceptor +- **main**: The framework automatically creates the Terminal object diff --git a/documents/modules/terminal_CN.md b/documents/modules/terminal_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..a026438503beb8fa95c38241fafc7af6d34b524e --- /dev/null +++ b/documents/modules/terminal_CN.md @@ -0,0 +1,153 @@ +# 交互终端模块 (terminal) + +## 是什么? + +terminal 模块提供与运行中程序类似 shell 的交互命令终端。开发或运维人员可通过 telnet 登陆,以命令的方式让程序执行指定函数,实现运行时调试、参数调整、状态查看等功能。 + +## 为什么需要它? + +服务程序运行时通常只能通过日志输出执行过程,无法直接交互。但以下场景非常需要交互能力: +- 开发过程中,想让程序执行某个动作但还没有实现完整的界面 +- 程序异常时,希望打印关键信息排查问题 +- 突发状态下,运维希望不停止服务就调整运行参数 + +terminal 的设计模仿 Bash,命令的组织类似文件系统目录树: + +``` +# tree +|-- dir1 +| |-- dir1_1 +| | |-- async* +| | `-- root(R) +| `-- dir1_2 +| `-- sync* +|-- dir2 +`-- sync* +``` + +支持 cd, ls, tree, pwd, history, !n, !-n, !! 等常用命令;还支持 UP/DOWN/LEFT/RIGHT/DELETE/HOME/END 按键动作。 + +## 头文件 + +```cpp +#include //! 终端主类 +#include //! 结点管理接口 +#include //! 交互接口 +#include //! 辅助函数 +#include //! 会话管理 +#include //! 连接管理 +#include //! 类型定义 +``` + +## 核心类与接口 + +### Terminal + +Terminal 继承了 TerminalInteract 和 TerminalNodes,同时提供交互能力和结点管理能力。 + +| 方法 | 说明 | +|------|------| +| `Terminal(loop)` | 构造 | +| `createFuncNode(func, help)` | 创建函数结点 | +| `createDirNode(help)` | 创建目录结点 | +| `deleteNode(token)` | 删除结点 | +| `rootNode()` | 获取根结点 | +| `findNode(path)` | 根据路径查找结点 | +| `mountNode(parent, child, name)` | 将子结点挂载到父目录 | +| `umountNode(parent, name)` | 卸载子结点 | +| `setWelcomeText(text)` | 设置欢迎文字 | + +### Func 回调类型 + +```cpp +using Func = std::function &args)>; +``` + +函数结点的回调接收 Session 和参数列表。通过 Session 可向客户端输出文字。 + +### 已验证的 Telnet 客户端 + +| 客户端 | 说明 | +|--------|------| +| Windows telnet | 自带命令 | +| Linux telnet | 终端命令 | +| Putty | telnet 连接 | +| XShell | telnet 连接 | +| Tabby | telnet profile(Input mode 要设置为 Normal) | + +## 使用示例 + +### 在 main 模块中使用 Terminal + +> 完整示例见 `examples/terminal/telnetd/` + +```cpp +class App : public tbox::main::Module { + public: + App(Context &ctx) : Module("app", ctx) { } + + bool onInit(const Json &cfg) override { + auto term = ctx.terminal(); + + //! 创建函数结点 + auto func_node = term->createFuncNode( + [](const terminal::Session &s, const std::vector &args) { + s.send("Hello from terminal!\r\n"); + }, "say hello" + ); + + //! 创建目录结点 + auto dir_node = term->createDirNode("demo commands"); + + //! 将结点挂载到目录树 + term->mountNode(term->rootNode(), dir_node, "demo"); + term->mountNode(dir_node, func_node, "hello"); + + //! 设置欢迎文字 + term->setWelcomeText("Welcome to my app terminal!\r\n"); + + return true; + } +}; +``` + +### 通过 Terminal 启动 Telnet 服务 + +> 完整示例见 `examples/terminal/telnetd/` + +terminal 通常配合 TcpAcceptor 提供 telnet 服务: + +```cpp +//! 在 main 模块中,Terminal 已由框架自动创建 +//! 只需创建 TcpAcceptor 监听端口,并处理连接 +``` + +### Stdio 终端 + +> 完整示例见 `examples/terminal/stdio/` + +也可通过标准输入输出与 terminal 交互,无需 telnet: + +```cpp +//! 使用 StdioStream 将 stdin/stdout 连接到 Terminal +``` + +## 常见场景 + +1. **运行时调试**:创建函数结点打印关键变量值 +2. **参数调整**:创建函数结点修改运行参数(如日志级别、定时器间隔) +3. **状态查看**:创建函数结点返回系统状态信息 +4. **远程运维**:通过 telnet 远程连接,无需停机调整 + +## 注意事项 + +1. **Terminal 由 main 框架创建**:在 main 模块中使用时,通过 `ctx.terminal()` 获取,无需手动创建 +2. **结点路径命名**:建议使用有意义的英文名称,便于命令行输入 +3. **回调线程安全**:Terminal 回调在 Loop 线程中执行,与业务逻辑同线程 +4. **Func 回调的 Session**:通过 Session.send() 向客户端输出文字,需包含 `\r\n` 换行 + +## 相关模块 + +- **event**:Terminal 基于 Loop 运行 +- **network**:通过 TcpAcceptor 提供 telnet 服务连接 +- **main**:框架自动创建 Terminal 对象 diff --git a/documents/modules/trace.md b/documents/modules/trace.md new file mode 100644 index 0000000000000000000000000000000000000000..b9e9b0edd02a964d56783797924449ae53a9cf3d --- /dev/null +++ b/documents/modules/trace.md @@ -0,0 +1,114 @@ +# Performance Trace Module (trace) + +## What is it? + +The trace module provides lightweight function-level performance tracing functionality, recording the execution time of functions/events and writing trace data to binary files for subsequent visual analysis. + +## Why do you need it? + +In service applications, understanding which functions execute the slowest and how time is distributed is crucial for performance optimization. The trace module automatically records timestamps and durations at key function entry/exit points without modifying business code, enabling you to obtain detailed performance data. + +![trace-view](../images/0011-trace-view.png) + +## Header Files + +```cpp +#include +``` + +## Core Classes and Interfaces + +### Sink — Trace Data Receiver + +Sink follows the singleton pattern, with a single global instance. + +| Method | Description | +|------|------| +| `Sink::GetInstance()` | Get the singleton instance | +| `setPathPrefix(prefix)` | Set path prefix, e.g. "/data/my_proc", automatically creates a subdirectory with timestamp and PID | +| `setFileSyncEnable(enable)` | Set whether to sync data to disk in real time | +| `setRecordFileMaxSize(size)` | Set the maximum record file size | +| `setFilterStrategy(strategy)` | Set filter strategy (kPermit/kReject) | +| `setFilterExemptSet(exempt_set)` | Set the exempt set | +| `enable()` | Enable tracing | +| `disable()` | Disable tracing | +| `isEnabled()` | Check if tracing is enabled | +| `getDirPath()` | Get the directory path | +| `commitRecord(name, module, line, end_ts, duration)` | Commit a trace record | + +### Directory Structure + +After setting the path prefix, trace automatically creates the following directory structure: + +``` +/data/my_proc.20240525_123300.7723/ +├── names.txt # Function name list (index-encoded) +├── modules.txt # Module name list (index-encoded) +├── threads.txt # Thread name list (index-encoded) +└── records/ # Record file directory + └── 20240530_041046.bin # Binary trace record file +``` + +## Usage Examples + +### Basic Tracing + +> Full example available in `examples/trace/01_demo/` + +```cpp +#include + +//! Enable tracing +auto &sink = tbox::trace::Sink::GetInstance(); +sink.setPathPrefix("/data/my_app_trace"); +sink.enable(); + +//! Commit trace records in key functions +void myFunction() { + uint64_t start_us = /* get start timestamp */; + //! ... execute business logic ... + uint64_t end_us = /* get end timestamp */; + sink.commitRecord("myFunction", "my_module", 0, end_us, end_us - start_us); +} +``` + +### Multi-thread Tracing + +> Full example available in `examples/trace/02_multi_threads/` + +```cpp +//! trace supports multi-thread record commits, thread IDs are automatically encoded +//! Backend thread writes to file asynchronously, does not block business threads +``` + +### Filter Strategy + +```cpp +//! Default strategy: record all modules +sink.setFilterStrategy(tbox::trace::Sink::FilterStrategy::kPermit); + +//! Reverse strategy: reject specific modules only +sink.setFilterStrategy(tbox::trace::Sink::FilterStrategy::kReject); +sink.setFilterExemptSet({"network", "http"}); //! Exempt these modules +//! Effect: only record trace data for network and http modules +``` + +## Common Scenarios + +1. **Performance Analysis**: Trace execution time of key functions, identify slow functions +2. **Bottleneck Discovery**: Analyze time distribution across modules, find performance bottlenecks +3. **Runtime Monitoring**: Continuously trace program execution, generate a complete execution timeline +4. **Selective Tracing**: Use filter strategies to trace only the modules you care about + +## Important Notes + +1. **Singleton Pattern**: Sink is obtained via GetInstance(), globally unique, cannot be manually created +2. **Binary File Format**: Record files are in binary format and require a dedicated viewer tool (such as trace_view) for analysis +3. **Path Prefix**: The prefix set by setPathPrefix() automatically gets a timestamp and PID suffix appended +4. **Real-time Disk Sync**: By default, data is not synced to disk in real time (performance priority); enable via setFileSyncEnable(true) +5. **Filter Strategy Combinations**: kPermit + exempt_set = record all by default, only reject modules in exempt_set; kReject + exempt_set = reject all by default, only record modules in exempt_set + +## Related Modules + +- **util**: Uses AsyncPipe to implement backend thread writing +- **base**: Provides Cabinet, Log and other infrastructure diff --git a/documents/modules/trace_CN.md b/documents/modules/trace_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..ffe1a4c34f20c70e839eb0c17207e94021133b26 --- /dev/null +++ b/documents/modules/trace_CN.md @@ -0,0 +1,114 @@ +# 性能追踪模块 (trace) + +## 是什么? + +trace 模块提供了轻量级函数级性能追踪功能,记录函数/事件的执行时间,将追踪数据写入二进制文件供后续可视化分析。 + +## 为什么需要它? + +在服务程序运行中,了解哪些函数执行最慢、耗时分布如何,对于性能优化至关重要。trace 模块通过在关键函数入口/出口自动记录时间戳和耗时,无需修改业务代码,就能获取详细的性能数据。 + +![trace-view](../images/0011-trace-view.png) + +## 头文件 + +```cpp +#include +``` + +## 核心类与接口 + +### Sink — 追踪数据接收器 + +Sink 是单例模式,全局唯一实例。 + +| 方法 | 说明 | +|------|------| +| `Sink::GetInstance()` | 获取单例实例 | +| `setPathPrefix(prefix)` | 设置路径前缀,如 "/data/my_proc",自动创建带时间戳和PID的子目录 | +| `setFileSyncEnable(enable)` | 设置是否实时落盘 | +| `setRecordFileMaxSize(size)` | 设置记录文件大小上限 | +| `setFilterStrategy(strategy)` | 设置过滤策略(kPermit/kReject) | +| `setFilterExemptSet(exempt_set)` | 设置豁免集合 | +| `enable()` | 启用追踪 | +| `disable()` | 停用追踪 | +| `isEnabled()` | 是否已启用 | +| `getDirPath()` | 获取目录路径 | +| `commitRecord(name, module, line, end_ts, duration)` | 提交一条追踪记录 | + +### 目录结构 + +设置路径前缀后,trace 自动创建如下目录结构: + +``` +/data/my_proc.20240525_123300.7723/ +├── names.txt # 函数名列表(索引编码) +├── modules.txt # 模块名列表(索引编码) +├── threads.txt # 线程名列表(索引编码) +└── records/ # 记录文件目录 + └── 20240530_041046.bin # 二进制追踪记录文件 +``` + +## 使用示例 + +### 基本追踪 + +> 完整示例见 `examples/trace/01_demo/` + +```cpp +#include + +//! 启用追踪 +auto &sink = tbox::trace::Sink::GetInstance(); +sink.setPathPrefix("/data/my_app_trace"); +sink.enable(); + +//! 在关键函数中提交追踪记录 +void myFunction() { + uint64_t start_us = /* 获取开始时间戳 */; + //! ... 执行业务逻辑 ... + uint64_t end_us = /* 获取结束时间戳 */; + sink.commitRecord("myFunction", "my_module", 0, end_us, end_us - start_us); +} +``` + +### 多线程追踪 + +> 完整示例见 `examples/trace/02_multi_threads/` + +```cpp +//! trace 支持多线程提交记录,线程号自动编码 +//! 后端线程异步写入文件,不阻塞业务线程 +``` + +### 过滤策略 + +```cpp +//! 默认策略:记录所有模块 +sink.setFilterStrategy(tbox::trace::Sink::FilterStrategy::kPermit); + +//! 反向策略:只拒绝特定模块 +sink.setFilterStrategy(tbox::trace::Sink::FilterStrategy::kReject); +sink.setFilterExemptSet({"network", "http"}); //! 豁免这些模块 +//! 效果:只记录 network 和 http 模块的追踪数据 +``` + +## 常见场景 + +1. **性能分析**:追踪关键函数的执行耗时,定位慢函数 +2. **瓶颈发现**:统计各模块的耗时分布,找到性能瓶颈 +3. **运行时监控**:持续追踪程序运行过程,生成完整的执行时间线 +4. **选择性追踪**:使用过滤策略仅追踪关心的模块 + +## 注意事项 + +1. **单例模式**:Sink 使用 GetInstance() 获取,全局唯一,不可手动创建 +2. **二进制文件格式**:记录文件为二进制格式,需要专门的查看工具(如 trace_view)分析 +3. **路径前缀**:setPathPrefix() 设置的前缀会自动添加时间戳和PID后缀 +4. **实时落盘**:默认不实时落盘(性能优先),可通过 setFileSyncEnable(true) 启用 +5. **过滤策略组合**:kPermit + exempt_set = 默认记录所有,仅拒绝 exempt_set 中的模块;kReject + exempt_set = 默认拒绝所有,仅记录 exempt_set 中的模块 + +## 相关模块 + +- **util**:使用 AsyncPipe 实现后端线程写入 +- **base**:提供 Cabinet、Log 等基础设施 diff --git a/documents/modules/util.md b/documents/modules/util.md new file mode 100644 index 0000000000000000000000000000000000000000..89b3330a09cedb9f2a7f653c1ec1538ced9fb9e9 --- /dev/null +++ b/documents/modules/util.md @@ -0,0 +1,235 @@ +# Utility Module (util) + +## What is it? + +The util module provides 17+ general-purpose utility components, covering data processing, JSON parsing, serialization, encoding/decoding, process management, argument parsing, and more. These utilities are independent and lightweight, and can be used as needed. + +## Why do you need it? + +In C++ project development, you often need general-purpose utilities that are not in the standard library: binary data buffering, JSON configuration file parsing, command-line argument parsing, data serialization and deserialization, UUID generation, CRC checksums, etc. The util module encapsulates these commonly used utilities in a unified way, avoiding repetitive implementation in every project. + +## Header Files + +```cpp +// Data processing +#include //! Binary buffer + +// JSON utilities +#include //! JSON parsing and field extraction +#include //! JSON deep loading (supports __include__) + +// Serialization +#include //! Serialization/deserialization (big/little endian) + +// Encoding/decoding +#include //! Base64 encoding/decoding +#include //! CRC checksum +#include //! Checksum + +// Variables and arguments +#include //! Variable management object +#include //! Command-line argument parsing + +// Process management +#include //! PID file +#include //! Async pipe +#include //! Execute command +#include //! Filesystem utilities +#include //! fd utilities +#include //! Split command line + +// Others +#include //! UUID generation +#include //! Timestamp +#include //! String utilities +#include //! String conversion +#include //! Scalable integer +``` + +## Core Components + +### Buffer -- Binary Buffer + +Buffer is a read-write separated buffer, supporting append for writing and fetch for reading: + +``` + buffer_ptr_ buffer_size_ + | | + v V + +----+----------------+----------------+ + | | readable bytes | writable bytes | + +----+----------------+----------------+ + ^ ^ + | | + read_index_ write_index_ +``` + +| Method | Description | +|--------|-------------| +| `append(data, size)` | Write data, returns actual written size | +| `fetch(buff, size)` | Read data, returns actual read size | +| `readableSize()` | Readable data size | +| `writableSize()` | Writable space size | +| `readableBegin()` | Readable area start address | +| `writableBegin()` | Writable area start address | +| `hasRead(size)` | Mark size bytes as read | +| `hasReadAll()` | Mark all data as read | +| `hasWritten(size)` | Mark size bytes as written | +| `ensureWritableSize(size)` | Ensure writable space | +| `reset()` | Reset the buffer | +| `shrink()` | Reduce excess capacity | + +> **Note**: Buffer is not thread-safe; external locking is required for multi-threaded use. + +### Json -- JSON Parsing and Field Extraction + +Provides safe JSON field extraction functions: + +```cpp +//! Extract field values from a Json object +bool Get(const Json &js, int &value); +bool Get(const Json &js, std::string &value); +bool GetField(const Json &js, "field_name", int &value); +bool GetField(const Json &js, "field_name", std::string &value); + +//! Check field types +bool HasObjectField(const Json &js, "field"); +bool HasArrayField(const Json &js, "field"); +bool HasStringField(const Json &js, "field"); +bool HasIntegerField(const Json &js, "field"); + +//! Parse JSON file +Json js = json::Load("config.json"); //! Exception-throwing version +bool ok = json::Load("config.json", js); //! Non-throwing version +``` + +### DeepLoader -- JSON Deep Loading + +Supports using `__include__` in JSON files to import other JSON files: + +```json +// main.json +{ + "main.a": 1, + "__include__": ["sub/sub1.json => sub1", "common.json"] +} +``` + +```cpp +Json js = json::LoadDeeply("main.json"); //! Automatically loads referenced files and merges them +``` + +> For a complete example, see `examples/util/json_deep_loader/` + +### ArgumentParser -- Command-line Argument Parsing + +Supports short arguments (-h) and long arguments (--help, --level=6): + +```cpp +bool print_help = false; +int level = 0; + +tbox::util::ArgumentParser parser( + [&](char short_opt, const std::string &long_opt, + ArgumentParser::OptionValue &opt_value) { + if (short_opt == 'h' || long_opt == "help") { + print_help = true; + } else if (short_opt == 'l' || long_opt == "level") { + level = std::stoi(opt_value.get()); + } else { + cerr << "invalid option" << endl; + return false; + } + return true; + } +); + +if (!parser.parse(argc, argv)) + return 0; +``` + +### Serializer / Deserializer -- Serialization + +Supports big/little endian data serialization and deserialization, providing stream-style operations: + +```cpp +std::vector block; +Serializer s(block, Endian::kBig); + +s << uint16_t(0x1234) << int32_t(42) << float(3.14); + +Deserializer d(block.data(), block.size(), Endian::kBig); +uint16_t v1; int32_t v2; float v3; +d >> v1 >> v2 >> v3; +``` + +### Variables -- Variable Management + +Variable management object, supporting hierarchical inheritance (parent lookup): + +```cpp +Variables vars; +vars.define("name", Json("default")); +vars.set("name", Json("new_value")); + +Json value; +vars.get("name", value); //! Look up locally +vars.get("name", value, false); //! Continue lookup from parent + +//! Set parent variable table +vars.setParent(&parent_vars); +``` + +### UUID -- UUID Generation + +```cpp +std::string id = util::UUID::Generate(); //! Generate v4 UUID +``` + +### Base64 -- Base64 Encoding/Decoding + +```cpp +std::string encoded = util::Base64::Encode(data, size); +std::vector decoded = util::Base64::Decode(encoded); +``` + +### CRC -- CRC Checksum + +```cpp +uint16_t crc16 = util::CRC::Calc16(data, size); +uint32_t crc32 = util::CRC::Calc32(data, size); +``` + +### PidFile -- PID File + +Prevents duplicate startup of the same program: + +```cpp +util::PidFile pid_file; +pid_file.setPathPrefix("/var/run/myapp"); //! Automatically creates PID file +pid_file.enable(); +``` + +## Common Scenarios + +1. **Configuration file loading**: Use json::Load or LoadDeeply to read JSON configuration +2. **Command-line arguments**: Use ArgumentParser to parse -h/--help/-l/--level etc. +3. **Binary protocol**: Use Buffer to buffer sent/received data, Serializer to serialize protocol fields +4. **Checksum and encoding**: CRC for data integrity, Base64 for encoding binary data +5. **Prevent duplicate startup**: Use PidFile to ensure only one process instance + +## Important Notes + +1. **Buffer is not thread-safe**: External locking is required for multi-threaded use +2. **Json::Load exceptions**: Load() throws OpenFileError or ParseJsonFileError; LoadDeeply also has DuplicateIncludeError +3. **Serializer byte order**: Pay attention to big/little endian alignment; default is big endian (commonly used for network protocols) +4. **ArgumentParser.get()**: After calling opt_value.get(), the parser will not mistake the value for an argument item +5. **DeepLoader duplicate prevention**: The same file cannot be included twice; it will throw DuplicateIncludeError + +## Related Modules + +- **base**: Provides Json (nlohmann/json) forward declarations and definitions +- **event**: AsyncPipe runs based on Loop +- **flow**: Action uses Variables to store variables +- **main**: Module uses Variables and json to load configuration +- **network**: Uses Buffer as the received data buffer diff --git a/documents/modules/util_CN.md b/documents/modules/util_CN.md new file mode 100644 index 0000000000000000000000000000000000000000..59dd69df7a00fec0d984c57607b04e5e36508ec2 --- /dev/null +++ b/documents/modules/util_CN.md @@ -0,0 +1,235 @@ +# 工具集模块 (util) + +## 是什么? + +util 模块提供了 17+ 个通用工具组件,涵盖数据处理、JSON 解析、序列化、编码解码、进程管理、参数解析等功能。这些工具独立且轻量,可按需使用。 + +## 为什么需要它? + +在 C++ 项目开发中,经常需要一些通用但不在标准库中的工具:二进制数据缓冲、JSON 配置文件解析、命令行参数解析、数据序列化与反序列化、UUID 生成、CRC 校验等。util 模块将这些常用工具统一封装,避免每个项目重复实现。 + +## 头文件 + +```cpp +// 数据处理 +#include //! 二进制缓冲区 + +// JSON 工具 +#include //! JSON 解析与字段提取 +#include //! JSON 深度加载(支持 __include__) + +// 序列化 +#include //! 序列化/反序列化(大端/小端) + +// 编码解码 +#include //! Base64 编解码 +#include //! CRC 校验 +#include //! 校验和 + +// 变量与参数 +#include //! 变量管理对象 +#include //! 命令行参数解析 + +// 进程管理 +#include //! PID 文件 +#include //! 异步管道 +#include //! 执行命令 +#include //! 文件系统工具 +#include //! fd 工具 +#include //! 分割命令行 + +// 其他 +#include //! UUID 生成 +#include //! 时间戳 +#include //! 字串工具 +#include //! 字串转换 +#include //! 可缩放整数 +``` + +## 核心组件 + +### Buffer — 二进制缓冲区 + +Buffer 是一个读写分离的缓冲区,支持 append 写入和 fetch 读取: + +``` + buffer_ptr_ buffer_size_ + | | + v V + +----+----------------+----------------+ + | | readable bytes | writable bytes | + +----+----------------+----------------+ + ^ ^ + | | + read_index_ write_index_ +``` + +| 方法 | 说明 | +|------|------| +| `append(data, size)` | 写入数据,返回实际写入大小 | +| `fetch(buff, size)` | 读取数据,返回实际读取大小 | +| `readableSize()` | 可读数据大小 | +| `writableSize()` | 可写空间大小 | +| `readableBegin()` | 可读区首地址 | +| `writableBegin()` | 可写区首地址 | +| `hasRead(size)` | 标记已读 size 字节 | +| `hasReadAll()` | 标记已读全部数据 | +| `hasWritten(size)` | 标记已写 size 字节 | +| `ensureWritableSize(size)` | 保障可写空间 | +| `reset()` | 重置缓冲区 | +| `shrink()` | 缩减多余容量 | + +> **注意**:Buffer 不是线程安全的,多线程使用需在外部加锁。 + +### Json — JSON 解析与字段提取 + +提供安全的 JSON 字段提取函数: + +```cpp +//! 从 Json 对象中提取字段值 +bool Get(const Json &js, int &value); +bool Get(const Json &js, std::string &value); +bool GetField(const Json &js, "field_name", int &value); +bool GetField(const Json &js, "field_name", std::string &value); + +//! 检查字段类型 +bool HasObjectField(const Json &js, "field"); +bool HasArrayField(const Json &js, "field"); +bool HasStringField(const Json &js, "field"); +bool HasIntegerField(const Json &js, "field"); + +//! 解析 JSON 文件 +Json js = json::Load("config.json"); //! 抛异常版本 +bool ok = json::Load("config.json", js); //! 不抛异常版本 +``` + +### DeepLoader — JSON 深度加载 + +支持在 JSON 文件中使用 `__include__` 导入其他 JSON 文件: + +```json +// main.json +{ + "main.a": 1, + "__include__": ["sub/sub1.json => sub1", "common.json"] +} +``` + +```cpp +Json js = json::LoadDeeply("main.json"); //! 自动加载引用的文件并合并 +``` + +> 完整示例见 `examples/util/json_deep_loader/` + +### ArgumentParser — 命令行参数解析 + +支持短参数(-h)和长参数(--help、--level=6): + +```cpp +bool print_help = false; +int level = 0; + +tbox::util::ArgumentParser parser( + [&](char short_opt, const std::string &long_opt, + ArgumentParser::OptionValue &opt_value) { + if (short_opt == 'h' || long_opt == "help") { + print_help = true; + } else if (short_opt == 'l' || long_opt == "level") { + level = std::stoi(opt_value.get()); + } else { + cerr << "invalid option" << endl; + return false; + } + return true; + } +); + +if (!parser.parse(argc, argv)) + return 0; +``` + +### Serializer / Deserializer — 序列化 + +支持大端/小端的数据序列化与反序列化,提供流式操作: + +```cpp +std::vector block; +Serializer s(block, Endian::kBig); + +s << uint16_t(0x1234) << int32_t(42) << float(3.14); + +Deserializer d(block.data(), block.size(), Endian::kBig); +uint16_t v1; int32_t v2; float v3; +d >> v1 >> v2 >> v3; +``` + +### Variables — 变量管理 + +变量管理对象,支持层级继承(parent 查找): + +```cpp +Variables vars; +vars.define("name", Json("default")); +vars.set("name", Json("new_value")); + +Json value; +vars.get("name", value); //! 从本地查找 +vars.get("name", value, false); //! 从 parent 继续查找 + +//! 设置父变量表 +vars.setParent(&parent_vars); +``` + +### UUID — UUID 生成 + +```cpp +std::string id = util::UUID::Generate(); //! 生成 v4 UUID +``` + +### Base64 — Base64 编解码 + +```cpp +std::string encoded = util::Base64::Encode(data, size); +std::vector decoded = util::Base64::Decode(encoded); +``` + +### CRC — CRC 校验 + +```cpp +uint16_t crc16 = util::CRC::Calc16(data, size); +uint32_t crc32 = util::CRC::Calc32(data, size); +``` + +### PidFile — PID 文件 + +防止同一程序重复启动: + +```cpp +util::PidFile pid_file; +pid_file.setPathPrefix("/var/run/myapp"); //! 自动创建 PID 文件 +pid_file.enable(); +``` + +## 常见场景 + +1. **配置文件加载**:使用 json::Load 或 LoadDeeply 读取 JSON 配置 +2. **命令行参数**:使用 ArgumentParser 解析 -h/--help/-l/--level 等 +3. **二进制协议**:使用 Buffer 缓冲收发数据,Serializer 序列化协议字段 +4. **校验与编码**:CRC 校验数据完整性,Base64 编码二进制数据 +5. **防止重复启动**:使用 PidFile 确保只有一个进程实例 + +## 注意事项 + +1. **Buffer 非线程安全**:多线程使用需在外部加锁 +2. **Json::Load 异常**:Load() 抛 OpenFileError 或 ParseJsonFileError,LoadDeeply 还有 DuplicateIncludeError +3. **Serializer 字节序**:注意大小端对齐,默认大端(网络协议常用) +4. **ArgumentParser.get()**:调用 opt_value.get() 后,解析器不会将该值误认为参数项 +5. **DeepLoader 防重复**:同一文件不能被 include 两次,会抛 DuplicateIncludeError + +## 相关模块 + +- **base**:提供 Json(nlohmann/json)前置声明和定义 +- **event**:AsyncPipe 基于 Loop 运行 +- **flow**:Action 使用 Variables 存储变量 +- **main**:Module 使用 Variables 和 json 加载配置 +- **network**:使用 Buffer 作为接收数据缓冲区