Cloudflare TURN 实战指南:WebRTC 长通话不掉线的 5 个关键设计
Cloudflare TURN 实战指南WebRTC 长通话不掉线的 5 个关键设计【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills想象这样一个场景用户开了一场 WebRTC 视频会议前半小时一切正常随后画面突然卡住、声音消失重连提示iceConnectionState failed。这类断在半路的通话往往不是网络真断了而是凭证悄悄过期、端口被浏览器拦截或者缺少 ICE 重启的兜底逻辑。Cloudflare TURN托管中继服务覆盖全球 310 城市的 anycast 网络不含中国网络的价值正是在 NAT 或防火墙挡住 P2P 直连时接管流量。本文基于 skills/.curated/cloudflare-deploy/references/turn/ 模块的实现模式按接通前 → 通话中 → 断线时 → 上线前的时间线把一套可直接落地的 TURN 接入方案讲清楚。一通会自己断掉的通话问题通常出在哪在写任何客户端代码之前值得先弄清楚两件事凭证从哪来、为什么必须自己发。Cloudflare TURN 的临时凭证由你的服务端调用POST https://rtc.live.cloudflare.com/v1/turn/keys/{key_id}/credentials/generate请求体{ttl: 3600}Header 带Authorization: Bearer {key_secret}换取响应里包含iceServers.urls、username形如1738035200:user123和credentialBase64 编码的 HMAC。也就是说密钥永远留在服务端浏览器只拿得到短期凭证——这也是为什么很多团队会专门部署一个 Cloudflare Worker 来承接这个签发动作。签发之前需要先准备一把 TURN KeyPOST /accounts/{account_id}/calls/turn_keys Content-Type: application/json { name: my-turn-key }Base URL 是https://api.cloudflare.com/client/v4所有端点都要求带 Calls Write 权限的 API Token。响应中的key实际密钥只在创建时返回一次必须立即保存uid、name、created、modified则用于后续管理。Key 还支持列出、查看、改名、删除等操作细节见 api.md。Worker 侧的集成要点在 configuration.md非敏感的TURN_KEY_ID放进 wrangler 的vars敏感的密钥用wrangler secret put TURN_KEY_SECRET单独注入生产环境可以绑定一个名为CREDENTIALS_CACHE的 KV 命名空间做缓存。Worker 收到浏览器请求后调用凭证生成端点在返回前过滤掉 53 端口的 URL原因见下文再把带 STUN 的完整iceServers交给客户端。接通前客户端怎么配端口怎么选浏览器拿到凭证后构造RTCIceServer数组交给RTCPeerConnection。推荐的组合是公开 STUN 多协议 TURNconst data await fetch(/api/turn-credentials).then(r r.json()); const iceServers [ { urls: stun:stun.cloudflare.com:3478 }, { urls: [ turn:turn.cloudflare.com:3478?transportudp, turn:turn.cloudflare.com:3478?transporttcp, turns:turn.cloudflare.com:5349?transporttcp, turns:turn.cloudflare.com:443?transporttcp ], username: data.username, credential: data.credential, credentialType: password } ]; const pc new RTCPeerConnection({ iceServers });分工很清晰STUN 负责让客户端发现自己的公网候选TURN 在直连走不通时接力做中继最终由 ICE 协商自动择优。端口顺序建议遵循延迟优先、兼容性兜底的原则3478/udp延迟最低放第一位UDP 被封时退回3478/tcp企业防火墙下5349/tls最可靠443/tls作为防火墙友好的备用。这里藏着一个最隐蔽的坑端口 53 在浏览器里是静默失败的。凭证生成 API 的响应中会包含turn:turn.cloudflare.com:53?transportudp、turn:turn.cloudflare.com:80?transporttcp这类地址它们对非浏览器客户端可用但 Chrome 和 Firefox 会拦截 53 端口流量。所以过滤逻辑应该放在服务端Worker 返回前做一次!url.includes(:53)而不是指望每个前端各自处理。通话中凭证过期前要做的事临时凭证的 TTL 上限是48 小时172800 秒超过会被 API 直接拒绝仓库示例里常用 3600 秒。凭证一旦到期通话就会在某个时刻无声地断开——长通话必须提前续命。刷新凭证靠setConfiguration()更新iceServers但要注意一个反直觉的细节它不会触发 ICE 重启。如果连接已经失败光刷新凭证是不够的还得配合restartIce()async function refreshTURNCredentials(pc: RTCPeerConnection) { const newCreds await fetch(/turn-credentials).then(r r.json()); const config pc.getConfiguration(); config.iceServers newCreds.iceServers; pc.setConfiguration(config); // 注意这不会触发 ICE 重启 } setInterval(() refreshTURNCredentials(pc), ttl * 1000 - 60000); // 提前 1 分钟ttl * 1000 - 60000提前一分钟是 gotchas.md 给出的推荐刷新时机。TTL 为 1 小时时大约每 50 分钟刷新一次。服务端侧还可以进一步减少打向生成端点的请求用一个内存管理器缓存未过期凭证本地校验 TTL 上限缓存有效期同样比 TTL 提前 1 分钟预留刷新窗口async getCredentials(keyId: string, keySecret: string) { if (this.creds this.creds.expiresAt Date.now()) { return this.buildIceServers(this.creds); } const ttl 3600; if (ttl 172800) throw new Error(TTL max 48hrs); // 调用 generate 端点过滤 :53 端口写入缓存 // expiresAt now ttl*1000 - 60000 }另外两个常被忽略的能力一是吊销调用POST .../credentials/revokebody 传{username: ...}返回 204计费立即停止被吊销会话的活跃连接会在数秒内断开——这是处理被攻陷会话的应急手段二是计费边界与 Cloudflare Calls SFU 搭配使用时 TURN 免费否则按 $0.05/GB 出站流量计费所以先直连、后中继的策略同时也是省钱策略。断线时ICE 重启的触发条件与恢复流程网络切换、TURN 服务器维护Cloudflare 网络上偶尔发生、anycast 路由调整、长会话中的凭证刷新——这四种情况发生后iceconnectionstatechange会进入failed。生产级做法是依次执行刷新凭证 →restartIce()→ 重新创建带iceRestart: true的 offer → 通过信令发给对端pc.addEventListener(iceconnectionstatechange, async () { if (pc.iceConnectionState failed || pc.iceConnectionState disconnected) { await refreshTURNCredentials(pc); pc.restartIce(); const offer await pc.createOffer({ iceRestart: true }); await pc.setLocalDescription(offer); // 通过信令通道把 offer 发给对端 } });多纳一个disconnected状态是个值得做的小改进移动用户在地铁里切网时状态常常先短暂进入disconnected再恢复提前介入能避免真的掉线。不同业务对连通性和开销的取舍不同可以用两个参数控制 ICE 行为视频会议用iceTransportPolicy: all先试 P2P失败再走中继IoT 这类要求连通性可预期的场景用relay强制全走 TURN屏幕共享则加bundlePolicy: max-bundle把多路媒体聚合成单条传输降低开销。如果整体接入 Cloudflare Calls SFUTURN 会在需要时自动启用客户端无需自己编排协调const session await callsClient.createSession({ appId: your-app-id, sessionId: meeting-123 });上线前自查排障手段、常见坑与安全边界三个观察窗口icecandidate事件看候选的typehost/srflx/relay与protocol确认有没有出现 relay 候选iceconnectionstatechange追踪checking → connected → completed或failed的状态流转getStats()里selected为 true 的candidate-pair报告直接告诉你流量最终走的是直连还是 TURN 中继。如果连接建立慢按顺序排查候选收集是否完整、到 Cloudflare 边缘的延迟、防火墙是否放行 3478/5349/443以及企业网络是否该改走 443 上的 TURN over TLS。最容易踩的几个坑TTL 设成6048007 天超 48 小时 API 直接拒绝按预期会话时长设如86400硬编码 IP 如turn:141.101.90.1:3478IP 可能提前 14 天通知后变更应使用 DNS 域名turn.cloudflare.com浏览器端保留:53端口 URL改为服务端统一过滤凭证到期不刷新用setInterval提前 1 分钟刷新掉线后只打日志不恢复failed/disconnected时刷新凭证并restartIce()把TURN_KEY_SECRET写进前端密钥只留在服务端客户端只请求自己的/api/turn-credentials。限额与网络边界单分配按用户非账户级的限额是每秒超过 5 个新唯一 IP、包速率超过 5-10k pps、数据速率超过 50-100 Mbps都会表现为丢包——高丢包问题时优先核对这三项。安全方面上线前建议自查凭证仅服务端签发且先做客户端认证、TURN_KEY_SECRET走 wrangler secrets、TTL 不超会话时长、凭证端点做限流、保留吊销 API、浏览器客户端已过滤 53 端口。企业防火墙环境可以把turn.cloudflare.com白名单化为 IPv4141.101.90.1/32、162.159.207.1/32与 IPv62a06:98c1:3200::1/128、2606:4700:48::1/128但记得用dig turn.cloudflare.com A/dig turn.cloudflare.com AAAA做自动监控在 14 天窗口内更新白名单。最后两个部署边界值得知晓客户端到 TURN 支持 IPv4/IPv6但中继地址只分配 IPv4不支持 RFC 6156TCP 中继RFC 6062也不支持——IPv6 客户端可以接入中继流量仍走 IPv4TLS 方面 1.1/1.2/1.3 均支持TLS 1.3 推荐AEAD-AES128-GCM-SHA256等 AEAD 套件TLS 1.2 推荐ECDHE-ECDSA-AES128-GCM-SHA256等详见 configuration.md。下一步建议先用最小闭环跑通创建 TURN Key → Worker 签发并过滤 53 端口 → 浏览器端配上 STUN 4 个 TURN URL确认getStats()里出现被选中的 relay 候选对把提前 1 分钟刷新凭证和failed/disconnected时 ICE 重启两条保活链路加进所有长通话场景并做一次断网/切网演练验证恢复流程对照上面的安全清单完成上线前自查尤其确认密钥只在服务端、凭证端点已限流、IP 白名单有监控。更多细节可参考同模块的 patterns.md完整实现模式、api.md凭证与 Key 管理 API与 gotchas.md陷阱与排查。【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考