moonkcp

    MoonKCP — KCP 协议 MoonBit 纯实现(零 IO 零时钟纯状态机 ARQ 核心 + 会话壳 + async UDP adapter + 多会话服务端;C 参照差分对拍逐字节等价)

    kcp
    udp
    arq
    reliable-transport
    concurrency
    Download zip
    Author
    Version
    0.2.0
    License
    MulanPSL-2.0
    Last updated
    9 days ago
    Downloads
    51

    #MoonKCP

    thy1016/moonkcp 是 KCP 协议的 MoonBit 纯实现:以 UDP 为底座的可靠有序 ARQ 传输库。协议核心为零 IO、零时钟的纯状态机,毫秒由调用方注入,帧由 调用方搬运;实现与 skywind3000/kcp C 参照保持差分对拍逐字节等价。

    包结构:thy1016/moonkcp(协议核心与会话面)+ thy1016/moonkcp/adapter (async UDP 适配器,native 门)。

    #特性

    • 协议核心:ACK 结算、选择性与超时重传、快速重传、窗口探针、拥塞门限, 全量直译 C 参照。
    • 会话壳 KcpSession:消息分片与碎片合并、发送水位背压、FIFO 收件队列。
    • 多会话服务端 KcpServer:按报文头 conv 路由、未知会话自动建立、 会话数上限护栏、闲置回收。
    • UDP 适配器:KcpUdpPeer(双端直连)与 KcpUdpHub(单 socket 多会话 组合宿主)。
    • 观测面 Kcp::stats:RTT、RTO、拥塞窗、队列读数、接收守恒账。
    • FEC 前向纠错层 fec/(可选):Reed-Solomon 系统码(GF(2^8)、Cauchy 阵 MDS),组内 ≤m 丢失当场恢复不等重传;MdsCodec 端口可换编码器, FecSession 组合壳一键接入。
    • 差分对拍:与 C 参照在确定性丢包、乱序、停摆场景下帧转录逐字节一致。

    #安装

    确保环境中已安装 MoonBit 工具链,然后在模块根目录:

    moon add thy1016/moonkcp

    协议核心在消费包的 moon.pkg import 块中添加 "thy1016/moonkcp", 代码内以 @moonkcp 前缀使用;UDP 适配器另加 "thy1016/moonkcp/adapter"(@adapter 前缀)。

    #快速上手

    #协议核心(零 IO 零时钟)

    宿主持有对端实例,按不大于 interval 的间隔注入当前毫秒并搬运帧:

    let sender = @moonkcp.Kcp::create(1)
    let receiver = @moonkcp.Kcp::create(1)
    sender.set_nodelay(1, 10, 2, 1)
    let _ = sender.send(payload)
    let frames = sender.update(now_ms)
    for frame in frames {
    let _ = receiver.input(frame)
    }
    match receiver.recv() {
    Some(msg) => // 整消息(碎片已合并)
    None => () // 暂无就绪消息
    }

    #会话壳

    let session = @moonkcp.KcpSession::new(1, 128)
    let n = session.send_message(payload) // 窗口满返回 -2(背压)
    let code = session.on_datagram(datagram)
    let frames = session.tick(now_ms)

    #UDP 适配器

    let hub = @adapter.KcpUdpHub::listen(128, 1024) // 服务端:多会话
    let peer = @adapter.KcpUdpPeer::connect_client(7, 128, hub.local_addr())

    可运行示例见 examples/(echo 服务端与对打客户端)。

    #FEC 前向纠错(可选层)

    FecSession 组合 KcpSession 与 FEC 发/收机,应用面 API 一一透传; FEC 关闭 = 直接用 KcpSession(核零沾):

    let session = @moonkcp.FecSession::new(1, 128, 3, 2) // conv, 背压水位, k, m
    let n = session.send_message(payload)
    let wire = session.tick(now_ms) // 组满同突发附带冗余帧
    let code = session.on_datagram(frame) // 线帧进;恢复对应用透明
    match session.poll_message() {
    Some(msg) => // 整消息
    None => ()
    }

    • 线格式:12B 自描述头(group_id / shard_index / k / m / payload_len), 逐组参数由发侧单边拍板、收侧零协商(合同 = domain/fec_dialect.toml)。
    • 语义:数据帧即刻透传(零排队延时);组齐 k 片当场恢复补投;尾组不满 k 不闭组,>m 丢失回落 ARQ 兜底。
    • 开销:带宽冗余 m/(k+m)——(3,2) 为 40%,(10,3) 为 23%;CPU 读数 moon run workspace/fec_bench --release(RS(10,3)/1200B 片:编码 ~100 MB/s、最坏恢复 ~45 MB/s、层直通 ~9 MB/s,百 KB/s 量级视频流 占 CPU 不足 1%)。
    • 六边形:MdsCodec 端口注入,RS 仅为缺省适配器;编解码器工厂逐组 实例化(k/m 随帧头自描述),换码 = 换适配器,层机器零改动。

    #API 总览

    入口作用
    Kcp::create / set_nodelay / set_wnd / set_mtu实例创建与配置
    Kcp::send / recv / peeksize消息下发(分片)、整消息读取、待读长度
    Kcp::input / update / check报文结算、时间推进取帧、下次推进时刻
    Kcp::stats / waitsnd / is_dead观测快照、待确认数、链路判死
    KcpSession::send_message / poll_message会话级收发(背压 / FIFO)
    KcpServer::on_datagram / tick / sweepconv 路由、全会话推进、闲置回收
    @adapter.KcpUdpPeer双端直连适配器
    @adapter.KcpUdpHub单 socket 多会话组合宿主
    @fec.FecSessionFEC 会话组合壳(透传 + 组恢复)
    @fec.FecTx / FecRx / RsCodec / MdsCodecFEC 发/收机与可换编码端口

    #错误面

    • Kcp::input:-1 长度不足或 conv 不符;-2 声明长度超出报文余量; -3 非法命令字节。
    • KcpSession::send_message:-2 发送窗口满(背压拒收);成功返回 接受字节数。
    • KcpServer::on_datagram:-4 会话表满,报文弃置。
    • Kcp::recv:None 表示队列为空或碎片未齐。

    #设计边界

    • 仅消息模式;KCP 的 stream 追加模式不提供。
    • 协作式单运行时内使用,非线程安全;跨线程事件须经运行时进入。
    • 协议核心全后端可移植;适配器因原生 socket 限 native 后端。
    • FEC 层为核外可选层:不接入即不存在;KCP 差分对拍门与 FEC 门互不触及。
    • 吞吐扩展按实测判据重估,当前未排期。

    定位与演进的完整说明见 spec.md。

    #测试与验证

    moon test --target native node workspace/async_watchdog.mjs node workspace/diff_gate.mjs node workspace/bench.mjs

    实测读数(2026-10-25):

    面读数
    单元与契约门37 / 37
    C 参照差分对拍(20% 确定性丢包、乱序、停摆)102 帧逐字节一致
    真 UDP 回环(双端直连与多会话宿主)5 / 5
    纯核吞吐基准(64 MB,窗 512,release)约 77–89 万消息/秒/核

    .tests/kcp/ 内为差分对拍所用 C 参照(skywind3000/kcp 的 ikcp.c / ikcp.h 精简收录,原件未修改),出处见 .tests/README.md。

    #许可证

    • 本项目代码采用木兰宽松许可证第 2 版(MulanPSL-2.0,见 LICENSE)。
    • .tests/kcp/ 内 C 参照文件按 MIT 许可分发,版权归其原作者。
    • 无背书声明:文中对拍与基准计数为本项目自测结果,不构成任何第三方 的认证或背书。

    FecSession

    FEC 会话:KCP 会话 + FEC 发/收机(层外组合,双向对称)。

    FecSession::fec_rx_stats

    FecSession::fec_tx_stats

    FecSession::is_dead

    fn FecSession::is_dead(self : FecSession) -> Bool

    FecSession::last_active

    fn FecSession::last_active(self : FecSession) -> Int

    FecSession::new

    fn FecSession::new(conv : Int, high_water : Int, k : Int, m : Int) -> FecSession

    新会话:conv 双端一致;high_water = 背压水位;k/m 组形; 恢复窗深缺省 8 组。片容量按 KCP 当前 mtu 推导。

    FecSession::on_datagram

    fn FecSession::on_datagram(self : FecSession, frame : FixedArray[Byte]) -> Int

    宿主投递一线帧:FEC 收机结算,出的 KCP 报文逐个喂会话。 返回第一个非零 input 错码(全零返 0)。

    FecSession::poll_message

    fn FecSession::poll_message(self : FecSession) -> FixedArray[Byte]?

    应用收一消息(直通,FIFO)。

    FecSession::send_message

    fn FecSession::send_message(self : FecSession, payload : FixedArray[Byte]) -> Int

    应用下发一消息(透传;返回同 KcpSession::send_message)。

    FecSession::stats

    fn FecSession::stats(self : FecSession) -> KcpStats

    KCP 观测快照(直通;FEC 侧读数走 fec_tx_stats / fec_rx_stats)。

    FecSession::tick

    fn FecSession::tick(self : FecSession, now : Int) -> Array[FixedArray[Byte]]

    宿主时钟推进:KCP 到点 flush 出的帧逐个入 FEC 发机, 返回待发送线帧(宿主逐帧走 UDP 发出)。

    Kcp

    pub(all) struct Kcp {
    conv : Int
    mtu : Int
    mss : Int
    snd_wnd : Int
    rcv_wnd : Int
    nodelay : Int
    interval : Int
    fastresend : Int
    fastlimit : Int
    nocwnd : Int
    dead_link : Int
    current : Int
    updated : Int
    ts_flush : Int
    snd_una : Int
    snd_nxt : Int
    rcv_nxt : Int
    ts_probe : Int
    probe_wait : Int
    probe : Int
    rmt_wnd : Int
    cwnd : Int
    incr : Int
    ssthresh : Int
    rx_srtt : Int
    rx_rttval : Int
    rx_rto : Int
    min_rto : Int
    state : Int
    xmit_total : Int
    rcv_accepted : Int
    last_active : Int
    snd_queue : Array[KcpSegment]
    snd_buf : Array[KcpSegment]
    rcv_buf : Array[KcpSegment]
    rcv_queue : Array[KcpSegment]
    acklist : Array[(Int, Int)]
    }

    KCP 状态机一实例(一条会话一实例;conv 双端一致)。

    Kcp::check

    fn Kcp::check(self : Kcp, current : Int) -> Int

    下次该调 update 的时刻(ikcp_check 直译)——省空转用。

    Kcp::create

    fn Kcp::create(conv : Int) -> Kcp

    初始化:ikcp_create 默认值直译。

    Kcp::flush_frames

    fn Kcp::flush_frames(self : Kcp) -> Array[FixedArray[Byte]]

    flush(ikcp_flush 直译):ACK 先行 → 探窗 → snd_queue 进窗 → 数据段首发/超时重发/快速重发 → 拥塞窗结算。返回出帧列表。

    Kcp::input

    fn Kcp::input(self : Kcp, datagram : FixedArray[Byte]) -> Int

    输入一报文(可含多段;ikcp_input 直译)。返回 0 / 负错码: -1 长度或 conv 不符;-2 声明 len 超出;-3 非法 cmd。

    Kcp::peeksize

    fn Kcp::peeksize(self : Kcp) -> Int

    下一条消息字节数(碎片合并视角;ikcp_peeksize 直译)。 队列空或碎片未齐返回 -1。

    Kcp::recv

    fn Kcp::recv(self : Kcp) -> FixedArray[Byte]?

    应用读一整消息(碎片合并);队列空或碎片未齐返回 None。 C ikcp_recv 序:先 peeksize 完整性判(负值拒读),后合并。 单片段零拷贝直返;多片段 FixedArray 直写合并。

    Kcp::send

    fn Kcp::send(self : Kcp, payload : FixedArray[Byte]) -> Int

    应用下发一消息(按 mss 分片,frg 倒计;ikcp_send 消息模式直译)。 返回字节数;超接收窗口分片数返回 -2。单片段零拷贝共享载荷。

    Kcp::set_mtu

    fn Kcp::set_mtu(self : Kcp, mtu : Int) -> Int

    MTU 配置(ikcp_setmtu 直译;帧组装即席分配,无预留块)。

    Kcp::set_nodelay

    fn Kcp::set_nodelay(self : Kcp, nodelay : Int, interval : Int, resend : Int, nc : Int) -> Unit

    nodelay 配置(ikcp_nodelay 直译):nodelay 档位 / interval(ms) / 快速重传触发(0 关)/ 是否关拥塞控制。传 -1 表示该项不动。

    Kcp::set_wnd

    fn Kcp::set_wnd(self : Kcp, sndwnd : Int, rcvwnd : Int) -> Unit

    窗口配置(ikcp_wndsize 直译):传 0 表示该项不动;rcv_wnd 有下限钳制 imax(rcvwnd, WND_RCV)(C 参照同式——收窗永不低于 128,差分门逮出的 直译偏差,2026-10-25)。

    Kcp::stats

    fn Kcp::stats(self : Kcp) -> KcpStats

    Kcp::update

    fn Kcp::update(self : Kcp, current : Int) -> Array[FixedArray[Byte]]

    时间推进(ikcp_update 直译,纯形):到 interval 点则 flush, 返回本次出帧(未到点返回空表)。

    Kcp::waitsnd

    fn Kcp::waitsnd(self : Kcp) -> Int

    待确认段总数(应用层流控参考)。

    KcpSegment

    pub(all) struct KcpSegment {
    conv : Int
    cmd : Int
    frg : Int
    wnd : Int
    ts : Int
    sn : Int
    una : Int
    len : Int
    data : FixedArray[Byte]
    xmit : Int
    fastack : Int
    resendts : Int
    rto : Int
    }

    KCP 段:24B 线格式字段 + snd_buf 在途态。data 为 FixedArray[Byte] (纪律不可变,发送侧与载荷共享同一对象,零拷贝;批拷走 unsafe_blit)。

    KcpServer

    pub(all) struct KcpServer {
    sessions : Map[Int, KcpSession]
    high_water : Int
    max_sessions : Int
    }

    conv 分离器多会话服务端。

    KcpServer::convs

    fn KcpServer::convs(self : KcpServer) -> Array[Int]

    在册 conv 清单(宿主逐会话 poll 用)。

    KcpServer::get_or_create

    fn KcpServer::get_or_create(self : KcpServer, conv : Int) -> KcpSession?

    会话取建:未知 conv 自动建;表满返回 None(调用方弃包)。

    KcpServer::new

    fn KcpServer::new(high_water : Int, max_sessions : Int) -> KcpServer

    新服务端:high_water = 每会话背压水位;max_sessions = 会话表上限 (洪泛护栏,未知 conv 建会话超限即弃)。

    KcpServer::on_datagram

    fn KcpServer::on_datagram(self : KcpServer, datagram : FixedArray[Byte]) -> Int

    收一报:头 4 字节 conv 路由。返回码:input 错码直传(0/-1/-2/-3); -4 = 会话表满弃包。

    KcpServer::remove

    fn KcpServer::remove(self : KcpServer, conv : Int) -> Unit

    会话收尸(宿主经 is_dead 探得后调用;或管理面强拆)。

    KcpServer::session_of

    fn KcpServer::session_of(self : KcpServer, conv : Int) -> KcpSession?

    会话查访(已建会话;不建)。

    KcpServer::size

    fn KcpServer::size(self : KcpServer) -> Int

    在册会话数(护栏读数)。

    KcpServer::sweep

    fn KcpServer::sweep(self : KcpServer, current : Int, idle_timeout : Int) -> Array[Int]

    闲置清扫:current - last_active > idle_timeout 的会话移除并返回 被逐 conv 清单。纯形:时钟由宿主注入,清扫由宿主在驱动环里调。

    KcpServer::tick

    fn KcpServer::tick(self : KcpServer, current : Int) -> Array[(Int, FixedArray[Byte])]

    时间推进:全部会话各 tick 一拍,出帧带 conv 标签展平返回 (宿主按 conv 查地址投递)。

    KcpSession

    pub(all) struct KcpSession {
    kcp : Kcp
    inbound : Array[FixedArray[Byte]]
    high_water : Int
    }

    KCP 会话:状态机 + 收件队列 + 发送背压水位。

    KcpSession::is_dead

    fn KcpSession::is_dead(self : KcpSession) -> Bool

    链路判死(dead_link 触发后 state = -1)。

    KcpSession::last_active

    fn KcpSession::last_active(self : KcpSession) -> Int

    活性戳透传(最近一次合法对端报文的注入 ms;闲置回收判据)。

    KcpSession::new

    fn KcpSession::new(conv : Int, high_water : Int) -> KcpSession

    新会话:conv 双端一致;high_water = waitsnd 背压上限 (snd_queue + snd_buf 达此值 send_message 拒收)。

    KcpSession::on_datagram

    fn KcpSession::on_datagram(self : KcpSession, datagram : FixedArray[Byte]) -> Int

    宿主投递一 UDP 报文:状态机结算后,把已就绪的整消息 全部搬入收件队列。返回 input 错码(0 = 正常)。

    KcpSession::poll_message

    fn KcpSession::poll_message(self : KcpSession) -> FixedArray[Byte]?

    应用收一消息(FIFO);无消息返回 None。

    KcpSession::send_message

    fn KcpSession::send_message(self : KcpSession, payload : FixedArray[Byte]) -> Int

    应用下发一消息。waitsnd 达 high_water 返回 -2(窗口满背压); 否则转 Kcp::send(负错码直传;成功返回字节数)。

    KcpSession::stats

    fn KcpSession::stats(self : KcpSession) -> KcpStats

    观测快照透传(运维/调试面)。

    KcpSession::tick

    fn KcpSession::tick(self : KcpSession, current : Int) -> Array[FixedArray[Byte]]

    宿主时钟推进:到 interval 点则 flush,返回待发送帧 (宿主逐帧走 UDP 发出)。

    KcpStats

    pub(all) struct KcpStats {
    srtt : Int
    rttval : Int
    rto : Int
    cwnd : Int
    snd_wnd : Int
    rmt_wnd : Int
    in_flight : Int
    queued : Int
    rto_retransmits : Int
    rcv_accepted : Int
    rcv_queue_len : Int
    rcv_buf_len : Int
    last_active : Int
    }

    观测快照(只读;运维/调试面)。rto_retransmits = C kcp->xmit (超时重传累计;快速重传不计,同参照)。

    ASK_SEND

    let ASK_SEND : Int

    探窗标志:需发 WASK(问对方窗口)。

    ASK_TELL

    let ASK_TELL : Int

    探窗标志:需发 WINS(报自己窗口)。

    CMD_ACK

    let CMD_ACK : Int

    CMD_PUSH

    let CMD_PUSH : Int

    CMD_WASK

    let CMD_WASK : Int

    CMD_WINS

    let CMD_WINS : Int

    let DEADLINK : Int

    同段重发达此次数判链路死(state = -1)。

    FASTACK_LIMIT

    let FASTACK_LIMIT : Int

    快速重传触发上限次数。

    HEADER_SIZE

    let HEADER_SIZE : Int

    INTERVAL_DEF

    let INTERVAL_DEF : Int

    默认 flush 间隔(ms)。

    MTU_DEF

    let MTU_DEF : Int

    默认 MTU(字节)。

    PROBE_INIT

    let PROBE_INIT : Int

    探窗间隔初值(ms)。

    PROBE_LIMIT

    let PROBE_LIMIT : Int

    探窗间隔上限(ms)。

    RTO_DEF

    let RTO_DEF : Int

    初始 RTO(ms)。

    RTO_MAX

    let RTO_MAX : Int

    RTO 上限(ms)。

    RTO_MIN

    let RTO_MIN : Int

    常规档最小 RTO(ms)。

    RTO_NDL

    let RTO_NDL : Int

    无延迟档最小 RTO(ms)。

    THRESH_INIT

    let THRESH_INIT : Int

    慢启动门限初值。

    THRESH_MIN

    let THRESH_MIN : Int

    慢启动门限下限。

    WND_RCV

    let WND_RCV : Int

    默认接收窗口(必须 >= 最大分片数)。

    WND_SND

    let WND_SND : Int

    默认发送窗口。

    decode_segment

    fn decode_segment(buf : FixedArray[Byte], offset : Int) -> (KcpSegment, Int)?

    从 Bytes 报文 offset 处解码一段。返回 (段, 消耗字节数); 头不完整或声明 len 越界返回 None。

    encode_segment

    fn encode_segment(seg : KcpSegment) -> FixedArray[Byte]

    编码 Segment 为线格式 Bytes(单趟 makei,头 + 载荷一体成形)。