Cloudflare TURN 完整实战指南:用 48 小时中继凭证与 ICE 自愈保活 WebRTC 长通话
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
在 WebRTC 生产环境中,最典型的隐性故障是:通话开了四十多个小时后无声无息地断了。根因往往不是网络,而是 Cloudflare TURN(Traversal Using Relays around NAT,托管中继服务)签发的临时凭证过期且无人续期。本文基于 skills 仓库 cloudflare-deploy 模块下 turn 参考文档,按"拿凭证 → 建连 → 续期 → 自愈 → 观测"这条生命周期主线,讲清如何把 Cloudflare TURN 做成一套可长期运行的中继方案,而不是跑通 Demo 就完事。
一张拓扑图讲清楚:TURN 在中继链路里扮演什么角色
先明确三个角色的分工,避免后面每个环节反复解释:
| 角色 | 职责 | 你接触它的时机 |
|---|---|---|
STUN(stun.cloudflare.com:3478) | 帮客户端发现自己对公网的候选地址,促成直连 | 每次建连,成本极低 |
TURN(turn.cloudflare.com,UDP/TCP/TLS) | 直连被 NAT 或防火墙挡死时,流量经 it 中继转发 | 直连失败时的兜底 |
| 你自己的 Worker | 用 TURN Key 向凭证端点换取临时凭证,再下发给浏览器 | 每个客户端会话开始前 |
关键背景:Cloudflare TURN 跑在覆盖 310+ 城市的全球 anycast 网络上(不含中国网络),客户端自动落到最近的边缘节点,你不需要选区、不需要管服务器列表。费用上有一个重要分叉:搭配 Cloudflare Calls SFU 使用时 TURN 免费;独立使用则按 $0.05/GB 出站流量计费(见 gotchas.md 的成本优化章节)。
什么时候值得引入 TURN?对称型 NAT 挡直连、企业防火墙封 WebRTC 端口、移动网络的运营商级 NAT,以及"宁可慢一点也要连得上"的可预测性场景。反过来,纯 P2P 能覆盖的内部网络就别强行上 TURN——iceTransportPolicy的取舍后文再说。
决策点一:凭证从哪来、活多久
TURN 凭证是"钥匙派生出的临时密码",整条链路的信任边界都在服务端。
第一步:创建 TURN Key(一次性操作)
POST /accounts/{account_id}/calls/turn_keys # Base URL: https://api.cloudflare.com/client/v4 # 需要 "Calls Write" 权限的 API Token响应里的key字段只在创建时返回一次,丢了无法找回,必须当场存进 secrets。后续还有GET列表/详情、PUT改名、DELETE删除四类管理端点(详见 api.md)。
第二步:用 Key 换临时凭证
POST https://rtc.live.cloudflare.com/v1/turn/keys/{key_id}/credentials/generate Authorization: Bearer {key_secret} { "ttl": 86400 }响应核心结构是iceServers.urls(STUN 与多协议 TURN 地址混合数组)、username(形如1738035200:user123,时间戳前缀即过期信息)、credential(Base64 编码的 HMAC)。
📌 这里有一条硬约束必须刻进代码:TTL 上限 172800 秒(48 小时),超过会被 API 直接拒绝。很多人写ttl: 604800(7 天)想一劳永逸,结果建连时才发现被拒。正确姿势是"TTL 略长于预期会话时长 + 会话内定时续期",而不是拉满上限。
第三步:把密钥藏进 Worker,而不是客户端
# wrangler.jsonc:TURN_KEY_ID 可放 vars(非敏感) # TURN_KEY_SECRET 必须走: wrangler secret put TURN_KEY_SECRET生产环境还可绑定CREDENTIALS_CACHEKV 命名空间做跨请求缓存。Worker 的职责就三件事:校验浏览器身份 → 用TURN_KEY_SECRET调生成端点 → 过滤后把临时凭证吐给客户端。完整 Worker 骨架见 configuration.md。为什么密钥绝不下发客户端:客户端拿 Key 就等于任何人可以无限免费/计费用你的中继,且凭证吊销都拦不住——这是 gotchas.md 安全清单第一条。
另外留一个"紧急开关":POST .../credentials/revoke(body 传{"username": "..."})返回 204,计费立即停止、活跃连接数秒内断开。给被攻陷或作恶的会话提供这条吊销通道,是生产系统的标配。
决策点二:端口怎么挑,53 端口的隐形地雷
凭证 API 返回的urls是"全量地址",浏览器却只能吃其中一部分:
| 顺序 | 地址 | 适用场景 |
|---|---|---|
| 1 | turn:turn.cloudflare.com:3478?transport=udp | 首选,延迟最低 |
| 2 | turn:turn.cloudflare.com:3478?transport=tcp | UDP 被封网络的回退 |
| 3 | turns:turn.cloudflare.com:5349?transport=tcp | 企业防火墙,最可靠 |
| 4 | turns:turn.cloudflare.com:443?transport=tcp | 备用 TLS 端口,防火墙最友好 |
⚠️ 地雷在这里:响应里还混着turn:turn.cloudflare.com:53?transport=udp和turn:turn.cloudflare.com:80?transport=tcp——非浏览器客户端可用,但 Chrome 和 Firefox 会静默拦截 53 端口,既不报错也不连接,排查时极难察觉。所以过滤逻辑必须放在服务端(Worker 返回前),而不是指望前端兜底:
const filteredUrls = data.iceServers.urls.filter(url => !url.includes(':53'));为什么放服务端:一旦漏过滤,故障只会在"某些用户 + 某些浏览器"上随机出现,而服务端过滤一次生效全体。仓库示例里还按 "UDP > 非 TLS TCP > TLS" 排序,保证 ICE 协商优先尝试低延迟路径(完整实现见 patterns.md)。
决策点三:建连时让 ICE 替你做选择
浏览器端把 STUN 和 TURN 一起交给RTCPeerConnection,由 ICE 协商自动择优——直连能通就走直连,不通自动降级到 TURN 中继:
const iceServers = await (await fetch('/api/turn-credentials')).json(); // iceServers: [{ urls: 'stun:stun.cloudflare.com:3478' }, { urls: [...], username, credential }] const pc = new RTCPeerConnection({ iceServers });为什么两个都配而不是二选一:STUN 成本近乎为零,先直连能省 TURN 流量费;TURN 是保险丝,只在直连失败时真正走流量。
同一个接口还能按业务形态调两档策略:
| 场景 | 配置 | 行为 |
|---|---|---|
| 视频会议 | iceTransportPolicy: 'all' | 先 P2P 直连,失败才中继(默认推荐,也省钱) |
| IoT / 可预测性优先 | iceTransportPolicy: 'relay' | 强制全走 TURN,连通性确定 |
| 屏幕共享等多路流 | bundlePolicy: 'max-bundle' | 多路媒体聚合到单条通道,降开销 |
如果直接用 Cloudflare Calls SFU,TURN 会在需要时自动启用,callsClient.createSession({ appId, sessionId })一行建会话,无需自己编排 TURN 与 SFU 的协调——顺带解锁了前文说的免费额度。
决策点四:续期与自愈,长通话的生死线
这是全文最核心的一段。两个 API 的语义区别决定了你能不能撑过 48 小时:
pc.setConfiguration():能更新iceServers,但不触发 ICE 重启,适合"凭证还没断、提前换粮";pc.restartIce()+createOffer({ iceRestart: true }):真正重新协商,用于连接已经失败后的抢救。
续期(预防):按 TTL 折算刷新间隔,提前 1 分钟换凭证,TTL 1 小时即 50 分钟一次:
const refreshInterval = ttl * 1000 - 60000; // 提前 1 分钟 setInterval(() => refreshTURNCredentials(pc), refreshInterval);为什么提前 1 分钟:给网络抖动和请求失败留出重试窗口,避免"续期请求恰好撞上凭证到期"的竞态。
自愈(抢救):iceconnectionstatechange进入failed(更稳妥是连disconnected一起兜,防移动网络切换掉线)时,按固定顺序执行:刷新凭证 →restartIce()→ 带iceRestart: true重新建 offer → 经信令通道发给对端:
if (pc.iceConnectionState === 'failed' || pc.iceConnectionState === 'disconnected') { await refreshTURNCredentials(pc); const offer = await pc.createOffer({ iceRestart: true }); await pc.setLocalDescription(offer); // 经信令发送 offer... }为什么必须这个顺序:先换新凭证再重启,否则 ICE 重启后拿的还是过期凭证,白重启一轮。需要触发这套流程的四种场景:TURN 服务器维护、anycast 路由调整、超 1 小时长会话中的凭证刷新、连接进入failed状态。
服务端侧还有一个配套动作:用TURNCredentialsManager把未过期凭证缓存在内存(或 KV),命中就复用、未命中才打生成端点,并在写缓存时一次性完成 53 端口过滤与ttl > 172800防御性校验。模式代码见 patterns.md。为什么缓存:高并发下所有客户端共用一份凭证能大幅降低生成端点压力,TTL 没到期前它对所有客户端都有效。
观测与排障:三个 API + 一张限额表 + 一份清单
先看流量到底走没走中继——三个观测点组合使用:
pc.addEventListener('icecandidate', e => { if (e.candidate) console.log(e.candidate.type, e.candidate.protocol); // host/srflx/relay }); const stats = await pc.getStats(); stats.forEach(r => { if (r.type === 'candidate-pair' && r.selected) console.log('Selected:', r); });icecandidate看候选类型(有relay说明 TURN 真正被用上了),iceconnectionstatechange跟踪checking → connected → completed(或failed),getStats()里selected === true的candidate-pair就是当前实际生效的通道——直连还是中继,一目了然。
丢包时先对照限额。这些限制是按用户分配(per-allocation)而非账户级:
| 维度 | 限额 | 超限后果 |
|---|---|---|
| 新唯一 IP 速率 | >5 个/秒 | 丢包 |
| 包速率 | 入/出 5–10k pps | 丢包 |
| 数据速率 | 入/出 50–100 Mbps | 丢包 |
建连慢时的排查路径:候选收集是否完整 → 到 Cloudflare 边缘的延迟 → 防火墙是否放行 3478/5349/443 → 企业网络是否该改用 443 端口的 TURN over TLS。
最后是上线前对照清单(源自 gotchas.md):
- 凭证只在服务端生成,密钥绝不下发
TURN_KEY_SECRET在 wrangler secrets 而非vars- TTL ≤ 预期会话时长且 ≤ 48 小时
- 凭证生成端点有限流 + 客户端先认证
- 会话被攻陷时可吊销凭证
- 不硬编码 IP(IP 有 14 天通知变更期,硬编码必炸),配 DNS 监控
- 浏览器端 URL 已过滤 53 端口
部署边界备忘:IPv6、TLS 与企业白名单
- IPv6:客户端到 TURN 支持 IPv4/IPv6 双栈;但中继地址只分配 IPv4(不支持 RFC 6156),TCP 中继(RFC 6062)也不支持——IPv6 客户端能接入,中继流量仍走 IPv4。
- TLS:支持 TLS 1.1/1.2/1.3;1.3 推荐
AEAD-AES128-GCM-SHA256、AEAD-AES256-GCM-SHA384、AEAD-CHACHA20-POLY1305-SHA256,1.2 推荐ECDHE-ECDSA-AES128-GCM-SHA256、ECDHE-RSA-AES128-GCM-SHA256等(详见 configuration.md)。 - 企业 IP 白名单:严格防火墙可对
turn.cloudflare.com放行 IPv4141.101.90.1/32、162.159.207.1/32与 IPv62a06:98c1:3200::1/128、2606:4700:48::1/128。但 IP 可能提前 14 天通知后变更,务必用dig turn.cloudflare.com A / AAAA做自动监控,14 天窗口内更新白名单。
延伸阅读
- TURN 服务概览:服务地址、端口清单、模块阅读路径
- TURN API 参考:Key 管理、凭证生成/吊销契约、TypeScript 类型、TTL 约束
- TURN 配置指南:Worker 搭建、wrangler.jsonc、环境变量、IP 白名单
- TURN 实现模式:建连、续期、缓存、ICE 重启与调试的完整代码模式
- TURN 陷阱与排查:高频错误、限额、安全清单与故障诊断
- 需要克隆本仓库参考文档时:
git clone https://link.gitcode.com/i/d7beb73ebdd8cb3b35d85e4b733378d4
【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考