实时游戏网络协议深度对比:KCP vs WebRTC vs WebSocket
本文基于 kratos-transport 项目的实际架构,深入分析三种主流实时通信协议在游戏场景下的性能差异与选型策略。
1. 三种协议的协议栈全景
1.1 KCP
#mermaid-svg-id1g1ZcPSmVGRpXO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-id1g1ZcPSmVGRpXO .error-icon{fill:#552222;}#mermaid-svg-id1g1ZcPSmVGRpXO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-id1g1ZcPSmVGRpXO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-id1g1ZcPSmVGRpXO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-id1g1ZcPSmVGRpXO .marker.cross{stroke:#333333;}#mermaid-svg-id1g1ZcPSmVGRpXO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-id1g1ZcPSmVGRpXO p{margin:0;}#mermaid-svg-id1g1ZcPSmVGRpXO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-id1g1ZcPSmVGRpXO .cluster-label text{fill:#333;}#mermaid-svg-id1g1ZcPSmVGRpXO .cluster-label span,#mermaid-svg-id1g1ZcPSmVGRpXO p{color:#333;}#mermaid-svg-id1g1ZcPSmVGRpXO .label text,#mermaid-svg-id1g1ZcPSmVGRpXO span,#mermaid-svg-id1g1ZcPSmVGRpXO p{fill:#333;color:#333;}#mermaid-svg-id1g1ZcPSmVGRpXO .node rect,#mermaid-svg-id1g1ZcPSmVGRpXO .node circle,#mermaid-svg-id1g1ZcPSmVGRpXO .node ellipse,#mermaid-svg-id1g1ZcPSmVGRpXO .node polygon,#mermaid-svg-id1g1ZcPSmVGRpXO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-id1g1ZcPSmVGRpXO .flowchart-label text{text-anchor:middle;}#mermaid-svg-id1g1ZcPSmVGRpXO .node .label{text-align:center;}#mermaid-svg-id1g1ZcPSmVGRpXO .node.clickable{cursor:pointer;}#mermaid-svg-id1g1ZcPSmVGRpXO .arrowheadPath{fill:#333333;}#mermaid-svg-id1g1ZcPSmVGRpXO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-id1g1ZcPSmVGRpXO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-id1g1ZcPSmVGRpXO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-id1g1ZcPSmVGRpXO .edgeLabel p{margin:0;padding:0;display:inline;}#mermaid-svg-id1g1ZcPSmVGRpXO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-id1g1ZcPSmVGRpXO .labelBkg{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-id1g1ZcPSmVGRpXO .node .cluster{fill:rgba(255, 255, 222, 0.5);stroke:rgba(170, 170, 51, 0.2);box-shadow:rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;stroke-width:1px;}#mermaid-svg-id1g1ZcPSmVGRpXO .cluster text{fill:#333;}#mermaid-svg-id1g1ZcPSmVGRpXO .cluster span,#mermaid-svg-id1g1ZcPSmVGRpXO p{color:#333;}#mermaid-svg-id1g1ZcPSmVGRpXO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-id1g1ZcPSmVGRpXO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-id1g1ZcPSmVGRpXO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-id1g1ZcPSmVGRpXO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-id1g1ZcPSmVGRpXO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} KCP 协议栈
Game Logic
KCP --- ARQ 重传(策略激进)
UDP
IP
KCP 是一个基于 UDP 的可靠传输协议,由 skywind3000 开发。其核心思想是用带宽换延迟------通过更激进的重传策略和拥塞控制,在弱网环境下获得远低于 TCP 的延迟。
关键特性:
- 纯算法层:不负责底层收发,只处理 ARQ(自动重传请求)逻辑
- 双模式:支持可靠模式(ARQ)和不可靠模式(纯 UDP)
- 极低开销:Header 仅 24 字节
- 可调参数:nodelay、interval、resend、nc 等参数可精细调节
1.2 WebRTC DataChannel
#mermaid-svg-3bBapJmkDokyXepi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3bBapJmkDokyXepi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3bBapJmkDokyXepi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3bBapJmkDokyXepi .error-icon{fill:#552222;}#mermaid-svg-3bBapJmkDokyXepi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3bBapJmkDokyXepi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3bBapJmkDokyXepi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3bBapJmkDokyXepi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3bBapJmkDokyXepi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3bBapJmkDokyXepi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3bBapJmkDokyXepi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3bBapJmkDokyXepi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3bBapJmkDokyXepi .marker.cross{stroke:#333333;}#mermaid-svg-3bBapJmkDokyXepi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3bBapJmkDokyXepi p{margin:0;}#mermaid-svg-3bBapJmkDokyXepi .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3bBapJmkDokyXepi .cluster-label text{fill:#333;}#mermaid-svg-3bBapJmkDokyXepi .cluster-label span,#mermaid-svg-3bBapJmkDokyXepi p{color:#333;}#mermaid-svg-3bBapJmkDokyXepi .label text,#mermaid-svg-3bBapJmkDokyXepi span,#mermaid-svg-3bBapJmkDokyXepi p{fill:#333;color:#333;}#mermaid-svg-3bBapJmkDokyXepi .node rect,#mermaid-svg-3bBapJmkDokyXepi .node circle,#mermaid-svg-3bBapJmkDokyXepi .node ellipse,#mermaid-svg-3bBapJmkDokyXepi .node polygon,#mermaid-svg-3bBapJmkDokyXepi .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3bBapJmkDokyXepi .flowchart-label text{text-anchor:middle;}#mermaid-svg-3bBapJmkDokyXepi .node .label{text-align:center;}#mermaid-svg-3bBapJmkDokyXepi .node.clickable{cursor:pointer;}#mermaid-svg-3bBapJmkDokyXepi .arrowheadPath{fill:#333333;}#mermaid-svg-3bBapJmkDokyXepi .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3bBapJmkDokyXepi .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3bBapJmkDokyXepi .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3bBapJmkDokyXepi .edgeLabel p{margin:0;padding:0;display:inline;}#mermaid-svg-3bBapJmkDokyXepi .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3bBapJmkDokyXepi .labelBkg{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3bBapJmkDokyXepi .node .cluster{fill:rgba(255, 255, 222, 0.5);stroke:rgba(170, 170, 51, 0.2);box-shadow:rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;stroke-width:1px;}#mermaid-svg-3bBapJmkDokyXepi .cluster text{fill:#333;}#mermaid-svg-3bBapJmkDokyXepi .cluster span,#mermaid-svg-3bBapJmkDokyXepi p{color:#333;}#mermaid-svg-3bBapJmkDokyXepi div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3bBapJmkDokyXepi .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3bBapJmkDokyXepi .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3bBapJmkDokyXepi .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3bBapJmkDokyXepi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WebRTC DataChannel 协议栈
Game Logic
SCTP --- 可靠/不可靠可配置 (RFC 4960)
DTLS --- 强制加密
ICE / UDP --- NAT 穿透 + STUN/TURN
IP
WebRTC 是 W3C 和 IETF 制定的实时通信标准。DataChannel 基于 SCTP over DTLS over UDP,在浏览器中原生支持。
关键特性:
- 双模式 :可靠模式(SCTP 重传)和不可靠模式(
maxRetransmits: 0) - 强制加密:DTLS 层加密,无法关闭
- P2P 直连:ICE 协商自动选择最优路径
- NAT 穿透:内置 STUN/TURN 支持
- 浏览器原生支持:所有现代浏览器无需插件
1.3 WebSocket
#mermaid-svg-c8n3ke98hHZK8CDy{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-c8n3ke98hHZK8CDy .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-c8n3ke98hHZK8CDy .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-c8n3ke98hHZK8CDy .error-icon{fill:#552222;}#mermaid-svg-c8n3ke98hHZK8CDy .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-c8n3ke98hHZK8CDy .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-c8n3ke98hHZK8CDy .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-c8n3ke98hHZK8CDy .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-c8n3ke98hHZK8CDy .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-c8n3ke98hHZK8CDy .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-c8n3ke98hHZK8CDy .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-c8n3ke98hHZK8CDy .marker{fill:#333333;stroke:#333333;}#mermaid-svg-c8n3ke98hHZK8CDy .marker.cross{stroke:#333333;}#mermaid-svg-c8n3ke98hHZK8CDy svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-c8n3ke98hHZK8CDy p{margin:0;}#mermaid-svg-c8n3ke98hHZK8CDy .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-c8n3ke98hHZK8CDy .cluster-label text{fill:#333;}#mermaid-svg-c8n3ke98hHZK8CDy .cluster-label span,#mermaid-svg-c8n3ke98hHZK8CDy p{color:#333;}#mermaid-svg-c8n3ke98hHZK8CDy .label text,#mermaid-svg-c8n3ke98hHZK8CDy span,#mermaid-svg-c8n3ke98hHZK8CDy p{fill:#333;color:#333;}#mermaid-svg-c8n3ke98hHZK8CDy .node rect,#mermaid-svg-c8n3ke98hHZK8CDy .node circle,#mermaid-svg-c8n3ke98hHZK8CDy .node ellipse,#mermaid-svg-c8n3ke98hHZK8CDy .node polygon,#mermaid-svg-c8n3ke98hHZK8CDy .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-c8n3ke98hHZK8CDy .flowchart-label text{text-anchor:middle;}#mermaid-svg-c8n3ke98hHZK8CDy .node .label{text-align:center;}#mermaid-svg-c8n3ke98hHZK8CDy .node.clickable{cursor:pointer;}#mermaid-svg-c8n3ke98hHZK8CDy .arrowheadPath{fill:#333333;}#mermaid-svg-c8n3ke98hHZK8CDy .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-c8n3ke98hHZK8CDy .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-c8n3ke98hHZK8CDy .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-c8n3ke98hHZK8CDy .edgeLabel p{margin:0;padding:0;display:inline;}#mermaid-svg-c8n3ke98hHZK8CDy .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-c8n3ke98hHZK8CDy .labelBkg{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-c8n3ke98hHZK8CDy .node .cluster{fill:rgba(255, 255, 222, 0.5);stroke:rgba(170, 170, 51, 0.2);box-shadow:rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;stroke-width:1px;}#mermaid-svg-c8n3ke98hHZK8CDy .cluster text{fill:#333;}#mermaid-svg-c8n3ke98hHZK8CDy .cluster span,#mermaid-svg-c8n3ke98hHZK8CDy p{color:#333;}#mermaid-svg-c8n3ke98hHZK8CDy div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-c8n3ke98hHZK8CDy .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-c8n3ke98hHZK8CDy .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-c8n3ke98hHZK8CDy .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-c8n3ke98hHZK8CDy :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} WebSocket 协议栈
Game Logic
Frame 帧
TCP --- 仅可靠传输
TLS --- 可选加密 (wss://)
IP
WebSocket 基于 HTTP 升级握手建立 TCP 长连接,是最简单的全双工通信方案。
关键特性:
- 仅可靠模式:基于 TCP,无不可靠发送能力
- 队头阻塞:TCP 的根本缺陷------一个包丢失,后续所有包排队等待
- 最广兼容:所有浏览器、所有网络环境
- 最简部署:HTTP 端口复用,无需额外基础设施
2. 核心机制深度对比
2.1 可靠性机制
| 机制 | KCP | WebRTC (SCTP) | WebSocket (TCP) |
|---|---|---|---|
| 重传触发 | 快速重传 + 超时重传 | 快速重传 + 超时重传 | 超时重传 |
| 首次重传等待 | 2 × RTT(可配置为 1 × RTT) | 约 3 × RTT | 依赖操作系统 TCP 栈 |
| 拥塞控制 | 可关闭(纯全力发送) | CMT(并发多路径) | Cubic / BBR |
| 丢包恢复速度 | ⭐⭐⭐ 快(激进) | ⭐⭐ 中等 | ⭐ 慢(保守) |
| 可靠/不可靠切换 | ✅ 同一连接 | ✅ 同一连接 | ❌ 全部可靠 |
2.2 为什么 TCP/WebSocket 不适合实时游戏
TCP 的核心问题是队头阻塞(Head-of-Line Blocking):
接收方 (TCP) 网络 发送方 接收方 (TCP) 网络 发送方 #mermaid-svg-aPL9EnA2fXFOXpk2{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-aPL9EnA2fXFOXpk2 .error-icon{fill:#552222;}#mermaid-svg-aPL9EnA2fXFOXpk2 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-aPL9EnA2fXFOXpk2 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-aPL9EnA2fXFOXpk2 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-aPL9EnA2fXFOXpk2 .marker.cross{stroke:#333333;}#mermaid-svg-aPL9EnA2fXFOXpk2 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-aPL9EnA2fXFOXpk2 p{margin:0;}#mermaid-svg-aPL9EnA2fXFOXpk2 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aPL9EnA2fXFOXpk2 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-aPL9EnA2fXFOXpk2 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aPL9EnA2fXFOXpk2 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-aPL9EnA2fXFOXpk2 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-aPL9EnA2fXFOXpk2 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-aPL9EnA2fXFOXpk2 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-aPL9EnA2fXFOXpk2 .sequenceNumber{fill:white;}#mermaid-svg-aPL9EnA2fXFOXpk2 #sequencenumber{fill:#333;}#mermaid-svg-aPL9EnA2fXFOXpk2 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-aPL9EnA2fXFOXpk2 .messageText{fill:#333;stroke:none;}#mermaid-svg-aPL9EnA2fXFOXpk2 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aPL9EnA2fXFOXpk2 .labelText,#mermaid-svg-aPL9EnA2fXFOXpk2 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-aPL9EnA2fXFOXpk2 .loopText,#mermaid-svg-aPL9EnA2fXFOXpk2 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-aPL9EnA2fXFOXpk2 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-aPL9EnA2fXFOXpk2 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-aPL9EnA2fXFOXpk2 .noteText,#mermaid-svg-aPL9EnA2fXFOXpk2 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-aPL9EnA2fXFOXpk2 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aPL9EnA2fXFOXpk2 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aPL9EnA2fXFOXpk2 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-aPL9EnA2fXFOXpk2 .actorPopupMenu{position:absolute;}#mermaid-svg-aPL9EnA2fXFOXpk2 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-aPL9EnA2fXFOXpk2 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-aPL9EnA2fXFOXpk2 .actor-man circle,#mermaid-svg-aPL9EnA2fXFOXpk2 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-aPL9EnA2fXFOXpk2 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 正常情况(无丢包)--- 延迟正常 ✅ 丢包情况(P2 丢失)--- P3 P4 P5 全部阻塞 ❌ P2 丢失! P3 P4 P5 已到达内核 但 TCP 等待 P2 重传 P2 P3 P4 P5 一次性交付 --- 延迟暴涨! P1 P1 P2 P2 P3 P3 P1 P1 P2 P3 → 内核缓冲区等待 P4 → 内核缓冲区等待 P5 → 内核缓冲区等待 P2 重传 P2
在游戏中,P3 P4 P5 是更新的位置数据,P2 已经过时了,等它毫无意义------我们需要的是"最新的",不是"全部的"。
KCP 和 WebRTC DataChannel 的不可靠模式解决了这个问题:
接收方 (KCP/WebRTC 不可靠模式) 网络 发送方 接收方 (KCP/WebRTC 不可靠模式) 网络 发送方 #mermaid-svg-0jaZ3E9fRzZQpUsw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0jaZ3E9fRzZQpUsw .error-icon{fill:#552222;}#mermaid-svg-0jaZ3E9fRzZQpUsw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0jaZ3E9fRzZQpUsw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0jaZ3E9fRzZQpUsw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0jaZ3E9fRzZQpUsw .marker.cross{stroke:#333333;}#mermaid-svg-0jaZ3E9fRzZQpUsw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0jaZ3E9fRzZQpUsw p{margin:0;}#mermaid-svg-0jaZ3E9fRzZQpUsw .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0jaZ3E9fRzZQpUsw text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-0jaZ3E9fRzZQpUsw .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0jaZ3E9fRzZQpUsw .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-0jaZ3E9fRzZQpUsw .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-0jaZ3E9fRzZQpUsw .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-0jaZ3E9fRzZQpUsw #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-0jaZ3E9fRzZQpUsw .sequenceNumber{fill:white;}#mermaid-svg-0jaZ3E9fRzZQpUsw #sequencenumber{fill:#333;}#mermaid-svg-0jaZ3E9fRzZQpUsw #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-0jaZ3E9fRzZQpUsw .messageText{fill:#333;stroke:none;}#mermaid-svg-0jaZ3E9fRzZQpUsw .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0jaZ3E9fRzZQpUsw .labelText,#mermaid-svg-0jaZ3E9fRzZQpUsw .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-0jaZ3E9fRzZQpUsw .loopText,#mermaid-svg-0jaZ3E9fRzZQpUsw .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-0jaZ3E9fRzZQpUsw .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0jaZ3E9fRzZQpUsw .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-0jaZ3E9fRzZQpUsw .noteText,#mermaid-svg-0jaZ3E9fRzZQpUsw .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-0jaZ3E9fRzZQpUsw .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0jaZ3E9fRzZQpUsw .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0jaZ3E9fRzZQpUsw .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0jaZ3E9fRzZQpUsw .actorPopupMenu{position:absolute;}#mermaid-svg-0jaZ3E9fRzZQpUsw .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-0jaZ3E9fRzZQpUsw .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0jaZ3E9fRzZQpUsw .actor-man circle,#mermaid-svg-0jaZ3E9fRzZQpUsw line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-0jaZ3E9fRzZQpUsw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} P2 丢失!(跳过,已过时) P1 P1 P2 P3 P3 ✅ 立即交付 P4 P4 ✅ 立即交付 P5 P5 ✅ 立即交付
2.3 Header 开销对比
| 协议 | Header 大小 | 说明 |
|---|---|---|
| KCP | 24 字节 | conv(4) + cmd(1) + frg(1) + wnd(2) + ts(4) + sn(4) + una(4) + len(4) |
| SCTP | 16 字节 | src/dst port(4) + vtag(4) + checksum(4) + chunk header |
| DTLS | 13 字节 | content type(1) + version(2) + epoch(2) + seq(6) + len(2) |
| WebRTC 总计 | 29+ 字节 | SCTP(16) + DTLS(13) |
| WebSocket | 2-6 字节 | FIN(1bit) + opcode(4bit) + mask(1bit) + len(7/16/64bit) |
| TCP | 20 字节 | 标准TCP头 |
| UDP | 8 字节 | 标准UDP头 |
实际效率(假设有效载荷 100 字节):
| 协议 | 总传输 | 有效率 |
|---|---|---|
| KCP over UDP | 132 字节 | 75.8% |
| WebRTC DC | 137+ 字节 | 73.0% |
| WebSocket over TCP | 126 字节 | 79.4% |
WebRTC 的有效率最低,但对于游戏场景(帧率 30-120fps,每帧几百字节),额外开销在整体带宽中占比极小。
2.4 延迟模型
理想网络(0% 丢包,局域网):
| 协议 | 单程延迟 |
|---|---|
| KCP | 1-3ms |
| WebRTC | 5-10ms |
| WebSocket | 1-3ms |
公网正常(0.5% 丢包,同城):
| 协议 | 单程延迟 |
|---|---|
| KCP | 10-30ms |
| WebRTC | 20-50ms |
| WebSocket | 30-60ms |
弱网(5% 丢包):
| 协议 | 单程延迟 | 原因 |
|---|---|---|
| KCP | 30-60ms | 激进重传 + 可选冗余包 |
| WebRTC | 50-100ms | SCTP 温和拥塞控制 |
| WebSocket | 200-500ms | TCP 指数退避 + 队头阻塞 |
恶劣网络(10% 丢包):
| 协议 | 单程延迟 | 状态 |
|---|---|---|
| KCP | 60-100ms | 可玩 |
| WebRTC | 100-200ms | 勉强可玩 |
| WebSocket | 500ms-2s | 完全不可玩 |
3. 游戏类型 vs 协议选型
3.1 延迟敏感度光谱
#mermaid-svg-Oao4h8IQznP4ApSb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Oao4h8IQznP4ApSb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Oao4h8IQznP4ApSb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Oao4h8IQznP4ApSb .error-icon{fill:#552222;}#mermaid-svg-Oao4h8IQznP4ApSb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Oao4h8IQznP4ApSb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Oao4h8IQznP4ApSb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Oao4h8IQznP4ApSb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Oao4h8IQznP4ApSb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Oao4h8IQznP4ApSb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Oao4h8IQznP4ApSb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Oao4h8IQznP4ApSb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Oao4h8IQznP4ApSb .marker.cross{stroke:#333333;}#mermaid-svg-Oao4h8IQznP4ApSb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Oao4h8IQznP4ApSb p{margin:0;}#mermaid-svg-Oao4h8IQznP4ApSb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Oao4h8IQznP4ApSb .cluster-label text{fill:#333;}#mermaid-svg-Oao4h8IQznP4ApSb .cluster-label span{color:#333;}#mermaid-svg-Oao4h8IQznP4ApSb .cluster-label span p{background-color:transparent;}#mermaid-svg-Oao4h8IQznP4ApSb .label text,#mermaid-svg-Oao4h8IQznP4ApSb span{fill:#333;color:#333;}#mermaid-svg-Oao4h8IQznP4ApSb .node rect,#mermaid-svg-Oao4h8IQznP4ApSb .node circle,#mermaid-svg-Oao4h8IQznP4ApSb .node ellipse,#mermaid-svg-Oao4h8IQznP4ApSb .node polygon,#mermaid-svg-Oao4h8IQznP4ApSb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Oao4h8IQznP4ApSb .rough-node .label text,#mermaid-svg-Oao4h8IQznP4ApSb .node .label text,#mermaid-svg-Oao4h8IQznP4ApSb .image-shape .label,#mermaid-svg-Oao4h8IQznP4ApSb .icon-shape .label{text-anchor:middle;}#mermaid-svg-Oao4h8IQznP4ApSb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Oao4h8IQznP4ApSb .rough-node .label,#mermaid-svg-Oao4h8IQznP4ApSb .node .label,#mermaid-svg-Oao4h8IQznP4ApSb .image-shape .label,#mermaid-svg-Oao4h8IQznP4ApSb .icon-shape .label{text-align:center;}#mermaid-svg-Oao4h8IQznP4ApSb .node.clickable{cursor:pointer;}#mermaid-svg-Oao4h8IQznP4ApSb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Oao4h8IQznP4ApSb .arrowheadPath{fill:#333333;}#mermaid-svg-Oao4h8IQznP4ApSb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Oao4h8IQznP4ApSb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Oao4h8IQznP4ApSb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Oao4h8IQznP4ApSb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Oao4h8IQznP4ApSb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Oao4h8IQznP4ApSb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Oao4h8IQznP4ApSb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Oao4h8IQznP4ApSb .cluster text{fill:#333;}#mermaid-svg-Oao4h8IQznP4ApSb .cluster span{color:#333;}#mermaid-svg-Oao4h8IQznP4ApSb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Oao4h8IQznP4ApSb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Oao4h8IQznP4ApSb rect.text{fill:none;stroke-width:0;}#mermaid-svg-Oao4h8IQznP4ApSb .icon-shape,#mermaid-svg-Oao4h8IQznP4ApSb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Oao4h8IQznP4ApSb .icon-shape p,#mermaid-svg-Oao4h8IQznP4ApSb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Oao4h8IQznP4ApSb .icon-shape .label rect,#mermaid-svg-Oao4h8IQznP4ApSb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Oao4h8IQznP4ApSb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Oao4h8IQznP4ApSb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Oao4h8IQznP4ApSb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 延迟容忍度(从高到低)
WebSocket 能凑合
WebRTC 够用
必须 KCP
棋牌
500ms
捕鱼
200ms
MOBA
100ms
RTS
60ms
FPS 轻度
40ms
FPS 竞技
<20ms
3.2 详细场景分析
场景 A:捕鱼 / 休闲游戏
网络特征:低频操作(点击发射),高频广播(鱼群位置),容忍偶尔卡顿
| 操作 | 频率 | 可靠性要求 | 延迟容忍 |
|---|---|---|---|
| 发射子弹 | 玩家触发 | 可靠 | 200ms |
| 鱼群同步 | 10-20fps | 不可靠 | 100ms |
| 命中判定 | 玩家触发 | 可靠 | 200ms |
| 金币变化 | 偶发 | 可靠 | 500ms |
推荐 :WebRTC DataChannel --- 支持不可靠模式广播鱼群位置,弱网表现远优于 WebSocket
场景 B:MOBA(LOL / Dota2 / 王者荣耀)
网络特征:客户端预测 + 服务端权威,30 tick/s,10 人同屏团战
| 操作 | 频率 | 可靠性要求 | 延迟容忍 |
|---|---|---|---|
| 英雄移动 | 持续(点击/摇杆) | 不可靠(服务端纠正) | 60ms |
| 技能释放 | 玩家触发 | 可靠 | 50ms |
| 位置同步 | 30fps 服务器广播 | 不可靠 | 60ms |
| 血量/状态 | 状态变化时 | 可靠 | 100ms |
推荐 :WebRTC DataChannel --- LOL 官方在 60ms 延迟下就属于"良好",WebRTC 完全满足
场景 C:竞技 FPS(CS2 / Valorant / APEX)
网络特征:服务端 60-128 tick/s,客户端预测 + 回滚,开镜/压枪/甩枪精度要求极高
| 操作 | 频率 | 可靠性要求 | 延迟容忍 |
|---|---|---|---|
| 射击 | 玩家触发 | 可靠(命中校验) | <20ms |
| 移动同步 | 60-128fps | 不可靠 | <15ms |
| 弹道同步 | 每帧 | 不可靠 | <15ms |
| 购买/换弹 | 偶发 | 可靠 | 100ms |
推荐 :必须 KCP --- WebRTC 的额外 10-20ms 延迟在 128 tick 下会产生 1-2 tick 的偏差,竞技场景不可接受
场景 D:格斗游戏(街霸 / 铁拳)
网络特征:60fps,1帧输入延迟 = 16.6ms,需要帧级回滚
| 操作 | 延迟容忍 |
|---|---|
| 输入同步 | < 16ms(1帧) |
| 状态回滚 | < 32ms(2帧) |
推荐 :必须 KCP --- 格斗游戏是最苛刻的场景,1 帧差距就是生死之别
3.3 决策矩阵
| 游戏类型 | 推荐方案 | WebRTC 可否 | 原因 |
|---|---|---|---|
| 回合制 / 棋牌 | WebSocket | ✅ 备选 | 完全不敏感 |
| 捕鱼 / 休闲 | WebRTC | ✅ 首选 | 不可靠模式广播,弱网表现好 |
| MOBA(LOL 级) | WebRTC | ✅ 可行 | tick 低 + 客户端预测兜底 |
| RTS(星际级) | WebRTC / KCP | ⚠️ 勉强 | 大量单位同步,弱网敏感 |
| 大逃杀(PUBG 级) | WebRTC / KCP | ⚠️ 勉强 | 比竞技 FPS 稍宽容 |
| 竞技 FPS(CS2 级) | KCP | ❌ | 20ms 差距决定胜负 |
| 格斗(街霸级) | KCP | ❌ | 1 帧 (16ms) 就是天壤之别 |
4. 基于 kratos-transport 的实战架构
4.1 架构总览
kratos-transport 项目已经提供了 KCP、WebRTC、WebSocket 三种传输模块,它们共享相同的消息处理架构:
#mermaid-svg-DLghOTj8Dh2Vkejl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-DLghOTj8Dh2Vkejl .error-icon{fill:#552222;}#mermaid-svg-DLghOTj8Dh2Vkejl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-DLghOTj8Dh2Vkejl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-DLghOTj8Dh2Vkejl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-DLghOTj8Dh2Vkejl .marker.cross{stroke:#333333;}#mermaid-svg-DLghOTj8Dh2Vkejl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-DLghOTj8Dh2Vkejl p{margin:0;}#mermaid-svg-DLghOTj8Dh2Vkejl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-DLghOTj8Dh2Vkejl .cluster-label text{fill:#333;}#mermaid-svg-DLghOTj8Dh2Vkejl .cluster-label span{color:#333;}#mermaid-svg-DLghOTj8Dh2Vkejl .cluster-label span p{background-color:transparent;}#mermaid-svg-DLghOTj8Dh2Vkejl .label text,#mermaid-svg-DLghOTj8Dh2Vkejl span{fill:#333;color:#333;}#mermaid-svg-DLghOTj8Dh2Vkejl .node rect,#mermaid-svg-DLghOTj8Dh2Vkejl .node circle,#mermaid-svg-DLghOTj8Dh2Vkejl .node ellipse,#mermaid-svg-DLghOTj8Dh2Vkejl .node polygon,#mermaid-svg-DLghOTj8Dh2Vkejl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-DLghOTj8Dh2Vkejl .rough-node .label text,#mermaid-svg-DLghOTj8Dh2Vkejl .node .label text,#mermaid-svg-DLghOTj8Dh2Vkejl .image-shape .label,#mermaid-svg-DLghOTj8Dh2Vkejl .icon-shape .label{text-anchor:middle;}#mermaid-svg-DLghOTj8Dh2Vkejl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-DLghOTj8Dh2Vkejl .rough-node .label,#mermaid-svg-DLghOTj8Dh2Vkejl .node .label,#mermaid-svg-DLghOTj8Dh2Vkejl .image-shape .label,#mermaid-svg-DLghOTj8Dh2Vkejl .icon-shape .label{text-align:center;}#mermaid-svg-DLghOTj8Dh2Vkejl .node.clickable{cursor:pointer;}#mermaid-svg-DLghOTj8Dh2Vkejl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-DLghOTj8Dh2Vkejl .arrowheadPath{fill:#333333;}#mermaid-svg-DLghOTj8Dh2Vkejl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-DLghOTj8Dh2Vkejl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-DLghOTj8Dh2Vkejl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DLghOTj8Dh2Vkejl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-DLghOTj8Dh2Vkejl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DLghOTj8Dh2Vkejl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-DLghOTj8Dh2Vkejl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-DLghOTj8Dh2Vkejl .cluster text{fill:#333;}#mermaid-svg-DLghOTj8Dh2Vkejl .cluster span{color:#333;}#mermaid-svg-DLghOTj8Dh2Vkejl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-DLghOTj8Dh2Vkejl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-DLghOTj8Dh2Vkejl rect.text{fill:none;stroke-width:0;}#mermaid-svg-DLghOTj8Dh2Vkejl .icon-shape,#mermaid-svg-DLghOTj8Dh2Vkejl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-DLghOTj8Dh2Vkejl .icon-shape p,#mermaid-svg-DLghOTj8Dh2Vkejl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-DLghOTj8Dh2Vkejl .icon-shape .label rect,#mermaid-svg-DLghOTj8Dh2Vkejl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-DLghOTj8Dh2Vkejl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-DLghOTj8Dh2Vkejl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-DLghOTj8Dh2Vkejl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Game Server --- 共享消息处理逻辑
RegisterMessageHandler
SessionManager
NetPacket (Type + Payload)
KCP Server
WebRTC Server
WebSocket Server
原生客户端
(低延迟)
浏览器
(P2P)
Web 客户端
(最大兼容)
4.2 统一消息协议
三种传输模块使用相同的消息封装格式:
go
// 网络消息包 --- 所有传输模块通用
type NetPacket struct {
Type NetMessageType // 消息类型(uint32)
Payload []byte // 编码后的载荷
}
消息类型注册(以射击游戏为例):
go
const (
MsgPlayerMove NetMessageType = 1 // 玩家移动(不可靠)
MsgPlayerShoot NetMessageType = 2 // 射击(可靠)
MsgEntitySync NetMessageType = 3 // 实体同步(不可靠)
MsgChat NetMessageType = 10 // 聊天(可靠)
MsgStateChange NetMessageType = 11 // 状态变化(可靠)
)
4.3 KCP 服务端
go
package main
import (
kcpTransport "github.com/tx7do/kratos-transport/transport/kcp"
)
func main() {
srv := kcpTransport.NewServer(
kcpTransport.WithAddress(":8080"),
// KCP 加密
kcpTransport.WithBlockCrypt("password", "salt"),
// FEC 纠错(前向纠错,可选)
kcpTransport.WithFEC(10, 3), // 10 数据片 + 3 校验片
)
// 注册消息处理器
kcpTransport.RegisterServerMessageHandler[PlayerMoveMsg](
srv, MsgPlayerMove, handlePlayerMove,
)
kcpTransport.RegisterServerMessageHandler[ShootMsg](
srv, MsgPlayerShoot, handleShoot,
)
srv.Start(context.Background())
}
关键优势:
- FEC 前向纠错 --- 丢包不需要重传,直接恢复
- 加密传输 --- BlockCrypt 基于 AES/TEA/XOR
- 可调窗口 ---
kcp.NoDelay(1, 10, 2, 1)极速模式
4.4 WebRTC 服务端
go
package main
import (
webrtcTransport "github.com/tx7do/kratos-transport/transport/webrtc"
)
func main() {
srv := webrtcTransport.NewServer(
webrtcTransport.WithAddress(":8080"),
webrtcTransport.WithSignalPath("/signal"),
// ICE 服务器配置
webrtcTransport.WithICEServers([]webrtc.ICEServer{
{URLs: []string{"stun:stun.l.google.com:19302"}},
}),
// 数据通道配置
webrtcTransport.WithDataChannelLabel("game"),
)
// 注册消息处理器(与 KCP 完全一致)
webrtcTransport.RegisterServerMessageHandler[PlayerMoveMsg](
srv, MsgPlayerMove, handlePlayerMove,
)
webrtcTransport.RegisterServerMessageHandler[ShootMsg](
srv, MsgPlayerShoot, handleShoot,
)
srv.Start(context.Background())
}
浏览器端连接:
javascript
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
});
// 创建不可靠数据通道(用于位置同步)
const unreliableChannel = pc.createDataChannel('game', {
ordered: false,
maxRetransmits: 0 // 不可靠模式!
});
// 或创建可靠数据通道(用于命中判定)
const reliableChannel = pc.createDataChannel('game-reliable', {
ordered: true,
maxRetransmits: null // 可靠模式(默认)
});
unreliableChannel.onmessage = (event) => {
const data = new Uint8Array(event.data);
// 解析 NetPacket,与 Go 端格式一致
const msgType = new DataView(data.buffer).getUint32(0, true);
const payload = data.slice(4);
handleGameMessage(msgType, payload);
};
// 信令流程
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
const response = await fetch('/signal', {
method: 'POST',
body: JSON.stringify({ offer: pc.localDescription })
});
const { answer } = await response.json();
await pc.setRemoteDescription(answer);
4.5 业务逻辑复用
三种传输方式共享完全相同的处理函数:
go
// 这个函数同时服务于 KCP 和 WebRTC 的 Server
func handlePlayerMove(sessionId SessionID, msg *PlayerMoveMsg) error {
// 1. 服务端权威:验证移动合法性
if !validateMove(msg) {
return nil
}
// 2. 更新游戏状态
player.UpdatePosition(msg.X, msg.Y, msg.Z)
// 3. 广播给其他玩家(使用不可靠发送)
// KCP Server 和 WebRTC Server 都有 Broadcast 方法
srv.Broadcast(MsgEntitySync, &EntitySyncMsg{
PlayerID: player.ID,
X: player.X, Y: player.Y, Z: player.Z,
})
return nil
}
5. 为什么不应该做 KCP over WebRTC
5.1 协议栈冲突
如果将 KCP 跑在 WebRTC DataChannel 上:
#mermaid-svg-ZXSCluIO4nE4H72L{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ZXSCluIO4nE4H72L .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ZXSCluIO4nE4H72L .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ZXSCluIO4nE4H72L .error-icon{fill:#552222;}#mermaid-svg-ZXSCluIO4nE4H72L .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ZXSCluIO4nE4H72L .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ZXSCluIO4nE4H72L .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ZXSCluIO4nE4H72L .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ZXSCluIO4nE4H72L .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ZXSCluIO4nE4H72L .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ZXSCluIO4nE4H72L .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ZXSCluIO4nE4H72L .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ZXSCluIO4nE4H72L .marker.cross{stroke:#333333;}#mermaid-svg-ZXSCluIO4nE4H72L svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ZXSCluIO4nE4H72L p{margin:0;}#mermaid-svg-ZXSCluIO4nE4H72L .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ZXSCluIO4nE4H72L .cluster-label text{fill:#333;}#mermaid-svg-ZXSCluIO4nE4H72L .cluster-label span,#mermaid-svg-ZXSCluIO4nE4H72L p{color:#333;}#mermaid-svg-ZXSCluIO4nE4H72L .label text,#mermaid-svg-ZXSCluIO4nE4H72L span,#mermaid-svg-ZXSCluIO4nE4H72L p{fill:#333;color:#333;}#mermaid-svg-ZXSCluIO4nE4H72L .node rect,#mermaid-svg-ZXSCluIO4nE4H72L .node circle,#mermaid-svg-ZXSCluIO4nE4H72L .node ellipse,#mermaid-svg-ZXSCluIO4nE4H72L .node polygon,#mermaid-svg-ZXSCluIO4nE4H72L .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ZXSCluIO4nE4H72L .flowchart-label text{text-anchor:middle;}#mermaid-svg-ZXSCluIO4nE4H72L .node .label{text-align:center;}#mermaid-svg-ZXSCluIO4nE4H72L .node.clickable{cursor:pointer;}#mermaid-svg-ZXSCluIO4nE4H72L .arrowheadPath{fill:#333333;}#mermaid-svg-ZXSCluIO4nE4H72L .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ZXSCluIO4nE4H72L .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ZXSCluIO4nE4H72L .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ZXSCluIO4nE4H72L .edgeLabel p{margin:0;padding:0;display:inline;}#mermaid-svg-ZXSCluIO4nE4H72L .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ZXSCluIO4nE4H72L .labelBkg{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ZXSCluIO4nE4H72L .node .cluster{fill:rgba(255, 255, 222, 0.5);stroke:rgba(170, 170, 51, 0.2);box-shadow:rgba(50, 50, 93, 0.25) 0px 13px 27px -5px,rgba(0, 0, 0, 0.3) 0px 8px 16px -8px;stroke-width:1px;}#mermaid-svg-ZXSCluIO4nE4H72L .cluster text{fill:#333;}#mermaid-svg-ZXSCluIO4nE4H72L .cluster span,#mermaid-svg-ZXSCluIO4nE4H72L p{color:#333;}#mermaid-svg-ZXSCluIO4nE4H72L div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ZXSCluIO4nE4H72L .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ZXSCluIO4nE4H72L .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ZXSCluIO4nE4H72L .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ZXSCluIO4nE4H72L :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} KCP over WebRTC --- 双重可靠性冲突
Game Logic
KCP ARQ --- 第一层可靠性
SCTP --- 第二层可靠性(冲突!)
DTLS --- 额外加密开销
ICE / UDP
问题一览:
| 问题 | 说明 |
|---|---|
| 双重可靠性 | KCP ARQ + SCTP ARQ = 双重重传、双重 ACK,纯浪费带宽 |
| 双重拥塞控制 | KCP 的带宽探测与 SCTP 的 CMT 互相干扰,吞吐量反而下降 |
| 延迟叠加 | SCTP 的保守重传 + KCP 的激进重传 = 延迟不减反增 |
| Header 膨胀 | KCP(24) + SCTP(16) + DTLS(13) = 53 字节,有效载荷率骤降 |
| 浏览器端复杂 | 需要一个 JS/WASM 版本的 KCP 实现,维护成本极高 |
5.2 正确的做法
不是"KCP over WebRTC",而是"根据场景选择 KCP 或 WebRTC":
#mermaid-svg-XdCeAbUowE1ydZ8g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-XdCeAbUowE1ydZ8g .error-icon{fill:#552222;}#mermaid-svg-XdCeAbUowE1ydZ8g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-XdCeAbUowE1ydZ8g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-XdCeAbUowE1ydZ8g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-XdCeAbUowE1ydZ8g .marker.cross{stroke:#333333;}#mermaid-svg-XdCeAbUowE1ydZ8g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-XdCeAbUowE1ydZ8g p{margin:0;}#mermaid-svg-XdCeAbUowE1ydZ8g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-XdCeAbUowE1ydZ8g .cluster-label text{fill:#333;}#mermaid-svg-XdCeAbUowE1ydZ8g .cluster-label span{color:#333;}#mermaid-svg-XdCeAbUowE1ydZ8g .cluster-label span p{background-color:transparent;}#mermaid-svg-XdCeAbUowE1ydZ8g .label text,#mermaid-svg-XdCeAbUowE1ydZ8g span{fill:#333;color:#333;}#mermaid-svg-XdCeAbUowE1ydZ8g .node rect,#mermaid-svg-XdCeAbUowE1ydZ8g .node circle,#mermaid-svg-XdCeAbUowE1ydZ8g .node ellipse,#mermaid-svg-XdCeAbUowE1ydZ8g .node polygon,#mermaid-svg-XdCeAbUowE1ydZ8g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-XdCeAbUowE1ydZ8g .rough-node .label text,#mermaid-svg-XdCeAbUowE1ydZ8g .node .label text,#mermaid-svg-XdCeAbUowE1ydZ8g .image-shape .label,#mermaid-svg-XdCeAbUowE1ydZ8g .icon-shape .label{text-anchor:middle;}#mermaid-svg-XdCeAbUowE1ydZ8g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-XdCeAbUowE1ydZ8g .rough-node .label,#mermaid-svg-XdCeAbUowE1ydZ8g .node .label,#mermaid-svg-XdCeAbUowE1ydZ8g .image-shape .label,#mermaid-svg-XdCeAbUowE1ydZ8g .icon-shape .label{text-align:center;}#mermaid-svg-XdCeAbUowE1ydZ8g .node.clickable{cursor:pointer;}#mermaid-svg-XdCeAbUowE1ydZ8g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-XdCeAbUowE1ydZ8g .arrowheadPath{fill:#333333;}#mermaid-svg-XdCeAbUowE1ydZ8g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-XdCeAbUowE1ydZ8g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-XdCeAbUowE1ydZ8g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XdCeAbUowE1ydZ8g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-XdCeAbUowE1ydZ8g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XdCeAbUowE1ydZ8g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-XdCeAbUowE1ydZ8g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-XdCeAbUowE1ydZ8g .cluster text{fill:#333;}#mermaid-svg-XdCeAbUowE1ydZ8g .cluster span{color:#333;}#mermaid-svg-XdCeAbUowE1ydZ8g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-XdCeAbUowE1ydZ8g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-XdCeAbUowE1ydZ8g rect.text{fill:none;stroke-width:0;}#mermaid-svg-XdCeAbUowE1ydZ8g .icon-shape,#mermaid-svg-XdCeAbUowE1ydZ8g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-XdCeAbUowE1ydZ8g .icon-shape p,#mermaid-svg-XdCeAbUowE1ydZ8g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-XdCeAbUowE1ydZ8g .icon-shape .label rect,#mermaid-svg-XdCeAbUowE1ydZ8g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-XdCeAbUowE1ydZ8g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-XdCeAbUowE1ydZ8g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-XdCeAbUowE1ydZ8g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 最低延迟
浏览器最佳选择
最大兼容
弱网体验差
原生客户端
(PC / 主机 / 手游)
KCP
浏览器客户端
WebRTC DataChannel
Web 备选方案
WebSocket
三者共享业务逻辑,传输层按平台选择最优方案。
6. WebRTC DataChannel 的不可靠模式详解
WebRTC 的不可靠模式是浏览器端做实时游戏的关键能力。通过配置 DataChannel 参数:
javascript
// 不可靠 + 无序 --- 适合位置同步、状态广播
const unreliableDC = pc.createDataChannel('game-unreliable', {
ordered: false, // 不保证顺序
maxRetransmits: 0 // 不重传(完全不可靠)
});
// 不可靠 + 有序 --- 适合需要顺序但不关心偶尔丢包的场景
const sequencedDC = pc.createDataChannel('game-sequenced', {
ordered: true, // 保证顺序
maxRetransmits: 0 // 不重传(只投递最新的)
});
// 可靠 + 有序 --- 适合命中判定、交易等关键操作
const reliableDC = pc.createDataChannel('game-reliable', {
ordered: true, // 保证顺序
maxRetransmits: null, // 无限重传(完全可靠)
// 或使用 maxPacketLifetime 替代
// maxPacketLifetime: 3000 // 3秒内可靠
});
// 部分可靠 --- 有限重传
const partialDC = pc.createDataChannel('game-partial', {
ordered: false,
maxRetransmits: 3 // 最多重传3次
});
SCTP 的可靠性模型:
#mermaid-svg-4ENdc9PqAgXxxcOq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-4ENdc9PqAgXxxcOq .error-icon{fill:#552222;}#mermaid-svg-4ENdc9PqAgXxxcOq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-4ENdc9PqAgXxxcOq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-4ENdc9PqAgXxxcOq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-4ENdc9PqAgXxxcOq .marker.cross{stroke:#333333;}#mermaid-svg-4ENdc9PqAgXxxcOq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-4ENdc9PqAgXxxcOq p{margin:0;}#mermaid-svg-4ENdc9PqAgXxxcOq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-4ENdc9PqAgXxxcOq .cluster-label text{fill:#333;}#mermaid-svg-4ENdc9PqAgXxxcOq .cluster-label span{color:#333;}#mermaid-svg-4ENdc9PqAgXxxcOq .cluster-label span p{background-color:transparent;}#mermaid-svg-4ENdc9PqAgXxxcOq .label text,#mermaid-svg-4ENdc9PqAgXxxcOq span{fill:#333;color:#333;}#mermaid-svg-4ENdc9PqAgXxxcOq .node rect,#mermaid-svg-4ENdc9PqAgXxxcOq .node circle,#mermaid-svg-4ENdc9PqAgXxxcOq .node ellipse,#mermaid-svg-4ENdc9PqAgXxxcOq .node polygon,#mermaid-svg-4ENdc9PqAgXxxcOq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-4ENdc9PqAgXxxcOq .rough-node .label text,#mermaid-svg-4ENdc9PqAgXxxcOq .node .label text,#mermaid-svg-4ENdc9PqAgXxxcOq .image-shape .label,#mermaid-svg-4ENdc9PqAgXxxcOq .icon-shape .label{text-anchor:middle;}#mermaid-svg-4ENdc9PqAgXxxcOq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-4ENdc9PqAgXxxcOq .rough-node .label,#mermaid-svg-4ENdc9PqAgXxxcOq .node .label,#mermaid-svg-4ENdc9PqAgXxxcOq .image-shape .label,#mermaid-svg-4ENdc9PqAgXxxcOq .icon-shape .label{text-align:center;}#mermaid-svg-4ENdc9PqAgXxxcOq .node.clickable{cursor:pointer;}#mermaid-svg-4ENdc9PqAgXxxcOq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-4ENdc9PqAgXxxcOq .arrowheadPath{fill:#333333;}#mermaid-svg-4ENdc9PqAgXxxcOq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-4ENdc9PqAgXxxcOq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-4ENdc9PqAgXxxcOq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ENdc9PqAgXxxcOq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-4ENdc9PqAgXxxcOq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ENdc9PqAgXxxcOq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-4ENdc9PqAgXxxcOq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-4ENdc9PqAgXxxcOq .cluster text{fill:#333;}#mermaid-svg-4ENdc9PqAgXxxcOq .cluster span{color:#333;}#mermaid-svg-4ENdc9PqAgXxxcOq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-4ENdc9PqAgXxxcOq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-4ENdc9PqAgXxxcOq rect.text{fill:none;stroke-width:0;}#mermaid-svg-4ENdc9PqAgXxxcOq .icon-shape,#mermaid-svg-4ENdc9PqAgXxxcOq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-4ENdc9PqAgXxxcOq .icon-shape p,#mermaid-svg-4ENdc9PqAgXxxcOq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-4ENdc9PqAgXxxcOq .icon-shape .label rect,#mermaid-svg-4ENdc9PqAgXxxcOq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-4ENdc9PqAgXxxcOq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-4ENdc9PqAgXxxcOq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-4ENdc9PqAgXxxcOq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SCTP DataChannel 可靠性模型
ordered=true + maxRetransmits=null
→ 可靠有序
ordered=true + maxRetransmits=0
→ 顺序投递
ordered=false + maxRetransmits=null
→ 可靠无序
ordered=false + maxRetransmits=0
→ 完全不可靠
ordered=false + maxRetransmits=N
→ 部分可靠
ordered=true + maxPacketLifetime=T
→ 限时可靠
7. 弱网对抗策略
7.1 KCP 的弱网优化
go
// 极速模式 --- 牺牲带宽换取最低延迟
session.SetNoDelay(1, 10, 2, 1)
// 参数说明:
// nodelay=1 : 启用无延迟模式
// interval=10: 内部刷新间隔 10ms
// resend=2 : 快速重传,2 个 ACK 跨越即重传
// nc=1 : 关闭拥塞控制,全力发送
// FEC 前向纠错 --- 无需重传即可恢复丢包
// 10 个数据片 + 3 个校验片 = 可容忍 3 个包丢失
listener, _ := kcp.ListenWithOptions(":8080", block, 10, 3)
// 发送窗口
session.SetWindowSize(1024, 1024)
7.2 WebRTC 的弱网对抗
WebRTC 的弱网对抗依赖 SCTP 内部机制,无法像 KCP 那样精细调节。但可以结合应用层策略:
javascript
// 客户端侧:插值 + 预测
class GameState {
constructor() {
this.entities = new Map();
this.lastUpdate = 0;
}
// 收到服务端位置更新
onEntitySync(data) {
const entity = this.entities.get(data.playerId);
if (!entity) return;
// 客户端插值:平滑过渡到新位置
entity.targetX = data.x;
entity.targetY = data.y;
entity.lastSyncTime = Date.now();
}
// 每帧渲染
update(deltaTime) {
for (const entity of this.entities.values()) {
// 线性插值(Lerp)平滑位置
const lerpFactor = Math.min(1, deltaTime * 10);
entity.renderX += (entity.targetX - entity.renderX) * lerpFactor;
entity.renderY += (entity.targetY - entity.renderY) * lerpFactor;
}
}
}
7.3 通用弱网策略
无论使用哪种协议,都应实现以下应用层策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 客户端预测 | 本地先执行,服务端后纠正 | 移动、射击 |
| 服务端回滚 | 收到延迟输入后回滚状态重算 | 竞技 FPS、格斗 |
| 实体插值 | 平滑过渡其他玩家位置 | 所有实时游戏 |
| 冗余发送 | 关键数据连续发 2-3 次 | 命中判定 |
| 快照压缩 | 只发送增量(delta) | 大量实体同步 |
| 优先级队列 | 关键消息优先投递 | 混合可靠/不可靠 |
8. 总结
选择决策树
#mermaid-svg-cCB6YAg5Wf7LK2TS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-cCB6YAg5Wf7LK2TS .error-icon{fill:#552222;}#mermaid-svg-cCB6YAg5Wf7LK2TS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-cCB6YAg5Wf7LK2TS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .marker.cross{stroke:#333333;}#mermaid-svg-cCB6YAg5Wf7LK2TS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-cCB6YAg5Wf7LK2TS p{margin:0;}#mermaid-svg-cCB6YAg5Wf7LK2TS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .cluster-label text{fill:#333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .cluster-label span{color:#333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .cluster-label span p{background-color:transparent;}#mermaid-svg-cCB6YAg5Wf7LK2TS .label text,#mermaid-svg-cCB6YAg5Wf7LK2TS span{fill:#333;color:#333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .node rect,#mermaid-svg-cCB6YAg5Wf7LK2TS .node circle,#mermaid-svg-cCB6YAg5Wf7LK2TS .node ellipse,#mermaid-svg-cCB6YAg5Wf7LK2TS .node polygon,#mermaid-svg-cCB6YAg5Wf7LK2TS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-cCB6YAg5Wf7LK2TS .rough-node .label text,#mermaid-svg-cCB6YAg5Wf7LK2TS .node .label text,#mermaid-svg-cCB6YAg5Wf7LK2TS .image-shape .label,#mermaid-svg-cCB6YAg5Wf7LK2TS .icon-shape .label{text-anchor:middle;}#mermaid-svg-cCB6YAg5Wf7LK2TS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-cCB6YAg5Wf7LK2TS .rough-node .label,#mermaid-svg-cCB6YAg5Wf7LK2TS .node .label,#mermaid-svg-cCB6YAg5Wf7LK2TS .image-shape .label,#mermaid-svg-cCB6YAg5Wf7LK2TS .icon-shape .label{text-align:center;}#mermaid-svg-cCB6YAg5Wf7LK2TS .node.clickable{cursor:pointer;}#mermaid-svg-cCB6YAg5Wf7LK2TS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .arrowheadPath{fill:#333333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-cCB6YAg5Wf7LK2TS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cCB6YAg5Wf7LK2TS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-cCB6YAg5Wf7LK2TS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cCB6YAg5Wf7LK2TS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-cCB6YAg5Wf7LK2TS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-cCB6YAg5Wf7LK2TS .cluster text{fill:#333;}#mermaid-svg-cCB6YAg5Wf7LK2TS .cluster span{color:#333;}#mermaid-svg-cCB6YAg5Wf7LK2TS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-cCB6YAg5Wf7LK2TS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-cCB6YAg5Wf7LK2TS rect.text{fill:none;stroke-width:0;}#mermaid-svg-cCB6YAg5Wf7LK2TS .icon-shape,#mermaid-svg-cCB6YAg5Wf7LK2TS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-cCB6YAg5Wf7LK2TS .icon-shape p,#mermaid-svg-cCB6YAg5Wf7LK2TS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-cCB6YAg5Wf7LK2TS .icon-shape .label rect,#mermaid-svg-cCB6YAg5Wf7LK2TS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-cCB6YAg5Wf7LK2TS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-cCB6YAg5Wf7LK2TS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-cCB6YAg5Wf7LK2TS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
是
否
看延迟
小于 30ms
大于 30ms
需要浏览器支持?
KCP(最佳性能)
需要不可靠传输?
WebRTC DataChannel
WebSocket(更简单)
延迟敏感度?
建议原生客户端 + KCP
WebRTC DataChannel
一句话总结
不是所有游戏都需要 KCP,但所有浏览器实时游戏都需要 WebRTC DataChannel 的不可靠模式。 WebSocket 的 TCP 队头阻塞是实时游戏的致命伤------当你在弱网下看到鱼群瞬移、英雄瞬移、子弹延迟发射,那就是 TCP 队头阻塞在作祟。
本文基于 kratos-transport 项目的 KCP、WebRTC、WebSocket 传输模块的实际实现编写。项目地址:https://github.com/tx7do/kratos-transport