Files
OmniSocket/README.md
2026-03-20 09:04:24 +08:00

327 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OmniSocket
OmniSocket 当前包含 3 个核心程序:
- `omni_peer`
- `omni_hub`
- `omni_bridge`
三者统一支持 `tcp | udp | kcp` 三种传输协议。
## 构建
本地构建:
```bash
make
```
生成文件:
- `build/omni_peer`
- `build/omni_hub`
- `build/omni_bridge`
Jetson 场景常用 ARM64 交叉编译:
```bash
make arm64
```
生成文件:
- `build/arm64/omni_peer`
- `build/arm64/omni_hub`
- `build/arm64/omni_bridge`
## 程序说明
### `omni_peer`
`omni_peer` 支持两种工作模式:
- `hub` 模式:连接 `hub``bridge`
- `direct` 模式:`peer` 之间直接互连
常见用途:
- 发送文本命令
- 发送文件
- 接收文件
### `omni_hub`
`omni_hub` 是中心注册与转发节点,通常部署在公网服务器或中心网络位置。
主要职责:
- 维护 `client_id -> session`
- 转发 `peer` 之间的 `bind / tunnel / status`
### `omni_bridge`
`omni_bridge` 是桥接节点,用于将远端 `peer` 接入上游 `hub`
主要职责:
- 上游连接 `hub`
- 下游监听本地入口供 `peer` 接入
- 适用于 `A <-> C <-> D <-> B` 这类桥接链路
当前限制:
- 一个 `bridge` 仅支持一个下游 `peer`
- 下游 `peer``-i` 必须与 `bridge -i` 保持一致
- 上下游必须使用同一种协议,不支持协议转换
- 当前实现更接近“单下游、单身份桥接”,而不是通用多租户中继
## 角色说明
- `A`:本地电脑
- `B`Jetson
- `C`:公网 Hub 服务器
- `D`:公网 Bridge 服务器
下面示例中的 `<proto>` 可替换为 `tcp``udp``kcp`
说明:
- 以下 `omni_peer` 示例统一采用长连接交互模式
- 启动后连接会保持,不再使用“传输一次后自动退出”的一次性写法
- 文本和文件传输通过终端中的交互命令完成
- 若启动命令中使用了 `-m``-F`,程序会执行启动动作模式,而不是进入当前 README 使用的交互模式
## 常用参数与命令
| 分类 | 写法 | 含义 |
| --- | --- | --- |
| 启动参数 | `-i <client_id>` | 当前 `peer` 的逻辑身份。Hub 会根据这个 ID 记录 `client_id -> session` 映射,例如 `-i pc` 表示“我是 pc”`-i jetson` 表示“我是 jetson”。 |
| 启动参数 | `-b <peer_id>` | 启动后默认绑定的目标 `peer`。例如 `-b jetson` 表示后续直接输入 `send ...``put ...` 时,默认发给 `jetson`。 |
| 启动参数 | `-d <peer_id>` | 启动动作模式下的显式目标。通常与 `-m``-F` 配合使用,表示把启动时的那条消息或那个文件直接发给指定目标。 |
| 启动参数 | `-o <output_file>` | 本地接收文件时的落盘路径。收到文件后会写入当前机器上的这个路径,例如 `-o /tmp/from_pc.bin`。 |
| 交互命令 | `bind <peer_id>` | 将默认目标切换到指定 `peer`,后续 `send``put` 默认发给它。 |
| 交互命令 | `send <text>` | 向当前默认目标发送一条文本消息。 |
| 交互命令 | `say <peer_id> <text>` | 向指定 `peer` 发送一条文本消息,不修改当前默认目标。 |
| 交互命令 | `put <file>` | 将当前机器上的文件发送给当前默认目标。 |
| 交互命令 | `push <peer_id> <file>` | 将当前机器上的文件发送给指定 `peer`,不修改当前默认目标。 |
| 交互命令 | `show` | 显示当前本地状态,例如 `client_id`、当前绑定目标和输出路径。 |
| 交互命令 | `quit` | 退出当前 `omni_peer` 进程。 |
## 场景 1点对点直传
`A <-> B`
B 端监听:
```bash
./build/omni_peer -M direct -p <proto> -L 9001 -i jetson -o /tmp/from_pc.bin
```
A 端连接:
```bash
./build/omni_peer -M direct -p <proto> -H <B_IP> -P 9001 -i pc -b jetson -o /tmp/from_jetson.bin
```
连接建立后,可在 A 端输入:
```text
send start
put /tmp/input.bin
```
如需反向 `B -> A`,可在 B 端输入:
```text
put /path/to/file.bin
```
## 场景 2通过 Hub 中转
`A <-> C <-> B`
C 端启动 Hub
- C 维护着一张 `client_id -> session` 映射表,用于记录谁是 `pc`、谁是 `jetson`,并据此转发 `bind / tunnel / status`
```bash
./build/omni_hub -p <proto> -P 9002
```
B 端连接 Hub
```bash
./build/omni_peer -p <proto> -H <C_IP> -P 9002 -i jetson -o /tmp/from_pc.bin
```
A 端连接 Hub
```bash
./build/omni_peer -p <proto> -H <C_IP> -P 9002 -i pc -b jetson -o /tmp/from_jetson.bin
```
连接建立后,可在 A 端输入:
```text
send start
put /tmp/input.bin
```
如需反向 `B -> A`,可在 B 端输入:
```text
bind pc
put /path/to/file.bin
```
## 场景 3通过 Bridge 桥接
`A <-> C <-> D <-> B`
C 端启动 Hub
- C 仍然维护 `client_id -> session` 映射表A 以 `pc` 注册到 CBridge 以 `jetson` 这个逻辑身份注册到 C
```bash
./build/omni_hub -p <proto> -P 9003
```
D 端启动 Bridge
```bash
./build/omni_bridge -p <proto> -H <C_IP> -P 9003 -i jetson -L 9004
```
B 端连接 Bridge
```bash
./build/omni_peer -p <proto> -H <D_IP> -P 9004 -i jetson -o /tmp/from_pc.bin
```
A 端连接 Hub
```bash
./build/omni_peer -p <proto> -H <C_IP> -P 9003 -i pc -b jetson -o /tmp/from_jetson.bin
```
连接建立后,可在 A 端输入:
```text
send start
put /tmp/input.bin
```
如需反向 `B -> A`,可在 B 端输入:
```text
bind pc
put /path/to/file.bin
```
说明:
- 该场景已经实现 `A -> C -> D -> B``B -> D -> C -> A` 的桥接转发
- 但当前 `bridge` 仍是单下游、单身份模型,不是完整的多节点桥接网络
## 日志与指标
### 输出位置
- 终端文本日志:默认输出到 `stderr`,格式为 `key=value`
- 结构化日志:默认追加到当前工作目录下的 `omni_logs.jsonl`
- 性能快照统一写在 `component="perf"` 的 JSONL 记录里
- 终端里会额外出现 `component=perf_udp_loss` 文本行;同一批 UDP 丢包字段已经合并写进对应的 `component="perf"` JSONL 行
- 周期性性能快照大约每 `1s` 打一次;进程退出时会再打一条 `tag="final"`
### 当前文档口径
下面这些说法要以当前实现为准,不要再按旧文档理解:
- `processing / queue / transmission / propagation / end_to_end` 都是“当前实现下的本地观测值或估算值”,不是严格意义上的物理链路精确测量值。
- `processing_*` 当前表示本地应用层处理耗时,主要来自文件分片封装、接收端写盘等路径;它不是“某一块硬件 CPU 的完整开销画像”。
- `queue_*``transmission_*` 是基于最近活跃窗口的吞吐和缓冲/队列状态反推出来的估算值。最近没有足够流量样本、样本太小或者当前速率太低时,这两个值可能直接为 `0`
- `propagation_*` 当前来自 `min_rtt_ms / 2` 的估算;如果当前协议没有 RTT 样本,这组字段就是 `0`
- `end_to_end_*` 当前只在“最终接收文件的 peer”上有值来源是发送端分片里的 `origin_ts_ms`。发送文件前会先做一次时钟同步,把发送端时间对齐到接收端时钟域;如果同步没建立,这组字段会保持 `0`,而不是给出误导性的跨机结果。
- UDP 丢包统计只在 `UDP 文件接收侧 peer` 上有值UDP 发送侧、Hub、Bridge 不会产出这组汇总。
- `udp_retrans` 字段当前还没有实现应用层 UDP 重传统计,所以现在始终是 `0`
- `TCP/KCP` 当前记录的是重传次数/重传字节/累计发送分片,不直接记录“重传频率”这个单独字段。
- `send_buffer_pct_*` / `recv_buffer_pct_*` 是占用率风格的指标,但不保证永远严格落在 `0-100`;尤其 KCP 等待队列超过窗口时,理论上可以大于 `100`
- `send_call_* / recv_call_* / proto_* / processing_*` 有时会是 `0`,常见原因不是没统计,而是当前时间分辨率是毫秒,很多本地操作小于 `1ms`
### 基础与吞吐字段
| 文档含义 | JSONL 字段 | 单位 | 当前语义 | 什么时候有值 / 为什么会是 0 |
| --- | --- | --- | --- | --- |
| 时间戳 | `ts_ms` | ms | 这条日志写出的单调时间戳 | 始终有值 |
| 日志分类 | `level` / `component` / `tag` | - | `component="perf"` 表示性能快照,`tag` 常见为 `peer_transport_send``peer_transport_recv``final` | 始终有值 |
| 身份上下文 | `app` / `proto` / `mode` / `role` / `self_id` | - | 程序名、协议、模式、角色、逻辑 ID | `hub``self_id` 为空是正常的 |
| 运行时长 | `elapsed_ms` | ms | 当前进程从启动到本次快照的时长 | 始终有值 |
| 累计发送字节 | `bytes_sent` | bytes | 当前进程累计发送的协议帧总字节 | 无发送时为 `0` |
| 累计接收字节 | `bytes_recv` | bytes | 当前进程累计接收的协议帧总字节 | 无接收时为 `0` |
| 发送次数 | `send_count` | count | 当前进程累计发送帧次数 | 无发送时为 `0` |
| 接收次数 | `recv_count` | count | 当前进程累计接收帧次数 | 无接收时为 `0` |
| 瞬时发送带宽 | `tx_current_mbps` | Mbps | 最近一个统计窗口内的发送速率 | 当前窗口没流量时为 `0` |
| 瞬时接收带宽 | `rx_current_mbps` | Mbps | 最近一个统计窗口内的接收速率 | 当前窗口没流量时为 `0` |
| 平均发送带宽 | `tx_avg_mbps` | Mbps | 进程启动到当前的平均发送速率 | 从未发送时为 `0` |
| 平均接收带宽 | `rx_avg_mbps` | Mbps | 进程启动到当前的平均接收速率 | 从未接收时为 `0` |
| 传输进度字节 | `progress_bytes` | bytes | 当前文件传输已完成字节数 | 没有文件传输时为 `0` |
| 总工作量 | `total_work_bytes` | bytes | 当前文件总大小 | 没有文件传输时为 `0` |
| 传输进度百分比 | `progress_pct` | % | `progress_bytes / total_work_bytes * 100` | 没有文件传输时为 `0` |
### 调用耗时与延迟字段
| 文档含义 | JSONL 字段 | 单位 | 当前语义 | 什么时候有值 / 为什么会是 0 |
| --- | --- | --- | --- | --- |
| 应用层发送调用 | `send_call_last_ms` / `send_call_min_ms` / `send_call_max_ms` / `send_call_avg_ms` | ms | `peer_transport_send()` 调用耗时 | 没有发送,或每次发送都小于 `1ms` 时可能为 `0` |
| 应用层接收调用 | `recv_call_last_ms` / `recv_call_min_ms` / `recv_call_max_ms` / `recv_call_avg_ms` | ms | `peer_transport_next_event()` 接收调用耗时 | 没有接收,或每次接收都小于 `1ms` 时可能为 `0` |
| 协议层发送耗时 | `proto_send_avg_ms` | ms | TCP/UDP/KCP 实际 send 路径耗时 EWMA | 发送很快且小于 `1ms` 时常为 `0` |
| 协议层接收耗时 | `proto_recv_avg_ms` | ms | TCP/UDP/KCP 实际 recv 路径耗时 EWMA | 接收很快且小于 `1ms` 时常为 `0` |
| 本地处理耗时 | `processing_avg_ms` / `processing_min_ms` / `processing_max_ms` | ms | 当前进程内的分片封装、写盘等本地处理耗时 | 仅在文件发送/接收路径上采样;操作太快时可能为 `0` |
| 排队延迟估算 | `queue_avg_ms` / `queue_min_ms` / `queue_max_ms` | ms | 根据发送/接收队列字节数和最近活跃窗口速率估算 | 没有足够活跃样本时为 `0`;小样本/低速率时可能偏保守 |
| 传输延迟估算 | `transmission_avg_ms` / `transmission_min_ms` / `transmission_max_ms` | ms | 根据最近活跃窗口速率估算“这些字节推上链路需要多久” | 没有足够活跃样本时为 `0`;小样本/低速率时可能偏保守 |
| 传播延迟估算 | `propagation_avg_ms` / `propagation_min_ms` / `propagation_max_ms` | ms | 基于 `min_rtt_ms / 2` 的估算 | 当前协议没有 RTT 样本时为 `0` |
| 端到端延迟 | `end_to_end_avg_ms` / `end_to_end_min_ms` / `end_to_end_max_ms` | ms | 发送端分片 `origin_ts_ms` 对齐到接收端时钟后,到接收端处理完成时刻的差值 | 只在最终接收文件的 `peer` 上有值;若时钟同步未建立则为 `0` |
### 可靠性字段
| 文档含义 | JSONL 字段 | 单位 | 当前语义 | 什么时候有值 / 为什么会是 0 |
| --- | --- | --- | --- | --- |
| TCP 重传次数 | `tcp_retrans` | count | 内核 `TCP_INFO` 的累计重传次数 | 仅 TCP 有值;未重传或非 TCP 时为 `0` |
| TCP 数据段数 | `tcp_data_segs_out` | count | TCP 累计发送数据段数 | 仅 TCP 有值;非 TCP 时为 `0` |
| TCP 发送字节 | `tcp_data_bytes_sent` | bytes | TCP 累计发送数据字节 | 仅 TCP 有值;非 TCP 时为 `0` |
| TCP 重传字节 | `tcp_retrans_bytes` | bytes | TCP 累计重传字节 | 仅 TCP 有值;未重传或非 TCP 时为 `0` |
| UDP 重传次数 | `udp_retrans` | count | 预留字段,当前未实现 | 当前始终为 `0` |
| KCP 重传次数 | `kcp_retrans` | count | KCP 内部累计重传分片数 | 仅 KCP 有值;未重传或非 KCP 时为 `0` |
| KCP 数据分片数 | `kcp_data_segs_out` | count | KCP 累计发送分片数 | 仅 KCP 有值;非 KCP 时为 `0` |
| KCP 发送字节 | `kcp_data_bytes_sent` | bytes | KCP 累计发送分片字节 | 仅 KCP 有值;非 KCP 时为 `0` |
| KCP 重传字节 | `kcp_retrans_bytes` | bytes | KCP 累计重传字节 | 仅 KCP 有值;未重传或非 KCP 时为 `0` |
| UDP 预期分片数 | `udp_expected_chunks` | count | UDP 文件接收端预期应收到的总分片数 | 仅 UDP 文件接收侧 `peer` 有值;其他角色为 `0` |
| UDP 实收分片数 | `udp_received_chunks` | count | UDP 文件接收端实际收到的唯一分片数 | 仅 UDP 文件接收侧 `peer` 有值;其他角色为 `0` |
| UDP 丢失分片数 | `udp_lost_chunks` | count | 根据 `seq` 推断的缺失分片数 | 完整收到时为 `0`;非 UDP 接收侧也为 `0` |
| UDP 丢包率 | `udp_loss_rate_pct` | % | `udp_lost_chunks / udp_expected_chunks * 100` | 完整收到时为 `0`;非 UDP 接收侧也为 `0` |
| UDP 连续丢包区间数 | `udp_loss_burst_count` | count | 丢包区间个数 | 无丢包时为 `0` |
| UDP 最大连续丢包长度 | `udp_loss_burst_max_len` | count | 单个丢包区间的最大长度 | 无丢包时为 `0` |
| UDP 丢包区间摘要 | `udp_loss_ranges` | csv string | 例如 `4-6,9,12-13` | 无丢包时为空串 |
| UDP 丢失序号样本 | `udp_loss_seq_sample` | csv string | 最多记录一部分缺失 `seq` 样本 | 无丢包时为空串 |
| UDP 接收窗口分布 | `udp_recv_window_dist` | csv string | `window_id:count`,例如 `0:3,1:28` | 仅 UDP 接收侧有值;没有样本时为空串 |
### 资源与算法字段
| 文档含义 | JSONL 字段 | 单位 | 当前语义 | 什么时候有值 / 为什么会是 0 |
| --- | --- | --- | --- | --- |
| 发送缓冲区占用 | `send_buffer_pct_last` / `send_buffer_pct_avg` / `send_buffer_pct_max` | % 风格值 | Socket 或 KCP 发送队列占用率样本 | 没有缓冲采样时为 `0`KCP 拥塞时可能大于 `100` |
| 接收缓冲区占用 | `recv_buffer_pct_last` / `recv_buffer_pct_avg` / `recv_buffer_pct_max` | % 风格值 | Socket 或 KCP 接收队列占用率样本 | 没有缓冲采样时为 `0` |
| 拥塞窗口 | `cwnd_last` / `cwnd_avg` / `cwnd_max` | 协议窗口大小 | TCP/KCP 当前拥塞窗口样本 | UDP 没有拥塞窗口,因此为 `0` |
| RTT | `last_rtt_ms` / `min_rtt_ms` / `max_rtt_ms` | ms | TCP `TCP_INFO` 或 KCP `rx_srtt` 的 RTT 样本 | UDP 当前没有 RTT 探测,因此为 `0` |
### 实测样本
以下值来自本仓库当前实现的本地回环测试,仅用于说明“字段已经能落到 JSONL 且当前名字是什么”,不是固定性能指标。
| 样本 | 关键字段 | 实测值 |
| --- | --- | --- |
| `TCP` 点对点直传发送端 | `progress_pct` / `cwnd_last` / `last_rtt_ms` / `tcp_data_bytes_sent` | `100` / `10` / `1` / `287984` |
| `UDP` Hub 中转接收端 | `progress_pct` / `udp_expected_chunks` / `udp_received_chunks` / `udp_lost_chunks` / `udp_recv_window_dist` | `100` / `3` / `3` / `0` / `0:3` |
| `KCP` Bridge 桥接节点 | `cwnd_last` / `last_rtt_ms` / `kcp_data_bytes_sent` / `queue_avg_ms` | `2` / `7` / `265` / `33991.342083` |
| `KCP` 最终接收端 | `progress_pct` / `end_to_end_avg_ms` / `cwnd_last` | `100` / `121.333333` / `2` |
### 为什么有些字段“存在但没有值”
| 情况 | 典型字段 | 说明 |
| --- | --- | --- |
| 协议不适用 | `tcp_*` 出现在 UDP/KCP`kcp_*` 出现在 TCP/UDP`cwnd_*` 出现在 UDP | 字段统一保留,便于同一份 JSONL 脚本处理;不适用的协议就写 `0` |
| 还没实现 | `udp_retrans` | 现在没有应用层 UDP 重传,所以这个字段只是预留 |
| 当前角色不产出 | `udp_expected_chunks``udp_loss_*``end_to_end_*` | UDP 丢包统计只在最终接收文件的 UDP peer 上有值;`end_to_end_*` 也主要只在最终接收端有值 |
| 没有采样到 RTT | `last_rtt_ms``propagation_*` | UDP 当前没有 RTT 探针TCP/KCP 只有在拿到对应协议样本后才有值 |
| 没有建立时钟同步 | `end_to_end_*` | 发送端尚未和接收端完成 `TIME_SYNC_*` 探测,或同步结果太旧/未到达 |
| 时间分辨率太粗 | `send_call_*``proto_send_avg_ms``processing_*` | 当前很多路径按毫秒计时,本地回环下大量操作小于 `1ms`,所以会显示 `0` |
| 当前没有任务 | `progress_*` | 只注册但没有文件传输时,这组字段自然是 `0` |