feat: Go转C

This commit is contained in:
2026-03-30 13:52:56 +08:00
parent fd0270084b
commit d5ef84200e
46 changed files with 14830 additions and 1 deletions

465
change_to_c.md Normal file
View File

@@ -0,0 +1,465 @@
OmniSocketGo -> OmniSocketC 转换计划
Context
将现有的 Go 语言实现的 UDP/KCP 传输层项目 (OmniSocketGo) 转换为纯 C 语言项目,运行在 Linux 系统上。
原项目架构A(Jetson) <-> C(relay cloud) <-> D(hub cloud) <-> B(host)
- B <-> DKCP 链路
- D <-> CUDP relay 转发
- C <-> AKCP 链路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 <peer> <message> / file <peer> <path> / 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 <D_IP>:10909
- A 机器: ./kcppeer -id peer-a -server <D_IP>:10909 -relay-via <C_IP>:10909 -inbox-dir inbox/a
- B 机器: ./kcppeer -id peer-b -server <D_IP>:10909 -inbox-dir inbox/b
4. kcpping 测试:
- echo 端: ./kcpping -id peer-a -server <IP>:10909 -echo
- ping 端: ./kcpping -id peer-b -server <IP>: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, ...)` 不适合 KCPKCP 必须补齐 `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文件落盘为 `<from>-<messageID>-<baseFileName>`
- 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` 连 DB 直连 DA/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 仓库一致的范围,不额外扩展新的公共命令。