Files
OmniSocketGo/go/change_to_c.md

21 KiB
Raw Permalink Blame History

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)
  1. 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] 前缀来分帧。

  1. 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) — 判断是否业务消息
  1. 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
  1. 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
  1. 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 创建处理线程
  1. 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) — 关闭
  1. 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) — 关闭
  1. interactive.h / interactive.c

对应 Go: cmd/kcppeer/interactive.go

交互式命令行 REPL

  • help / text / file / quit
  • int run_interactive_shell(kcp_client_t *client) — 运行交互循环
  1. 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
  1. 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 或单次发送
  1. 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/
  1. 跨机器 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
  1. 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, ...) 不适合 KCPKCP 必须补齐 ikcp_update/check 调度与 conv 多路复用;纯 UDP 程序族不能省略;latencysummary/HTML chart 本次不迁移Makefile 需要修正链接目标并统一输出到 c/bin/

Public Interfaces

  • 新增二进制:kcpserverkcppeerkcppingudpserverudppeerudppingudprelay
  • 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
  • kcppingudpserverudppeerudppingudprelay 的参数与输出行为对齐当前 Go 入口;udpserver 默认不开 Linux timestamping只有设置 -tx-ts-debug-log 时才启用。
  • 协议层改为内存接口,不再设计 fd 风格 APImessage_t、datagram 编解码、stream frame 编解码、增量 frame feed。
  • 运行时日志层保留当前 JSON 字段和事件名server/hub 继续作为 black-box relay不新增端到端业务事件。
  • 内部网络 API 包括:udp_conn_tkcp_conn_tkcp_listener_tudp_hub_tkcp_hub_tudp_relay_tudp_client_tkcp_client_tKCP hub-to-hub relay 只做库级能力,不新增额外 CLI。

Implementation Changes

  • 目录固定为 c/includec/srcc/cmdc/third_party/{ikcp,cjson}c/binc/README.mdc/Makefile
  • 第三方依赖直接 vendoring 到仓库:ikcp 用于 KCP 核心,cJSON 同时用于协议头、ping payload、运行时日志。
  • 协议规则完全保留:text/file/register/errorServerPeerID8 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=hubC 跑 kcpserver -mode=relayA 用 -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 仓库一致的范围,不额外扩展新的公共命令。