OmniSocketGo -> OmniSocketC 转换计划 Context 将现有的 Go 语言实现的 UDP/KCP 传输层项目 (OmniSocketGo) 转换为纯 C 语言项目,运行在 Linux 系统上。 原项目架构:A(Jetson) <-> C(relay cloud) <-> D(hub cloud) <-> B(host) - B <-> D:KCP 链路 - D <-> C:UDP relay 转发 - C <-> A:KCP 链路(A 通过 relay C 连接到 hub D) - 最终目的:B 和 A 之间双向传输数据 转换要求: - 只保留 UDP 和 KCP,不需要 TCP - 不需要写测试 - 完整实现协议层、传输层、日志事件系统 - Linux only 项目位置 OmniSocketGo/c/ — 作为当前 Go 项目的子目录 项目结构 c/ ├── Makefile ├── README.md ├── include/ │ ├── protocol.h # 协议消息定义 + 编解码 │ ├── transport_kcp.h # KCP 连接封装 │ ├── transport_udp.h # UDP 连接封装(含 Linux timestamping) │ ├── linux_timestamping.h # Linux SO_TIMESTAMPING 底层实现 │ ├── kcp_packet_debug.h # KCP packet-level kernel timestamp debug logger │ ├── kcp_session_stats.h # KCP session stats (RTO/SRTT) logger │ ├── tx_timestamp_debug.h # TX errqueue timestamp debug logger │ ├── server_kcp_hub.h # KCP Hub (D 节点) │ ├── server_udp_relay.h # UDP Relay (C 节点) │ ├── peer_kcp_client.h # KCP Peer Client (A/B 节点) │ ├── latencylog.h # 延迟日志事件系统 │ ├── interactive.h # 交互式命令行 │ └── cJSON.h # JSON 库 (第三方轻量级) ├── src/ │ ├── protocol.c │ ├── transport_kcp.c │ ├── transport_udp.c │ ├── linux_timestamping.c │ ├── kcp_packet_debug.c │ ├── kcp_session_stats.c │ ├── tx_timestamp_debug.c │ ├── server_kcp_hub.c │ ├── server_udp_relay.c │ ├── peer_kcp_client.c │ ├── latencylog.c │ ├── interactive.c │ └── cJSON.c ├── cmd/ │ ├── kcpserver.c # 主程序: KCP Hub 或 UDP Relay │ ├── kcppeer.c # 主程序: KCP Peer (A/B) │ └── kcpping.c # 主程序: KCP Ping 工具 └── third_party/ └── kcp/ ├── ikcp.h # KCP 协议核心实现 (github.com/skywind3000/kcp) └── ikcp.c 依赖说明 - KCP: 使用 skywind3000/kcp 的原始 C 实现 (ikcp.h/ikcp.c),替代 Go 的 xtaci/kcp-go/v5 - JSON: 使用 cJSON (DaveGamble/cJSON) 替代 Go 的 encoding/json - 线程: 使用 pthread 替代 Go goroutine - 同步: 使用 pthread_mutex/pthread_rwlock 替代 Go sync.Mutex/sync.RWMutex 模块实现计划 1. 第三方库集成 - 下载 ikcp.h/ikcp.c (skywind3000/kcp) - 下载 cJSON.h/cJSON.c (DaveGamble/cJSON) 2. protocol.h / protocol.c 对应 Go: cmd/internal/protocol/message.go + codec.go // 消息类型 typedef enum { MSG_TYPE_TEXT = 0, MSG_TYPE_FILE = 1, MSG_TYPE_REGISTER = 2, MSG_TYPE_ERROR = 3, } message_type_t; // 消息结构 typedef struct { message_type_t type; uint64_t id; char from[64]; char to[64]; char file_name[256]; uint8_t *body; int body_len; } message_t; #define MAX_FRAME_SIZE (8 * 1024 * 1024) #define SERVER_PEER_ID "server" 核心函数: - int protocol_encode_message(const message_t *msg, uint8_t **out, int *out_len) — 编码消息为 [4B headerLen][header JSON][body] - int protocol_decode_message(const uint8_t *data, int data_len, message_t *msg) — 解码 - int protocol_write_frame(int fd, const uint8_t *payload, int payload_len) — 写带长度前缀的帧 (用于 KCP stream) - int protocol_read_frame(int fd, uint8_t **payload, int *payload_len) — 读帧 - int protocol_write_message(int fd, const message_t *msg) — 完整编码+写帧 - int protocol_read_message(int fd, message_t *msg) — 读帧+解码 - int protocol_validate_message(const message_t *msg) — 校验 - void message_free(message_t *msg) — 释放 body 内存 注意: KCP session 在 stream 模式下行为类似 TCP,需要 [4B frameLen] 前缀来分帧。 3. latencylog.h / latencylog.c 对应 Go: cmd/internal/latencylog/logger.go // 事件名常量 #define EVENT_A_APP_PREP_BEGIN "A_APP_PREP_BEGIN" #define EVENT_SEND_HANDOFF_BEGIN "send_handoff_begin" #define EVENT_SEND_HANDOFF_END "send_handoff_end" #define EVENT_B_APP_RECV "B_APP_RECV" #define EVENT_B_PERSIST_BEGIN "B_PERSIST_BEGIN" #define EVENT_B_PERSIST_END "B_PERSIST_END" // ... 其他事件 typedef struct { int64_t ts_unix_nano; char node_role[16]; char node_id[64]; char event[32]; message_type_t message_type; uint64_t message_id; char from[64]; char to[64]; char file_name[256]; int body_size; } latency_event_t; typedef struct latency_logger latency_logger_t; 核心函数: - latency_logger_t *latencylog_new_jsonl(const char *path) — 创建 JSONL 文件日志器 - void latencylog_log_event(latency_logger_t *logger, const latency_event_t *event) — 写事件 - void latencylog_log_message_event(latency_logger_t *logger, const char *node_role, const char *node_id, const char *event_name, const message_t *msg) — 为业务消息记事件 - void latencylog_close(latency_logger_t *logger) — 关闭 - int latencylog_is_business_message(const message_t *msg) — 判断是否业务消息 4. transport_kcp.h / transport_kcp.c 对应 Go: cmd/internal/transport/kcp.go + kcp_packet_conn.go KCP 连接封装,底层用 raw ikcp + UDP socket: typedef struct kcp_conn { ikcpcb *kcp; int udp_fd; struct sockaddr_in remote_addr; pthread_mutex_t write_mu; pthread_t recv_thread; // 底层 UDP -> ikcp_input 的线程 latency_logger_t *logger; char node_role[16]; char node_id[64]; int closed; } kcp_conn_t; 核心函数: - kcp_conn_t *kcp_conn_dial(const char *server_addr, const char *bind_ip, const char *bind_device) — 客户端拨号 - kcp_conn_t *kcp_conn_accept(int udp_fd, struct sockaddr_in *remote, uint32_t conv) — 服务端接受 - int kcp_conn_send(kcp_conn_t *conn, const message_t *msg) — 发送消息 - int kcp_conn_receive(kcp_conn_t *conn, message_t *msg) — 接收消息 - void kcp_conn_close(kcp_conn_t *conn) — 关闭 KCP 配置参数(与 Go 版一致): #define KCP_NODELAY 1 #define KCP_INTERVAL 10 #define KCP_RESEND 2 #define KCP_NC 1 #define KCP_WND_SIZE 256 #define KCP_MTU 1400 KCP 底层架构说明: Go 版使用 kcp-go 库,该库内部维护了一个 Listener 来多路复用一个 UDP socket 上的多个 KCP session(通过 conv ID 区分)。在 C 中需要自行实现: - 服务端:一个 UDP socket 监听,一个接收线程读取所有 UDP 包,根据 conv ID 分发到对应的 ikcpcb - 客户端:一个 UDP socket,一个 ikcpcb,一个后台线程负责 UDP recv -> ikcp_input 5. transport_udp.h / transport_udp.c 对应 Go: cmd/internal/transport/udp.go + udp_linux.go typedef struct udp_conn { int fd; struct sockaddr_in peer_addr; syscall_rawconn_t raw; // syscall.RawConn 等价 int linux_timestamping_enabled; latency_logger_t *logger; tx_timestamp_debug_logger_t *tx_debug_logger; uint32_t tx_packet_seq; // pending TX records for errqueue correlation struct udp_tx_pending *pending_tx; char node_role[16]; char node_id[64]; pthread_mutex_t write_mu; } udp_conn_t; 完整实现 Linux SO_TIMESTAMPING: - TX: SOF_TIMESTAMPING_TX_SCHED + SOF_TIMESTAMPING_TX_SOFTWARE + OPT_ID - RX: SOF_TIMESTAMPING_RX_SOFTWARE - errqueue 采集: recvmsg(MSG_ERRQUEUE) 读取 SCM_TIMESTAMPING 控制消息 - TX timestamp debug logger: 记录 send_chunk / errqueue_event 到 JSONL - 对应 Go 文件: udp_linux.go, tx_timestamp_debug.go 同时为 KCP packet conn 实现类似的 timestamping: - 对应 Go 文件: kcp_packet_conn_linux.go, kcp_packet_debug.go - KCP 底层 UDP 包的 TX/RX kernel timestamp 记录 KCP session stats 完整实现: - session-level: conv, RTO, SRTT, SRTTVar 周期采样 - 对应 Go 文件: kcp_session_stats.go 6. server_kcp_hub.h / server_kcp_hub.c 对应 Go: cmd/internal/server/kcp_hub.go typedef struct { pthread_rwlock_t lock; // peer_id -> kcp_conn_t* 的哈希表 struct peer_entry *peers; // 简单链表或哈希表 int peer_count; latency_logger_t *logger; // relay 相关 int relay_udp_fd; struct sockaddr_in relay_peer_addr; int relay_peer_known; } kcp_hub_t; 核心函数: - kcp_hub_t *kcp_hub_new(latency_logger_t *logger) — 创建 hub - int kcp_hub_serve_session(kcp_hub_t *hub, kcp_conn_t *conn) — 处理新会话(注册 + 转发循环) - void kcp_hub_set_relay(kcp_hub_t *hub, int udp_fd, struct sockaddr_in *peer_addr) — 配置 relay - int kcp_hub_serve_relay(kcp_hub_t *hub) — relay 接收循环 - void kcp_hub_free(kcp_hub_t *hub) — 释放 服务端 KCP listener 实现: - 主 UDP socket 监听 - 收到新 conv ID 时创建新 ikcpcb - 用 pthread 为每个 session 创建处理线程 7. server_udp_relay.h / server_udp_relay.c 对应 Go: cmd/internal/server/udp_relay.go typedef struct { int downstream_fd; // 监听端 int upstream_fd; // 连接到 hub D 的 UDP struct sockaddr_in upstream_addr; struct sockaddr_in client_addr; int client_known; pthread_mutex_t lock; } udp_relay_t; 核心函数: - udp_relay_t *udp_relay_new(int listen_fd, struct sockaddr_in *upstream_addr) — 创建 - int udp_relay_serve(udp_relay_t *relay) — 双向转发循环(两个线程) - void udp_relay_close(udp_relay_t *relay) — 关闭 8. peer_kcp_client.h / peer_kcp_client.c 对应 Go: cmd/internal/peer/kcp_client.go + persist.go typedef struct { char id[64]; kcp_conn_t *conn; latency_logger_t *logger; uint64_t next_msg_id; // atomic pthread_mutex_t id_mu; } kcp_client_t; 核心函数: - kcp_client_t *kcp_client_dial(const char *server_addr, const char *peer_id, ...) — 连接并注册 - int kcp_client_send_text(kcp_client_t *c, const char *to, const char *text) — 发文本 - int kcp_client_send_file(kcp_client_t *c, const char *to, const char *path) — 发文件 - int kcp_client_receive(kcp_client_t *c, message_t *msg) — 接收 - int kcp_client_persist_message(kcp_client_t *c, const message_t *msg, const char *inbox_dir) — 持久化 - void kcp_client_close(kcp_client_t *c) — 关闭 9. interactive.h / interactive.c 对应 Go: cmd/kcppeer/interactive.go 交互式命令行 REPL: - help / text / file / quit - int run_interactive_shell(kcp_client_t *client) — 运行交互循环 10. cmd/kcpserver.c 对应 Go: cmd/kcpserver/main.go 用法: kcpserver -listen 0.0.0.0:10909 # hub 模式 kcpserver -mode relay -listen 0.0.0.0:10909 -relay-remote 172.21.32.15:10909 # relay 模式 - 解析命令行参数 (getopt) - hub 模式:创建 KCP listener -> 接受连接 -> kcp_hub_serve_session - relay 模式:创建 UDP relay -> udp_relay_serve 11. cmd/kcppeer.c 对应 Go: cmd/kcppeer/main.go 用法: kcppeer -id peer-a -server 172.21.32.15:10909 -relay-via 106.55.173.235:10909 -inbox-dir inbox/a kcppeer -id peer-b -server 81.70.156.140:10909 -inbox-dir inbox/b - 连接到 KCP server - 启动接收线程 - 运行交互式 shell 或单次发送 12. cmd/kcpping.c 对应 Go: cmd/kcpping/main.go + platform_linux.go KCP ping 工具: - ping 模式: 发 JSON payload, 计算 RTT - echo 模式: 回弹文本消息 - 统计: min/avg/max/p50/p95/p99/stddev KCP session 多路复用实现(核心难点) Go 版的 kcp-go 库在一个 UDP socket 上透明地多路复用多个 KCP session。C 版需要手动实现: typedef struct kcp_listener { int udp_fd; pthread_t recv_thread; pthread_mutex_t sessions_lock; // conv -> kcp_session 的哈希表 struct kcp_session_entry *sessions; // 新会话通知队列 kcp_conn_t **accept_queue; int accept_queue_head, accept_queue_tail, accept_queue_cap; pthread_mutex_t accept_lock; pthread_cond_t accept_cond; } kcp_listener_t; - kcp_listener_t *kcp_listen(const char *addr, const char *bind_device) — 创建 listener - kcp_conn_t *kcp_accept(kcp_listener_t *listener) — 阻塞等待新会话 - 内部 recv_thread 循环读 UDP 包,解析前 4 字节 conv ID,分发到对应 ikcpcb - 未知 conv ID 时创建新 session 并放入 accept_queue 编译 CC = gcc CFLAGS = -Wall -Wextra -O2 -pthread -D_GNU_SOURCE LDFLAGS = -lpthread SRCS = src/protocol.c src/transport_kcp.c src/transport_udp.c \ src/server_kcp_hub.c src/server_udp_relay.c \ src/peer_kcp_client.c src/latencylog.c src/interactive.c \ src/cJSON.c third_party/kcp/ikcp.c all: kcpserver kcppeer kcpping kcpserver: cmd/kcpserver.c $(SRCS) $(CC) $(CFLAGS) -Iinclude -Ithird_party/kcp -o $@ $^ $(LDFLAGS kcppeer: cmd/kcppeer.c $(SRCS) $(CC) $(CFLAGS) -Iinclude -Ithird_party/kcp -o $@ $^ $(LDFLAGS kcpping: cmd/kcpping.c $(SRCS) $(CC) $(CFLAGS) -Iinclude -Ithird_party/kcp -o $@ $^ $(LDFLAGS 验证方法 1. 编译: make all 无错误无警告 2. 单机测试: - 启动 hub: ./kcpserver -listen 0.0.0.0:10909 - 启动 peer-a: ./kcppeer -id peer-a -server 127.0.0.1:10909 -inbox-dir inbox/a - 启动 peer-b: ./kcppeer -id peer-b -server 127.0.0.1:10909 -inbox-dir inbox/b - peer-b shell 中: text peer-a hello - 验证 peer-a 收到消息并落盘到 inbox/a/ 3. 跨机器 relay 测试: - D 机器: ./kcpserver -listen 0.0.0.0:10909 - C 机器: ./kcpserver -mode relay -listen 0.0.0.0:10909 -relay-remote :10909 - A 机器: ./kcppeer -id peer-a -server :10909 -relay-via :10909 -inbox-dir inbox/a - B 机器: ./kcppeer -id peer-b -server :10909 -inbox-dir inbox/b 4. kcpping 测试: - echo 端: ./kcpping -id peer-a -server :10909 -echo - ping 端: ./kcpping -id peer-b -server :10909 -to peer-a -count 20 -interval 100 实现顺序 1. 集成第三方库 (ikcp, cJSON) 2. protocol 模块 (消息编解码) 3. latencylog 模块 (日志事件) 4. transport_kcp 模块 (KCP 连接 + listener 多路复用) 5. transport_udp 模块 (UDP 连接,简化 timestamping) 6. server_udp_relay 模块 (C 节点 relay) 7. server_kcp_hub 模块 (D 节点 hub) 8. peer_kcp_client 模块 (A/B 节点 peer + persist) 9. interactive 模块 (交互 shell) 10. cmd/kcpserver.c 主程序 11. cmd/kcppeer.c 主程序 12. cmd/kcpping.c 主程序 13. Makefile + README 14. 编译测试 简化决策 - 不实现 TCP 传输: 去除 transport/tcp.go, server/hub.go(TCP版), peer/client.go(TCP版) 等 TCP 相关代码 - 不写测试: 去除所有 _test.go 对应的测试代码 - 完整实现 Linux timestamping: 完整移植 SO_TIMESTAMPING 的 TX/RX timestamp 采集,包括 errqueue TX sched/software timestamp 和 RX software timestamp,以及对应的 debug logger (KCPPacketDebugLogger, TXTimestampDebugLogger) - 完整实现 KCP session stats: 包括 session-level RTO/SRTT 采样和 JSONL 记录 - 不实现 latency summary/chart: 不实现 latencysummary 工具和 HTML chart 生成(这是离线分析工具,不属于核心传输功能) - peer 哈希表: 使用简单链表实现,hub 连接数不多时性能足够 # OmniSocketGo -> OmniSocketC 全量 UDP/KCP 迁移计划 ## Summary - 在仓库新增 `c/` 子项目,作为 Linux-only、C11、`make` 驱动的独立实现;现有 Go 项目保留不动,作为行为对照。 - 迁移范围按“全量 Go 对齐,但去掉 TCP 和离线 summary/chart”执行:保留 UDP/KCP 协议、纯 UDP 程序族、KCP 程序族、运行时 JSONL 日志、Linux timestamping、KCP packet debug、KCP session stats、以及 KCP hub-to-hub 内部 relay 能力。 - 你当前草案需要修正的关键点有 5 个:`protocol_*frame(int fd, ...)` 不适合 KCP;KCP 必须补齐 `ikcp_update/check` 调度与 conv 多路复用;纯 UDP 程序族不能省略;`latencysummary`/HTML chart 本次不迁移;Makefile 需要修正链接目标并统一输出到 `c/bin/`。 ## Public Interfaces - 新增二进制:`kcpserver`、`kcppeer`、`kcpping`、`udpserver`、`udppeer`、`udpping`、`udprelay`。 - `kcpserver` 保留当前 Go 旗标语义:`-mode=hub|relay`、`-listen`、`-bind-device`、`-relay-remote`、deprecated relay aliases、`-latency-log`、`-kcp-ts-debug-log`、`-kcp-session-stats-log`、`-kcp-session-stats-interval`。 - `kcppeer` 保留当前 Go 旗标语义:`-id`、`-server`、`-relay-via`、`-to`、`-text`、`-file`、`-bind-ip`、`-bind-device`、`-inbox-dir`、`-interactive`、`-latency-log`、`-kcp-ts-debug-log`、`-kcp-session-stats-log`、`-kcp-session-stats-interval`。 - `kcpping`、`udpserver`、`udppeer`、`udpping`、`udprelay` 的参数与输出行为对齐当前 Go 入口;`udpserver` 默认不开 Linux timestamping,只有设置 `-tx-ts-debug-log` 时才启用。 - 协议层改为内存接口,不再设计 fd 风格 API:`message_t`、datagram 编解码、stream frame 编解码、增量 frame feed。 - 运行时日志层保留当前 JSON 字段和事件名;server/hub 继续作为 black-box relay,不新增端到端业务事件。 - 内部网络 API 包括:`udp_conn_t`、`kcp_conn_t`、`kcp_listener_t`、`udp_hub_t`、`kcp_hub_t`、`udp_relay_t`、`udp_client_t`、`kcp_client_t`;KCP hub-to-hub relay 只做库级能力,不新增额外 CLI。 ## Implementation Changes - 目录固定为 `c/include`、`c/src`、`c/cmd`、`c/third_party/{ikcp,cjson}`、`c/bin`、`c/README.md`、`c/Makefile`。 - 第三方依赖直接 vendoring 到仓库:`ikcp` 用于 KCP 核心,`cJSON` 同时用于协议头、ping payload、运行时日志。 - 协议规则完全保留:`text/file/register/error`、`ServerPeerID`、`8 MiB` 限制、UTF-8 校验、`file_name` 约束、`register/error` 来源与目标约束。 - 线上 wire format 完全保留:UDP datagram 为 `[4B headerLen][header JSON][body]`;KCP stream 为 `[4B frameLen][4B headerLen][header JSON][body]`。 - inbox 持久化完全保留:文本追加写 `messages.log` JSONL;文件落盘为 `--`。 - UDP 传输层实现 connected/unconnected 两种发送模式,保留 register/forward 消息收发、Linux SO_TIMESTAMPING、TX errqueue 关联、JSONL debug 记录。 - KCP 客户端连接采用“一连接一 UDP socket + 一 `ikcpcb` + 一接收线程 + 一 update 线程 + 一阻塞接收缓冲区/条件变量”模型。 - KCP 服务端监听采用“单 listener UDP socket + 单 listener RX 线程 + conv->session 表 + accept 队列”模型;每个 session 拥有自己的 `ikcpcb`、update 线程、接收缓冲区和关闭状态,发送通过 listener 共享 socket 和写锁完成。 - `kcpserver` 的 relay 模式保持为原始 UDP 端口转发,不解码协议;`udprelay` 同样保持透明字节转发。 - 纯 UDP hub、KCP hub、双 peer、双 ping 工具、两套 interactive shell 全部对齐现有 Go 行为。 - KCP hub 保留“先本地投递,再尝试 relay”的策略;未知目标、重复注册、已注册 peer 再发 `register/error`、过大 relay 消息等错误路径全部保留。 - Linux 观测能力完整迁移:业务事件 JSONL、UDP TX debug、KCP packet debug、KCP session/process stats;不迁移 `latencysummary` 与 HTML chart。 ## Acceptance - 在 Linux 上执行 `make` 能无缺失符号地构建 7 个二进制,并输出到 `c/bin/`。 - 纯 UDP 冒烟通过:`udpserver` + 两个 `udppeer` 可双向收发文本和文件,`udpping` 的 echo/ping 正常。 - 单 hub KCP 冒烟通过:`kcpserver` + 两个 `kcppeer` 可双向收发文本和文件,`kcpping` 的 echo/ping 正常。 - README 目标拓扑通过:D 跑 `kcpserver -mode=hub`,C 跑 `kcpserver -mode=relay`,A 用 `-relay-via C` 连 D,B 直连 D,A/B 双向传输正常。 - 全量 Go 对齐场景通过:两个 KCP hub 通过内部 raw UDP relay API 互通,跨 hub 文本、文件、错误回送行为与当前 Go 一致。 - 负路径通过:重复注册被拒、未注册 UDP sender 被拒、未知目标返回 `error`、已注册 peer 发送 `register/error` 被拒、oversize relayed message 在实际 `WriteTo` 前被拒、`bind-ip`/`bind-device` 非法值在启动时失败。 - 打开任一日志旗标后,生成的 JSONL 记录字段名、事件名、时间戳语义与现有运行时日志一致,并在 Linux 支持的情况下出现非零 kernel timestamps。 ## Assumptions - 默认编译器为 `gcc`/`clang`,编译参数基线为 `-std=c11 -Wall -Wextra -O2 -pthread -D_GNU_SOURCE`。 - 本次不迁移任何 Go 测试文件,也不为 C 版编写自动化测试;验证仅靠 Linux 构建和手工场景回归。 - 本次不迁移 TCP 入口,也不迁移 `latencysummary`/HTML chart。 - hub-to-hub relay 在 C 版中实现为内部库能力,保持与当前 Go 仓库一致的范围,不额外扩展新的公共命令。