
Cloudflare TURN 生产实战WebRTC 中继凭证签发、ICE 重启与端口过滤指南【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skillsCloudflare TURN 是跑在全球 anycast 网络310 城市不含中国网络上的托管中继服务当 NAT 或防火墙阻断 WebRTC 客户端与 SFU 的直连时它作为流量中继兜底保证通话可用。本文不按先配好再写代码的顺序而是按服务端签发 → 浏览器消费 → 掉线自愈三层拆出一套可直接上生产的 TURN 接入方案覆盖凭证缓存、53 端口过滤、ICE 重启、限额排查与成本核算读完即可落地一套健壮的代码。把 TURN 塞进 RTCPeerConnection直连优先、中继兜底WebRTC 用RTCIceServer描述 ICE 服务器。客户端不自己硬编码服务器而是向自己的后端拉临时凭证再叠一个公开 STUNinterface RTCIceServer { urls: string | string[]; username?: string; credential?: string; credentialType?: password | oauth; } async function getTURNConfig(): PromiseRTCIceServer[] { const response await fetch(/api/turn-credentials); const data await response.json(); return [ { 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 iceServers await getTURNConfig(); const peerConnection new RTCPeerConnection({ iceServers });为什么要分 STUN 与 TURN 两段stun:stun.cloudflare.com:3478负责发现公网候选turn:/turns:负责在直连失败时中继。两段一起交给RTCPeerConnection由 ICE 协商自动择优——STUN 直连成功就省掉中继发费用失败才落到 TURN。按业务对效率 vs 连通性的取舍可用iceTransportPolicy与bundlePolicy控制行为场景关键配置实际行为视频会议iceTransportPolicy: all先试 P2P 直连失败才走中继IoT / 可预测连通iceTransportPolicy: relay强制全部流量经 TURN 中继屏幕共享bundlePolicy: max-bundle多路媒体聚合到单条传输降开销成本差异要心里有数与 Cloudflare Calls SFU 搭配时 TURN 免费否则按$0.05/GB出站流量计费。视频会议用all能省下直连场景的中继费只有对连通性可预测性要求高的场景才用relay。从自己的后端拉凭证凭证来自你自己的/api/turn-credentials而不是浏览器直接打 Cloudflare 生成端点。这样密钥永远留在服务端客户端只拿到一次性临时凭证。凭证不该出现在浏览器里Worker 端签发与缓存签发动作放在一个 Cloudflare Worker 里完成。密钥放环境变量与 secrets# .env CLOUDFLARE_ACCOUNT_IDyour_account_id CLOUDFLARE_API_TOKENyour_api_token TURN_KEY_IDyour_turn_key_id TURN_KEY_SECRETyour_turn_key_secretwrangler.jsonc里非敏感的TURN_KEY_ID可以放vars敏感密钥用wrangler secret put TURN_KEY_SECRET单独注入生产环境可再绑定CREDENTIALS_CACHE这个 KV 命名空间做凭证缓存{ name: turn-credentials-api, main: src/index.ts, vars: { TURN_KEY_ID: your-turn-key-id }, env: { production: { kv_namespaces: [ { binding: CREDENTIALS_CACHE, id: your-kv-namespace-id } ] } } }为什么密钥要分开存vars会随部署明文可见wrangler secret put的 secret 不落盘到配置里。把TURN_KEY_SECRET放进vars等于把签发钥匙挂在墙上。一次保存的密钥创建 TURN Key 走POST /accounts/{account_id}/calls/turn_keysBase URLhttps://api.cloudflare.com/client/v4需 Calls Write 权限的 TokenPOST /accounts/{account_id}/calls/turn_keys Content-Type: application/json { name: my-turn-key }响应里的key字段是实际密钥仅创建时返回一次必须立刻保存之后uid、name、created、modified都能再查但key查不回来。后续管理GET列表、GET/PUT/DELETE /accounts/{account_id}/calls/turn_keys/{key_id}。缓存未过期凭证为避免每个客户端都打rtc.live.cloudflare.com的生成端点Worker 侧可缓存未过期凭证并在本地校验 TTL 上限class TURNCredentialsManager { private creds: { username: string; credential: string; urls: string[]; expiresAt: number; } | null null; async getCredentials(keyId: string, keySecret: string): PromiseRTCIceServer[] { const now Date.now(); if (this.creds this.creds.expiresAt now) { return this.buildIceServers(this.creds); } const ttl 3600; if (ttl 172800) throw new Error(TTL max 48hrs); const res await fetch( https://rtc.live.cloudflare.com/v1/turn/keys/${keyId}/credentials/generate, { method: POST, headers: { Authorization: Bearer ${keySecret}, Content-Type: application/json }, body: JSON.stringify({ ttl }) } ); const data await res.json(); const filteredUrls data.iceServers.urls.filter((url: string) !url.includes(:53)); this.creds { username: data.iceServers.username, credential: data.iceServers.credential, urls: filteredUrls, expiresAt: now (ttl * 1000) - 60000 }; return this.buildIceServers(this.creds); } private buildIceServers(c: { username: string; credential: string; urls: string[] }): RTCIceServer[] { return [ { urls: stun:stun.cloudflare.com:3478 }, { urls: c.urls, username: c.username, credential: c.credential, credentialType: password as const } ]; } }三个细节别漏缓存有效期比 TTL 提前 1 分钟- 60000留出刷新窗口过滤 53 端口在缓存写入时一次性完成ttl 172800的防御性校验与 API 侧约束一致——API 会直接拒绝超过48 小时172800 秒的请求示例里常用ttl: 8640024 小时。凭证生成的请求/响应契约POST https://rtc.live.cloudflare.com/v1/turn/keys/{key_id}/credentials/generate Authorization: Bearer {key_secret} Content-Type: application/json { ttl: 86400 }核心响应字段iceServers.urls含 STUN 与多协议 TURN 地址、username形如1738035200:user123、credentialBase64 编码的 HMAC。要立即终止某会话调POST .../credentials/revokebody 传{username: ...}返回204计费立即停止活跃连接在数秒内断开。53 端口这条 URL 必须服务端拦掉凭证生成响应里会混入turn:turn.cloudflare.com:53?transportudp和turn:turn.cloudflare.com:80?transporttcp这类地址。它们对非浏览器客户端可用但Chrome 和 Firefox 会拦截 53 端口浏览器端会静默失败。所以过滤逻辑要放在服务端别依赖浏览器自己处理。推荐尝试顺序浏览器端顺序端口/协议定位13478/udp首选延迟最低23478/tcpUDP 被封网络的回退35349/tls企业防火墙最可靠4443/tls备用 TLS 端口防火墙友好function filterICEServersForBrowser(urls: string[]): string[] { return urls .filter(url !url.includes(:53)) // Remove port 53 .sort((a, b) { if (a.includes(transportudp)) return -1; if (b.includes(transportudp)) return 1; if (a.includes(transporttcp) !a.startsWith(turns:)) return -1; if (b.includes(transporttcp) !b.startsWith(turns:)) return 1; return 0; }); }为什么不放浏览器凭证列表由服务端统一生成后下发浏览器拿到的已经是过滤排序好的结果一旦放行 53 端口 URLICE 会尝试该候选却永远收不到响应白白拖慢建连。掉线自愈刷新、缓存与 ICE 重启长通话里凭证到期或网络切换会让iceconnectionstatechange进入failed。setConfiguration()能更新iceServers但它不触发 ICE 重启——连接已经失败时必须配合restartIce()。提前刷新 缓存管理器刷新时机以 TTL 为基准ttl * 1000 - 60000提前 1 分钟是推荐的刷新间隔。TTL 为 1 小时时约为 50 分钟async function refreshTURNCredentials(pc: RTCPeerConnection): Promisevoid { const newCreds await fetch(/turn-credentials).then(r r.json()); const config pc.getConfiguration(); config.iceServers newCreds.iceServers; pc.setConfiguration(config); // Note: setConfiguration() does NOT trigger ICE restart } const refreshInterval ttl * 1000 - 60000; // 1 min early setInterval(() refreshTURNCredentials(peerConnection), refreshInterval);只刷新不重启凭证能续上但旧候选对可能已死连接照样卡住所以刷新 重启要成对出现。failed / disconnected 时重启 ICE把failed和disconnected都纳入恢复条件防止移动网络切换时掉线。需要触发 ICE 重启的场景TURN 服务器维护、anycast 路由调整、1 小时的长会话凭证刷新、iceConnectionState failed。pc.addEventListener(iceconnectionstatechange, async () { if (pc.iceConnectionState failed || pc.iceConnectionState disconnected) { console.warn(ICE connection degraded, restarting...); // 1. 刷新凭证 await refreshTURNCredentials(pc); // 2. 触发 ICE 重启并重建 offer pc.restartIce(); const offer await pc.createOffer({ iceRestart: true }); await pc.setLocalDescription(offer); // 3. 通过信令通道把 offer 发给对端 } });顺序不能乱先刷新凭证拿到新iceServers再restartIce()再用iceRestart: true造 offer 并经信令发给对方。只打日志不重启连接就永远停在failed。按分配计的限额与掉包排查以下限额是按用户分配而非账户级超限后果统一是丢包维度限额超限后果唯一 IP 数5 个新 IP/秒丢包包速率入/出 5–10k pps丢包数据速率入/出 50–100 Mbps丢包排错时高频错误与正确做法对照错误做法正确做法ttl: 6048007 天ttl: 8640024h超 48h API 直接拒绝硬编码 IPturn:141.101.90.1:3478用域名turn:turn.cloudflare.com:3478IP 变更有 14 天通知浏览器端保留:53端口服务端过滤!url.includes(:53)凭证到期不刷新setInterval提前 1 分钟刷新只打日志不重启failed/disconnected时刷新凭证 restartIce()TURN_KEY_SECRET放客户端仅服务端签发客户端请求/api/turn-credentials建连缓慢时依次排查候选收集是否完整、到 Cloudflare 边缘的网络延迟、防火墙是否放行 WebRTC 端口3478、5349、443企业网络是否该改用 443 端口的 TURN over TLS。用 getStats 判断走没走中继靠三个事件/API 观察 ICE 过程pc.addEventListener(icecandidate, (event) { if (event.candidate) { console.log(ICE candidate:, event.candidate.type, event.candidate.protocol); } }); pc.addEventListener(iceconnectionstatechange, () { console.log(ICE state:, pc.iceConnectionState); }); const stats await pc.getStats(); stats.forEach(report { if (report.type candidate-pair report.selected) { console.log(Selected:, report); } });icecandidate看候选typehost/srflx/relay与protocol确认是否出现 relay 候选iceconnectionstatechange跟踪checking → connected → completed或failedgetStats()里selected为 true 的candidate-pair即当前实际选中的候选对能判定流量到底走直连还是 TURN 中继。企业网络、IPv6 与 TLS 的边界部署边界上有两个容易踩的点客户端到 TURNIPv4/IPv6 都支持但中继地址只分配 IPv4不支持 RFC 6156TCP 中继RFC 6062也不支持——IPv6 客户端能接入中继流量仍走 IPv4TLS 版本1.1/1.2/1.3 均支持。TLS 1.3 推荐AEAD-AES128-GCM-SHA256、AEAD-AES256-GCM-SHA384、AEAD-CHACHA20-POLY1305-SHA256TLS 1.2 推荐ECDHE-ECDSA-AES128-GCM-SHA256、ECDHE-RSA-AES128-GCM-SHA256等套件。严格防火墙的 IP 白名单可对turn.cloudflare.com白名单化IPv4141.101.90.1/32、162.159.207.1/32IPv62a06:98c1:3200::1/128、2606:4700:48::1/128。但这些 IP可能提前 14 天通知后变更需用dig turn.cloudflare.com A/dig turn.cloudflare.com AAAA定期核对并设自动监控在 14 天内更新白名单否则连接会直接失败。上线检查清单与参考索引上线前逐项确认凭证仅服务端生成绝不下发密钥TURN_KEY_SECRET放 wrangler secrets不进varsTTL ≤ 预期会话时长且 ≤ 48 小时172800 秒凭证生成端点做限流签发前先做客户端认证提供凭证吊销 API 应对被攻陷的会话不硬编码 IP或建立 DNS 监控浏览器客户端过滤 53 端口参考索引均在仓库skills/.curated/cloudflare-deploy/references/turn/下TURN 凭证与 Key 管理 API凭证生成/吊销、Key CRUD、TypeScript 类型与 TTL 约束的权威来源。TURN 配置指南Worker 搭建、wrangler.jsonc、环境变量与 IP 白名单配置。TURN 实现模式浏览器配置、端口过滤、刷新与 ICE 重启的完整代码范式。TURN 陷阱与排查常见错误、按分配限额、安全清单与成本优化。TURN 服务概览服务地址、端口清单与快速开始入口。cloudflare-deploy 决策树网络连通性分支中 WebRTC 场景对应turn/与realtime-sfu/、realtimekit/模块。【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考