实时通信协议选型实战指南:MQTT、WebSocket与WebRTC本质解析
1. 这张表,我贴在工位上三年了:为什么实时通信协议选型不能靠“听说”?
刚入行那会儿,我接手一个设备远程监控项目,前端同事说“用 WebSocket 吧,现在都这么干”,后端同事说“MQTT 更轻量,IoT 场景首选”,运维同事皱着眉问“WebRTC 是不是得配 Coturn?带宽扛得住吗?”——最后上线当天,设备在线率掉到 60%,用户消息延迟平均 8 秒,后台日志里全是重连失败和订阅超时。复盘时才发现,我们压根没搞清这三者根本不在同一维度上打架:MQTT 是应用层发布/订阅语义的协议,WebSocket 是 TCP 之上的双向通道封装,WebRTC 则是一整套点对点媒体与数据传输的端到端解决方案。它们不是“三选一”的关系,而是“什么场景该用哪一层”的问题。这张《实时通信协议对比速查表》,是我从 2021 年起在 7 个工业网关、3 类智能硬件、2 套远程协作系统中反复踩坑、压测、替换后,亲手整理出来的实战手册。它不讲 RFC 文档里的理想模型,只写真实设备在弱网下丢包率超过 35% 时哪个协议能稳住心跳;不列教科书定义,只标出 MQTT 的 QoS=1 在实际部署中到底要多花多少服务器资源;不谈“理论上支持”,只告诉你 WebSocket 在 Nginx 反向代理后必须加哪三行配置才能避免 60 秒强制断连。如果你正面临设备上报卡顿、音视频通话卡顿、网页实时通知收不到、或者被老板问“为什么不用 WebRTC 做聊天室”,那么这张表就是你打开调试工具前,最该先看的一页纸。它适合嵌入式工程师、IoT 后端开发、前端实时功能负责人,也适合技术选型阶段的产品经理——只要你的需求里有“实时”两个字,就绕不开这些协议底层的真实行为。
2. 协议本质解构:不是“谁更好”,而是“谁在解决什么问题”
2.1 MQTT:不是“轻量版 HTTP”,而是为不可靠网络设计的发布/订阅消息总线
MQTT 的核心价值,从来不是“比 HTTP 小”,而是它把“消息可靠送达”这件事,从应用层彻底下沉到了协议层。它的设计哲学非常朴素:设备可能随时断电、4G 信号忽强忽弱、边缘网关内存只有 64MB。所以它用三个关键机制应对:
第一是主题(Topic)分级路由。sensors/room1/temperature 和 sensors/+/temperature 这种树状结构,让 Broker 不需要遍历所有客户端,就能按前缀快速匹配订阅者。我在某冷链运输项目中实测过:当同时有 12,000 台车载终端连接到单台 EMQX 服务器时,Broker 路由耗时稳定在 0.8ms 内,而如果换成自研的基于 Redis Pub/Sub 的方案,路由延迟在高并发时会飙升到 15ms 以上——因为 Redis 没有原生 Topic 分层索引,全靠客户端做字符串匹配。
第二是QoS 级别分层保障。QoS=0 是“发了就忘”,适合温湿度这类可丢失数据;QoS=1 是“至少一次”,靠 PUBACK 确认+本地存储重发,但可能重复;QoS=2 是“恰好一次”,通过四步握手(PUBLISH → PUBREC → PUBREL → PUBCOMP)确保不重不丢,代价是三次往返延迟。这里有个关键细节:QoS=2 在真实网络中极少启用。我在某电力巡检机器人项目中做过压测——当网络丢包率达 12% 时,QoS=2 的端到端延迟中位数是 280ms,而 QoS=1 是 95ms。业务方最终接受“偶尔重复上报”,换来了 63% 的延迟下降。这不是妥协,而是对“实时性”和“精确性”的主动权衡。
第三是遗嘱消息(Last Will and Testament)。设备上线时声明:“如果我突然断开,替我发一条 offline 到 status/robot001”。这个机制让服务端无需轮询,就能感知设备离线。但要注意:遗嘱消息本身也受 QoS 控制,且只在 TCP 连接异常中断时触发(比如设备断电),正常调用 DISCONNECT 包则不会发送。很多初学者误以为这是“心跳保活”,其实它和心跳完全无关。
提示:MQTT 的“轻量”体现在报文头最小仅 2 字节(固定头),但真实开销要看整个链路。比如一个 100 字节的传感器数据,在 QoS=1 下,实际网络流量是:PUBLISH(102B) + PUBACK(4B) = 106B;而 HTTP POST 同样数据,加上 headers 至少 300B 起。但如果你用 MQTT over TCP 直连,又在公网部署,那 TLS 握手的 2-3RTT 开销,可能比报文本身还大——这时候就得考虑 MQTT over QUIC 或 TLS 1.3 的 0-RTT 恢复。
2.2 WebSocket:不是“HTTP 升级”,而是浏览器里唯一合法的全双工 TCP 通道
很多人以为 WebSocket 是“HTTP 的升级版”,这是个危险误解。HTTP 是请求-响应模型,天生不适合服务端主动推消息;WebSocket 则是在 HTTP 握手成功后,直接复用底层 TCP 连接,切换到二进制帧传输模式。它的本质,是给浏览器开了一个永不关闭的管道。
这个“管道”带来三个硬性事实:
第一,它没有内置的消息语义。WebSocket 只管“把字节流从 A 送到 B”,至于这串字节是 JSON 指令、Protobuf 序列化数据,还是纯文本日志,协议本身不关心。这就导致一个经典问题:“WebSocket 连接但不接受信息”——八成是因为前后端约定的 message 格式不一致,比如前端 expect {type: 'data', payload: {...}},后端却发了 {"msg": "hello"},解析失败后静默丢弃。我在某医疗监护系统中遇到过类似 case:前端用 JSON.parse(event.data) 解析,后端因日志埋点错误,偶尔发了未格式化的字符串 Connection established,导致整个连接后续消息全部无法解析。
第二,心跳必须由应用层实现。WebSocket 协议本身只有 ping/pong 帧(opcode=9/10),但浏览器 API 并不暴露发送 ping 的方法。所以标准做法是:客户端定时发 {"type":"ping"},服务端收到后立即回 {"type":"pong"}。关键参数在于间隔设置——太短(如 5 秒)会增加无谓流量;太长(如 120 秒)则无法及时发现 NAT 超时。我的经验是:取后端反向代理的 idle_timeout 值的 60%。比如 Nginx 默认 proxy_read_timeout 60s,那就设心跳间隔为 35 秒,服务端收到 ping 后 500ms 内必须 pong 回复,否则客户端主动断连重试。
第三,连接生命周期完全依赖 TCP。这意味着 WebSocket 会继承 TCP 的所有特性:三次握手建连、四次挥手断连、拥塞控制、NAT 映射老化。某次我们把 WebSocket 服务从 IDC 迁移到云厂商,发现移动端重连率飙升。抓包发现:云厂商的 SLB 对空闲连接默认 90 秒清理,而我们的心跳是 60 秒,看似安全,但 Android 系统在锁屏后会深度休眠网络模块,导致心跳包实际发出时间严重滞后。最终方案是:Android 客户端在锁屏时将心跳缩短至 25 秒,并监听 visibilitychange 事件,页面切到后台立即降级为 15 秒轮询。
注意:WebSocket 的
binaryType设置直接影响性能。默认binaryType = 'blob',每次接收二进制数据都会生成 Blob 对象,触发 GC;设为'arraybuffer'则直接分配连续内存,Chrome 下解析速度提升 3.2 倍。这个细节在实时音视频数据传输中尤为关键。
2.3 WebRTC:不是“音视频协议”,而是一套端到端协商的实时数据传输框架
把 WebRTC 等同于“视频通话”,就像把 TCP 等同于“网页浏览”。WebRTC 的核心是 ORTC(Object Real-Time Communications)架构:它不规定编解码,不强制信令方式,甚至不定义 SDP 格式——它只提供一套 JavaScript API,让浏览器能直接操作音视频采集、编码、网络传输、渲染的每一个环节。
它的三层结构决定了选型逻辑:
-
底层传输层(ICE/STUN/TURN):解决“怎么连上对方”。ICE 框架通过收集本机 IP、STUN 服务器获取公网映射、TURN 服务器中继,最终选出最优路径。这里的关键陷阱是:STUN 只能穿透 Cone NAT,对 Symmetric NAT 无效。某企业内网项目中,大量 Windows 设备使用企业防火墙(典型 Symmetric NAT),STUN 返回的地址根本无法被对方访问,必须强制走 TURN。但我们初期只部署了 STUN 服务,导致 43% 的内网用户无法建立 P2P 连接。
-
中层会话层(SDP/DTLS/SCTP):解决“连上后说什么”。SDP 描述媒体能力(支持哪些编解码器、分辨率、FEC 策略),DTLS 加密传输,SCTP 承载数据通道(DataChannel)。其中
RTCPeerConnection的setConfiguration()方法允许动态更新 ICE 服务器列表,这在混合网络(如同时有 4G 和 WiFi)场景下至关重要——可以先用 STUN 快速建连,再后台预加载 TURN 凭据,网络切换时无缝切换中继。 -
顶层应用层(MediaStream/DataChannel):解决“传什么”。MediaStream 处理音视频,DataChannel 处理任意二进制数据。重点来了:DataChannel 默认是有序、可靠传输(类似 TCP),但可通过
ordered: false, maxRetransmits: 0配置为无序、不可靠(类似 UDP)。某远程机械臂控制项目中,我们用 DataChannel 传控制指令,要求“最新指令永远覆盖旧指令”,于是启用不可靠模式,端到端延迟从 120ms 降至 22ms。
实测心得:WebRTC 的
getStats()API 是调试神器。不要只看inbound-rtp的jitter和packetsLost,重点关注candidate-pair的state(checking/connected/succeeded)和nominated字段。当state长期卡在checking,基本可判定 STUN/TURN 配置错误;若nominated为 false,则说明当前使用的候选地址并非最优路径。
3. 关键参数与实操决策树:从需求描述到协议选型的完整推演
3.1 六维评估矩阵:用真实业务指标替代模糊概念
我把所有实时通信需求拆解为六个可量化维度,每个维度对应一个明确的技术约束。当你拿到新需求时,只需按顺序回答这六个问题,答案会自然指向最合适的协议组合:
| 维度 | 问题 | MQTT 适配度 | WebSocket 适配度 | WebRTC 适配度 | 关键判断依据 |
|---|---|---|---|---|---|
| 1. 数据方向 | 是单向上报(设备→云),还是双向交互(用户↔用户)? | ★★★★★(专为单向/广播设计) | ★★★★☆(双向,但需服务端中转) | ★★★★★(原生 P2P 双向) | MQTT 的 Pub/Sub 天然适合“一发多收”;WebRTC 的 DataChannel 支持任意拓扑;WebSocket 必须经服务端转发,增加延迟。 |
| 2. 网络环境 | 终端是否常处于弱网、高丢包、NAT 严格限制环境?(如车载、井下、企业内网) | ★★★★★(QoS 重传+遗嘱+低带宽) | ★★☆☆☆(TCP 重传放大延迟,NAT 穿透依赖代理) | ★★★★☆(ICE 自动选路,但 TURN 中继增加延迟) | MQTT 在 30% 丢包下仍能维持 QoS=1;WebSocket 在丢包>15% 时 TCP 重传导致雪崩;WebRTC 的 loss-based BWE(如 webrtc lossbasedbwev2)可动态调整码率。 |
| 3. 实时性要求 | 端到端延迟容忍阈值是多少?(如告警<500ms,遥控<100ms,直播<3s) | ★★★☆☆(QoS=1 中位延迟 80-200ms) | ★★★★☆(纯文本 50-150ms,但受代理影响) | ★★★★★(P2P 下 20-80ms,TURN 中继 150-400ms) | WebRTC 的 P2P 路径最短;MQTT 的 Broker 路由和 QoS 确认增加固定开销;WebSocket 的代理转发是最大不确定因素。 |
| 4. 终端能力 | 终端是否具备 JS 运行环境?是否支持 WebRTC API?是否有固定 IP 或可控 NAT? | ★★★★★(仅需 TCP/IP,C/Python/Go 全支持) | ★★★★☆(需现代浏览器或 WebView) | ★★☆☆☆(需 Chrome/Firefox/Safari,iOS 15+) | MQTT 可跑在 8-bit 单片机上;WebSocket 依赖浏览器兼容性;WebRTC 在 iOS 上长期存在 H.264 编码支持问题。 |
| 5. 数据类型 | 传输的是结构化小数据(JSON)、二进制流(音视频)、还是大文件? | ★★★★☆(支持二进制 payload,但大文件需分片) | ★★★★☆(支持 ArrayBuffer,大文件需流式处理) | ★★★★★(DataChannel 原生支持流式二进制) | WebRTC 的 SCTP 专为实时流优化;MQTT 的 256MB 报文上限在物联网中足够;WebSocket 的 send() 方法对大文件无特殊优化。 |
| 6. 运维复杂度 | 团队是否有能力维护 STUN/TURN 服务器?是否接受 MQTT Broker 集群? | ★★★★☆(EMQX/Mosquitto 部署简单) | ★★★☆☆(需 Nginx/HAProxy 代理配置) | ★★☆☆☆(Coturn 部署+证书+带宽成本高) | Coturn 的 TLS 配置错误是 WebRTC 最常见故障源;MQTT Broker 的集群脑裂问题需仔细设计;WebSocket 的代理超时参数极易遗漏。 |
举个真实案例:某智能农业大棚项目,需求是“土壤传感器每 30 秒上报温湿度,手机 App 实时查看,管理员可远程开关通风扇”。我们逐项打分:
- 数据方向:传感器→云(单向)+ App←云(单向)+ 云→设备(单向)→ MQTT 的 Pub/Sub 完美匹配
- 网络环境:大棚无宽带,全靠 4G,信号波动大 → MQTT QoS=1 的重传机制比 WebSocket TCP 更鲁棒
- 实时性:开关指令要求 <1s → MQTT QoS=1 实测 320ms,达标
- 终端能力:传感器是 ESP32,App 是 React Native → MQTT 客户端库成熟(paho-mqtt-js / mqtt.js)
- 数据类型:JSON 小数据 → MQTT payload 完全胜任
- 运维复杂度:团队无 Coturn 经验,EMQX 有 Docker 一键部署 → MQTT 运维成本最低
结论:全链路 MQTT。后来扩展需求加入“大棚内摄像头直播”,我们才在视频流路径单独引入 WebRTC,形成 MQTT(控制信令)+ WebRTC(媒体流)的混合架构。
3.2 协议组合策略:90% 的“高阶需求”其实是分层混用
所谓“高级实时系统”,往往不是单一协议的胜利,而是分层精准使用的结果。以下是我在工业、医疗、教育领域验证过的三种黄金组合:
组合一:MQTT + WebSocket(信令与数据分离)
适用场景:需要 Web 端实时展示设备状态,但设备端资源受限(如 STM32)。
实现逻辑:设备通过 MQTT 上报数据到 Broker;WebSocket 服务端订阅同一 Topic,收到后立即推送给已连接的浏览器客户端。
关键技巧:WebSocket 服务端必须做消息聚合。比如 1000 台设备每秒各报 1 条,直接转发会导致浏览器每秒接收 1000 条消息。我们采用滑动窗口聚合:每 200ms 将同一设备的最新值打包为 {device_id: 'd001', temp: 25.3, humi: 45} 发送,降低前端处理压力。
避坑点:MQTT Broker 的订阅权限必须精细控制。曾有项目因未限制 WebSocket 服务端只能订阅 sensors/+,导致其意外收到 admin/commands 主题的指令,引发安全风险。
组合二:WebSocket + WebRTC(信令与媒体分离)
适用场景:Web 端音视频通话、远程协作白板。
实现逻辑:WebSocket 承担信令交换(offer/answer/ice-candidate),WebRTC 承担音视频流和 DataChannel 数据传输。
关键参数:RTCPeerConnection 的 iceTransportPolicy 设为 'relay' 可强制走 TURN,避免 P2P 失败;sdpSemantics 必须设为 'unified-plan'(Chrome 72+ 强制要求);bundlePolicy 设为 'max-bundle' 可将音视频流合并到一个传输通道,减少 ICE 候选数量。
实测数据:在 200ms RTT、15% 丢包的模拟弱网下,unified-plan + max-bundle 使首次媒体流建立时间从 4.2s 缩短至 1.8s。
组合三:MQTT + WebRTC(云边协同)
适用场景:边缘计算网关需统一管理下挂设备,同时向上云平台汇报。
实现逻辑:网关作为 MQTT Client 订阅 cloud/commands/# 接收云端指令;作为 WebRTC Peer 与下挂设备(如 IPC 摄像头)建立 DataChannel,透传原始视频流。
关键设计:网关的 MQTT Client 和 WebRTC Peer 必须共享同一网络栈。我们用 libwebrtc 的 C++ 接口在网关上实现,避免 Node.js 层的 WebSocket 代理成为瓶颈。
运维重点:TURN 服务器必须部署在网关就近区域,否则视频流经两次中继(设备→TURN→网关→TURN→云)导致延迟爆炸。
提示:永远不要为了“技术先进”而强行用 WebRTC。某在线教育项目曾坚持用 WebRTC 实现课件同步(白板+文字),结果因 Safari 对 DataChannel 的
maxPacketLifeTime支持不一致,导致 iOS 用户白板频繁卡顿。最终降级为 WebSocket + OT(Operational Transformation)算法,稳定性提升 99.2%。
4. 实操落地:从零搭建可验证的对比环境
4.1 本地最小化验证环境搭建(5 分钟完成)
不需要云服务器、不需要域名,用三行命令即可启动一个可交互的对比沙箱。所有组件均使用 Docker,配置文件已预置好关键参数:
验证步骤:
- MQTT 测试:用
mqtt-explorer(Windows 安装包已预编译)连接localhost:1883,订阅test/sensor,再用命令行发布:mosquitto_pub -h localhost -t test/sensor -m '{"temp":25}' - WebSocket 测试:浏览器打开
http://localhost:8765,F12 控制台执行:JAVASCRIPTconst ws = new WebSocket('ws://localhost:8765');ws.onmessage = e => console.log('WS:', JSON.parse(e.data)); - WebRTC 测试:打开
http://localhost:3000,点击“Create Room”,复制 Room ID 给另一浏览器,粘贴加入,观察控制台是否打印iceConnectionState: connected。
注意:Docker 启动后,务必检查端口是否被占用。Mac 用户需在 Docker Desktop 设置中开启
Use the Rosetta translation environment(M1/M2 芯片)。
4.2 关键参数压测方法论:用真实数据替代主观感受
协议选型不能只看“连得上”,要看“在极限条件下是否稳”。以下是我在生产环境验证过的三类必测场景及工具链:
场景一:高并发连接稳定性测试
目标:验证 5000 设备同时在线时,各协议的连接保持率。
工具:mqtt-benchmark(MQTT)、autocannon(WebSocket)、webrtc-load-tester(WebRTC)
关键指标:
- MQTT:
emqx_ctl clients list | wc -l查看实际连接数,对比预期值;emqx_ctl listeners stats查看current_connections - WebSocket:
netstat -an | grep :8765 | grep ESTABLISHED | wc -l - WebRTC:
curl http://localhost:3000/api/stats | jq '.active_sessions'
场景二:弱网抗性测试
目标:模拟 4G 切换、电梯信号丢失等场景。
工具:tc(Linux 流量控制)
实操命令(在服务端执行,模拟 30% 丢包+200ms 延迟):
然后运行客户端压测,记录各协议的:
- MQTT:
PUBACK超时重发次数(emqx_ctl log tail error) - WebSocket:
onclose事件触发频率(前端统计) - WebRTC:
RTCPeerConnection.iceConnectionState变为disconnected的次数
场景三:消息吞吐与延迟测试
目标:测量单位时间内可靠消息的最大吞吐量及 P95 延迟。
工具:mqtt-benchmark -c 1000 -I 100 -t test/topic -q 1 -h localhost -p 1883
关键输出解读:
Messages Received:实际接收数,应接近Messages SentLatency (ms) min/avg/max/p95:重点关注 p95,它反映 95% 消息的延迟上限Throughput (msgs/sec):吞吐量,但需结合延迟看——若 p95 达到 2s,吞吐再高也无意义
实测对比(500 并发,QoS=1):
- MQTT:吞吐 12,400 msg/s,p95 延迟 180ms
- WebSocket:吞吐 9,800 msg/s,p95 延迟 145ms(直连)/ 320ms(Nginx 代理后)
- WebRTC DataChannel:吞吐 18,200 msg/s,p95 延迟 45ms(P2P)/ 210ms(TURN 中继)
5. 常见问题排查速查表:那些让你加班到凌晨的“灵异事件”
5.1 MQTT 类问题
| 现象 | 可能原因 | 排查命令/方法 | 解决方案 |
|---|---|---|---|
设备频繁重连,日志显示 connection refused |
1. Broker 认证失败(用户名/密码错误) 2. 连接数超限(EMQX 默认 1024) 3. 客户端 ID 冲突(多个设备用相同 client_id) |
emqx_ctl users list 查看用户状态emqx_ctl status 查看 max_connectionsemqx_ctl clients list | grep <client_id> |
1. 检查 emqx.conf 中 allow_anonymous 配置2. 调大 max_connections 并重启3. 强制设备使用唯一 client_id(如 MAC 地址哈希) |
订阅后收不到消息,但 mosquitto_sub 命令行能收到 |
1. 主题过滤器(Topic Filter)与发布主题(Topic Name)不匹配 2. Broker ACL 权限未开放订阅权限 3. 客户端 QoS 与发布端不一致(如订阅 QoS=0,发布 QoS=2) |
emqx_ctl topics list 查看活跃主题emqx_ctl acl check <username> subscribe <topic> |
1. 确认通配符使用正确(+ 匹配单层,# 匹配多层)2. 在 acl.conf 中添加 {allow, all, subscribe, ["$SYS/#", "sensors/#"]}3. 订阅时显式指定 QoS: client.subscribe('sensors/room1/#', {qos: 1}) |
| QoS=1 消息重复,业务逻辑出错 | 1. 客户端未正确处理 PUBACK,导致重复发送2. Broker 持久化失败(磁盘满、权限不足) |
emqx_ctl log tail warning 查看 session_dropped 日志df -h 检查磁盘空间 |
1. 客户端收到 PUBACK 后立即清除本地重发队列2. 清理 /var/lib/emqx/mnesia 目录,重启 Broker |
5.2 WebSocket 类问题
| 现象 | 可能原因 | 排查方法 | 解决方案 |
|---|---|---|---|
连接成功但 onmessage 不触发 |
1. 服务端发送的数据不是合法 UTF-8 字符串 2. 前端 binaryType 设置为 'blob',但未调用 blob.text() 解析3. 消息体过大,触发浏览器 maxBufferSize 限制(Chrome 默认 128MB) |
用 wscat -c ws://localhost:8765 手动连接,发送 {"test":"ok"} 测试前端 console.log(event.data.constructor.name) |
1. 服务端确保 JSON.stringify() 后发送2. 前端设 ws.binaryType = 'arraybuffer',用 new TextDecoder().decode(event.data) 解析3. 大数据分片发送,每片 < 1MB |
| 连接 60 秒后自动断开 | 1. Nginx proxy_read_timeout 默认 60s2. 服务端未实现心跳,连接被中间设备(防火墙/NAT)切断 |
curl -v http://your-domain.com/ws 查看响应头 X-Upstream-Service-Time抓包看 TCP Keepalive 是否发送 |
1. Nginx 配置:proxy_read_timeout 300; proxy_send_timeout 300;2. 前端每 30 秒发 {"type":"ping"},服务端立即回 {"type":"pong"} |
| 移动端锁屏后连接断开 | 1. Android/iOS 系统休眠网络模块 2. 前端未监听页面可见性变化 |
在 visibilitychange 事件中打印 document.hidden |
锁屏时将心跳间隔缩短至 15 秒,并在 pagehide 事件中主动 ws.close(),唤醒后重连 |
5.3 WebRTC 类问题
| 现象 | 可能原因 | 排查工具 | 解决方案 |
|---|---|---|---|
iceConnectionState 长期 checking |
1. STUN/TURN 服务器地址配置错误 2. 防火墙阻止 UDP 3478/5349 端口 3. 企业 NAT 类型为 Symmetric |
chrome://webrtc-internals 查看 getStats() 输出curl -v stun:stun.l.google.com:19302 测试 STUN 连通性 |
1. 确认 STUN URL 格式为 stun:stun.example.com:34782. 开放 UDP 端口,或改用 TURN(TCP 443) 3. 强制 iceTransportPolicy: 'relay' |
视频卡顿,jitter 值 > 500ms |
1. 网络带宽不足 2. 编码器未启用硬件加速 3. RTCPeerConnection 未配置 bandwidth 限制 |
chrome://webrtc-internals 查看 outbound-rtp 的 bitrateMean用 ffplay -i rtmp://... 对比本地播放流畅度 |
1. 在 mediaConstraints 中设置 video: {width: {ideal: 640}, height: {ideal: 480}, frameRate: {ideal: 15}}2. Chrome 启动参数加 --use-gl=desktop3. pc.addTransceiver('video', {direction: 'sendonly', streams: [stream]}).setCodecPreferences(...) 优先选择 VP8 |
| iOS Safari 白屏,无错误日志 | 1. iOS 15+ 要求 getUserMedia 必须在用户手势后调用2. RTCPeerConnection 创建时未传 iceServers |
Safari 开发者工具 Console 输入 navigator.mediaDevices.getUserMedia({video:true}) |
1. 所有 getUserMedia 调用必须包裹在 button.onclick 等用户事件中2. new RTCPeerConnection({iceServers: [{urls: 'stun:stun.l.google.com:19302'}]}) |
最后分享一个血泪教训:某次 WebRTC 项目上线后,用户投诉“视频黑屏但有声音”。排查三天无果,最终发现是
RTCPeerConnection的addTrack方法在 iOS Safari 上对MediaStreamTrack的enabled属性有 bug——必须在addTrack后立即执行track.enabled = true,否则视频轨道默认禁用。这个细节在 MDN 文档里只有一行小字,却让我们多熬了两个通宵。所以,永远不要相信“浏览器兼容性已解决”,每个新版本都要回归测试。