Sign in

    moonkcp

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

    kcp
    udp
    arq
    reliable-transport
    concurrency
    Download zip
    Author
    Version
    0.1.0
    License
    MulanPSL-2.0
    Last updated
    2 hours ago
    Downloads
    3

    #MoonKCP

    thy1016/moonkcp 是 KCP 协议的 MoonBit 纯实现——以 UDP 为底座的可靠有序 ARQ 传输库。协议核心为零 IO、零时钟的纯状态机(毫秒由调用方注入,帧由 调用方搬运),其上提供会话壳(背压 / 碎片合并 / 生命期)、多会话服务端 (conv 分离 / 洪泛护栏 / 闲置回收)与 async UDP 适配器(双端直连与服务端 组合宿主)。实现与 skywind3000/kcp C 参照保持差分对拍逐字节等价。

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

    #特性

    • 纯状态机核心:Kcp::create 到 check 全量直译 C 参照——ACK 结算、 选择性与超时重传、快速重传(FASTACK_CONSERVE 变体)、窗口探针、拥塞 门限;update(current) 返回出帧,无任何隐藏时钟与系统调用。
    • 会话壳 KcpSession:应用消息按 MSS 分片与碎片合并,waitsnd 水位 背压,FIFO 收件队列,链路判死透传。
    • 多会话服务端 KcpServer:按报文头 conv 路由,未知会话自动建立, max_sessions 洪泛护栏,闲置回收(sweep,时钟由宿主注入)。
    • async UDP 适配器:KcpUdpPeer(客户端直连 / 服务端单对端)与 KcpUdpHub(单 socket 多会话组合宿主,conv→地址自学习)。
    • 观测面 Kcp::stats:RTT 平滑值、RTO、拥塞窗、在途与队列读数、超时 重传计数、接收守恒账(rcv_accepted == rcv_queue + rcv_buf 恒等可验)。
    • 差分对拍保证:与 C 参照驱动在同场景机(确定性丢包 + 乱序 + 停摆)下 帧转录逐字节一致;等价性由门锚定,不靠逐函数目测。

    #安装

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

    moon add thy1016/moonkcp

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

    #快速上手

    #纯核驱动环(零 IO 零时钟)

    宿主持有两个对端实例,每拍注入当前毫秒、搬运帧——传输介质无关:

    let sender = @moonkcp.Kcp::create(1)
    let receiver = @moonkcp.Kcp::create(1)
    sender.set_nodelay(1, 10, 2, 1) // 低延迟档:interval 10ms / 快速重传 2
    let _ = sender.send(Bytes::makei(64, fn(i) { i.to_byte() }))
    // 每拍(间隔不大于 interval):
    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) // conv 与背压水位
    let n = session.send_message(payload) // 窗口满返 -2(背压拒收)
    let code = session.on_datagram(datagram) // 宿主投递报文,就绪消息入队
    let frames = session.tick(now_ms) // 等价 update,返回待发帧

    #UDP 适配器

    // 服务端:一条 socket 服务多会话,按 conv 路由
    let hub = @adapter.KcpUdpHub::listen(128, 1024)
    // 客户端:connected UDP,指向服务端地址
    let peer = @adapter.KcpUdpPeer::connect_client(7, 128, hub.local_addr())
    // 驱动:peer.pump_out(t) 出帧、hub.recv_one() 收报、hub.pump_out(t) 回帧

    #API 总览

    入口作用
    Kcp::create / set_nodelay / set_wnd / set_mtu建实例与配置(nodelay 档位、窗口、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双端直连(connected 客户端 / 单对端服务端)
    @adapter.KcpUdpHub单 socket 多会话组合宿主

    #错误面

    • Kcp::input 返回码:-1 长度不足或 conv 不符;-2 声明长度超出报文 余量;-3 非法命令字节。
    • KcpSession::send_message 返回 -2 表示发送窗口满(背压拒收,应用 侧择时重试);成功返回接受字节数。
    • KcpServer::on_datagram 另有 -4:会话表满,报文弃置(洪泛护栏)。
    • Kcp::recv 返回 None:队列空或碎片未齐(完整性门,不产半截消息)。

    #设计边界

    • 仅消息模式:KCP 的 stream 追加模式不提供;文档同步与控制面按消息 语义设计。
    • 协作式单运行时内使用:非线程安全;跨线程事件须经运行时进入。
    • 协议核心全后端可移植;adapter 因原生 socket 限 native 后端。
    • 吞吐扩展(队列环形化等)按实测判据重估,当前未排期;基准读数见下节。

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

    #测试与验证

    moon test --target native node workspace/async_watchdog.mjs # async 门挂起看门狗 node workspace/diff_gate.mjs # C 参照差分对拍 node workspace/bench.mjs # 吞吐基准(留证不作门)

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

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

    bak/kcp/ 内为差分对拍的 C 参照(skywind3000/kcp 的 ikcp.c / ikcp.h 精简 vendored,原件未修改),出处与取舍见 bak/README.md。

    #许可证

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

    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,头 + 载荷一体成形)。