ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

实时通信选型:HTTP轮询、SSE、WebSocket与WebRTC实战对比

实时通信选型:HTTP轮询、SSE、WebSocket与WebRTC实战对比 1. 实时通信选型先看清这四种方案的真实边界先说一个我面试前端候选人时经常问的问题如果让你实现一个类似在线客服、协作白板或直播弹幕的功能你会怎么选技术方案十个人里有八个第一反应是WebSocket。但真的所有场景都适合WebSocket吗HTTP轮询虽然“老土”SSE虽然“小众”在某些业务里却可能是比WebSocket更合适的答案。而一旦涉及音视频通话和点对点文件传输WebRTC又是另一个维度的选择。这四者不是层层替代的关系而是各自守着不同的能力边界选错方案后续踩的坑往往是架构级别的。从字面理解HTTP轮询、SSE、WebSocket、WebRTC解决的是同一个问题——让数据突破“请求-响应”的一次性模型在客户端和服务端之间持续流动。但它们的实现原理、连接模型、延迟特性、服务端资源占用完全不同适用的业务规模也天差地别。这篇内容我想从实战角度把四条技术路线都拆开来看底层逻辑是什么、用代码怎么写、真实落地会踩哪些坑、什么场景下选谁最合理。不是那种罗列API文档式的整理而是把这几年在多个前端项目中碰到的真实问题和取舍逻辑讲透。为了方便后续讨论先把四种方案放在同一个坐标系里看个大概后面再逐个展开。方案通信方向连接模型典型延迟服务端实现成本浏览器兼容性HTTP轮询客户端主动拉取无状态请求取决于轮询间隔极低全兼容SSE服务端单向推送长连接HTTP毫秒级较低除IE外基本全兼容WebSocket全双工双向长连接TCP毫秒级较高全兼容WebRTCP2P双向点对点直连毫秒级音视频需搭建信令服务现代浏览器全兼容这个表格只是起点真正决定选型的因素远不止这几行后面我会结合具体业务场景继续展开。2. HTTP轮询最朴素但永远有用的降级方案2.1 短轮询的机制与代码写法HTTP轮询本质上是客户端按照固定时间间隔反复发起普通HTTP请求由服务端返回最新数据。理解它的关键在于客户端每次请求都是“干净”的服务端不维护任何连接状态请求结束连接即释放。因此它天然无状态、无跨域限制CORS正常处理即可、无连接数压力任何语言任何框架都能零成本实现。后端按最普通的接口写就行// Node.js Express 示例 app.get(/api/polling, (req, res) { const latestData getLatestData(); res.json({ code: 0, data: latestData, timestamp: Date.now() }); });前端用一个简单的定时器循环请求async function pollData() { try { const res await fetch(/api/polling); const data await res.json(); updateUI(data); } catch (err) { console.error(轮询失败, err); } finally { setTimeout(pollData, 3000); // 3秒后发起下一次请求 } } pollData();这里有两个细节值得注意。第一用setTimeout而不是setInterval是为了避免请求响应时间不稳定导致的重叠请求——如果上一次请求因为网络原因迟迟没返回setInterval到点又会发起下一个请求服务端压力翻倍且数据顺序错乱。第二轮询间隔要结合业务容忍延迟来设定3秒一次意味着数据更新理论上最多延迟3秒如果业务要求秒级以内延迟轮询基本就不合适了。2.2 长轮询用请求挂起换更低的延迟与代价长轮询是对短轮询的改良思路是客户端发起请求后服务端不立即返回而是将这个请求在服务端挂起等待有新数据才响应。如果等待超过一个阈值如30秒仍无新数据返回一个空包让客户端重新发起连接。这样数据产生后能立刻返回延迟大幅降低空转请求也少了很多。// Express 长轮询 app.get(/api/long-poll, async (req, res) { const TIMEOUT 30000; const newData await waitForData(TIMEOUT); // 阻塞等待数据或超时 res.json({ data: newData || null }); }); // 服务端有一个事件队列有新数据时通知所有挂起的请求 const pendingClients []; function waitForData(timeout) { return new Promise((resolve) { const timer setTimeout(() resolve(null), timeout); pendingClients.push({ timer, resolve }); }); } function notifyClients(data) { pendingClients.forEach(({ timer, resolve }) { clearTimeout(timer); resolve(data); }); pendingClients.length 0; }长轮询的代价是服务端必须为每个挂起的连接维护上下文包括连接对象、定时器、待推送事件等连接数上来后内存压力非常明显。而且要处理连接异常断开后的清理否则会内存泄漏。所以长轮询适合数据产生不频繁但要求尽快感知的消息场景且服务端能承担的并发连接数有限的情况下才用。2.3 轮询最常见的三个坑第一个坑是并发叠加。很多初学的人确实会用setInterval但没有考虑到如果一次请求耗时超过间隔会导致多个请求并发在途。除了改用setTimeout还可以在请求前用AbortController取消上一次未完成的请求或者用队列锁保证任何时刻只存在一个在途请求。第二个坑是轮询节奏不感知页面可见性。用户切换标签页或最小化浏览器后轮询还在继续白白消耗带宽和电量。监听visibilitychange事件页面隐藏时暂停轮询回到前台再恢复这在移动端尤其重要。document.addEventListener(visibilitychange, () { if (document.hidden) { clearTimeout(timer); } else { pollData(); } });第三个坑是服务端返回的数据量不可控。轮询接口如果每次都返回全量数据数据量大时对流量是很大浪费。妥协会用增量时间戳参数客户端每次请求携带上次数据的时间戳服务端只返回增量数据。3. SSE被低估的单向推送方案3.1 SSE的核心原理与EventSource接口SSEServer-Sent Events的字面意思是服务端发送事件是一种基于HTTP长连接的服务端单向推送协议。客户端通过EventSource接口建立一个HTTP连接服务端可在这个连接上持续向客户端推送数据。与WebSocket的“先握手再切换协议”不同SSE连接的响应头是Content-Type: text/event-stream本质上就是服务端告诉你这个连接不会立刻结束我会持续给你发东西。后端写法Express 原生实现app.get(/api/sse, (req, res) { res.setHeader(Content-Type, text/event-stream); res.setHeader(Cache-Control, no-cache); res.setHeader(Connection, keep-alive); res.flushHeaders(); // 立即发送响应头使客户端感知到连接建立 const timer setInterval(() { // SSE 格式注释行、事件行event、数据行data res.write(data: ${JSON.stringify({ time: Date.now() })}\n\n); }, 1000); req.on(close, () { clearInterval(timer); res.end(); }); });SSE消息格式要求严格每行以\n结尾数据内容放在data:之后一个消息块以空行\n\n结束。上面的代码中每1000毫秒写入一条JSON数据。前端接收非常简单const source new EventSource(/api/sse); source.onopen () console.log(SSE连接已建立); source.onmessage (event) { const data JSON.parse(event.data); updateUI(data); }; source.onerror (err) { console.error(SSE连接异常, err); // EventSource 默认会自动重连 };EventSource最方便的地方在于内置断线自动重连。只要不是客户端主动close()服务端断开连接后浏览器会自动重新发起连接且重连遵循指数退避算法不会造成重连风暴。这一点相比WebSocket需要自己完善重连逻辑SSE是真的省心。3.2 SSE字段命名事件、自定义ID与last-Event-ID除了最基本的data字段SSE协议还支持event和id字段这两个字段解决了实际业务里很关键的两个问题消息类型路由和断线续传。服务端这样推送带事件类型的消息res.write(event: userLogin\n); res.write(data: {userId: 123}\n\n); res.write(event: orderUpdate\n); res.write(data: {orderId: 456, status: paid}\n\n);前端可以这么处理const source new EventSource(/api/sse); source.addEventListener(userLogin, (event) { const data JSON.parse(event.data); // 处理登录事件 }); source.addEventListener(orderUpdate, (event) { // 处理订单事件 });再看id字段。服务端可以在消息中附带id: 12345客户端断线重连时浏览器会自动在重连请求的Header中带上Last-Event-ID: 12345服务端读取这个字段就可以确定从哪条消息开始补发。这是做断线续传最优雅的手段不需要自己设计额外的消息号同步机制。res.write(id: 12345\n); res.write(data: {msg: hello}\n\n);3.3 SSE的鉴权问题与浏览器连接数限制SSE项目里最常见的限制是浏览器对同域HTTP/1.1并发连接数限制——HTTP/1.1规范建议每域名最多6个连接。SSE是长连接每开一个EventSource就占一个连接如果一个页面同时开了多个SSE通道很容易就把连接数打满。解决办法一是尽量合并消息通道用一个SSE连接推送所有类型的事件用event字段区分二是正式环境上HTTP/2HTTP/2的并发请求不受6个限制多个长连接可以复用同一个TCP连接三是用Web Worker中建立EventSource不过Worker也受相同浏览器限制并没根本上解决。另一个典型问题是鉴权。EventSourceAPI没办法自定义请求头只能用GET方法不能带Authorization Header。所以SSE鉴权的通用做法是用一个普通HTTP接口先换取token然后把它拼在URL的查询参数里// 先取token const token await fetch(/api/auth, { method: POST, body: ... }).then(r r.json()); // 通过查询参数鉴权 const source new EventSource(/api/sse?token${token});服务端在建立连接前校验token校验不通过直接返回401即可。这种方式带来的问题是token会出现在访问日志里所以SSE场景中token要尽量短命或者只做一次性鉴权首次请求时校验成功即销毁token后续连接保持只认连接本身。SSE适合哪些业务典型如股票行情、做任务进度推送、通知栏消息、AI流式输出。它的优势是服务端实现简单天然支持重连和续传因为本质是HTTP协议也更容易穿透各种代理。但致命伤是单向通信——只能服务端到客户端如果业务需要客户端向服务端发指令就得另加一个HTTP接口配合使用这是个可接受的组合模式。4. WebSocket全双工连接的正确打开方式4.1 从握手到消息帧WebSocket连接的本质WebSocket是全双工通信协议客户端和服务端都能随时向对方发送数据。很多人对WebSocket有个误解以为它是一个和HTTP完全无关的新协议。实际上WebSocket的建立过程就是一次特殊的HTTP升级请求——客户端发送Upgrade: websocket请求头服务端返回101状态码协议就从HTTP切换为WebSocket协议使用独立于HTTP的帧协议但握手借道HTTP。握手请求长这样GET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ Sec-WebSocket-Version: 13服务端计算出一个Sec-WebSocket-Accept响应头返回101连接就建立了。这个握手过程带来的重要含义是WebSocket可以沿用HTTP的端口80/443、经过常规的负载均衡和反向代理但后续的数据帧传输不再走HTTP语义也不受HTTP连接数的限制。前端建立连接的API很简单const ws new WebSocket(wss://example.com/socket); ws.onopen () { console.log(连接已建立); ws.send(JSON.stringify({ type: join, room: lobby })); }; ws.onmessage (event) { const data JSON.parse(event.data); handleMessage(data); }; ws.onclose (event) { console.log(连接关闭, event.code, event.reason); }; ws.onerror (err) { console.error(WebSocket错误, err); };注意wss://是WebSocket的加密协议对应HTTPS。生产环境务必使用wss://否则在HTTPS页面混入明文WebSocket连接会被浏览器拦截也容易遭遇中间人攻击。4.2 心跳机制为什么不能只依赖oncloseWebSocket的onclose事件只能捕获“连接明确关闭”的情况。但如果网络中间设备如移动网络切换、路由器空闲超时、防火墙空闲超时因为长时间无数据而静默断开连接客户端其实是感知不到的——TCP层没有数据传输客户端不会立刻发现连接已经死了。这种情况表现为页面还显示“在线”但消息已经发不出去了服务端也不会主动推送过来。解决办法是心跳机制。客户端每N秒发送一个ping帧或一条自定义心跳消息服务端收到后响应pong帧或心跳回包如果客户端连续M次没收到响应就主动close()当前连接然后在重连逻辑中重新建立新连接。const HEARTBEAT_INTERVAL 30000; // 每30秒一次心跳 const HEARTBEAT_TIMEOUT 10000; // 10秒收不到pong就判定超时 let heartbeatTimer null; let pongTimeout null; function startHeartbeat() { // 常规心跳 heartbeatTimer setInterval(() { if (ws.readyState WebSocket.OPEN) { ws.send(JSON.stringify({ type: ping })); pongTimeout setTimeout(() { console.warn(心跳超时正在重连...); ws.close(); }, HEARTBEAT_TIMEOUT); } }, HEARTBEAT_INTERVAL); ws.addEventListener(message, (event) { // 收到任何消息都清一下pong超时服务端的心跳回包也可能是空消息 if (pongTimeout) { clearTimeout(pongTimeout); pongTimeout null; } }); }心跳不仅仅是保活连接也是判断对端是否存活的依据。实践中服务端也应该主动检测空闲连接并清理否则会积累大量半开连接占用内存和文件描述符。4.3 onclose 1006这个错误码到底意味着什么大量WebSocket项目里都会遇到onclose, code: 1006, reason: 这个问题。1006是WebSocket规范中的“连接非正常关闭”错误码表示连接在没有收到正常关闭帧的情况下被中断。通俗解释就是客户端和服务端之间的连接“突然没了”但客户端不知道具体原因。造成1006的常见原因服务端进程崩溃或被杀没有机会发送关闭帧网络链路中断如断网、路由器重启、手机切换WiFi到4G反代或网关超时比如Nginx配置的proxy_read_timeout默认60秒超过60秒没有任何数据交换Nginx直接断开连接客户端收到的就是1006服务器在发送关闭帧前就中断了响应排查1006的思路很明确先确认是否因为长时间无数据导致的超时断开这类问题的出现规律一般是“连上之后一段时间不动然后断开”。解决方案是加心跳保活让连接一直有数据流动其次检查反代超时配置适当调大proxy_read_timeout并通过心跳解决超时断开问题。如果1006出现时机无规律就要结合服务端日志看进程是否在报错、内存是否溢出。4.4 WebSocket断线重连与消息补偿心跳解决的是“如何发现连接断了”接下来要解决的是“断了之后怎么办”。成熟的WebSocket客户端必须实现三重能力指数退避重连、消息队列缓存、消息序号去重补偿。最简单的重连策略是固定延迟重连但这对服务端不友好——大量客户端同时断线后同时重连会形成峰值。更好的策略是指数退避加抖动let retryCount 0; function connect() { ws new WebSocket(WS_URL); ws.onclose () { const delay Math.min(500 * Math.pow(2, retryCount), 30000) Math.random() * 1000; retryCount; setTimeout(connect, delay); }; ws.onopen () { retryCount 0; // 连接恢复后补发离线期间积压的消息 }; }消息补偿指的是连接断开期间用户发起的消息不能直接丢弃也不能在页面提示“请稍后再试”。正确做法是把发送接口封装成带队列的形式发消息时推入一个sendingQueue中同时写入localStorage持久化收到服务端确认再出队重连成功后把队列中未确认的消息按顺序重新发送。服务端侧需要配合做消息幂等——客户端重发消息时带上唯一消息ID服务端校验重复ID直接返回确认但不重复处理业务。没有这一步网络抖动时很容易出现订单重复创建的灾难。5. WebRTC浏览器里的P2P实时通信5.1 一对一直连从SDP交换到ICE候选人WebRTCWeb Real-Time Communication和前面三种方案有一个本质区别前面三种都是客户端-服务器架构所有数据都要经过服务端中转WebRTC的目标是浏览器之间的点对点直连数据流不经过服务器服务端只负责配对协调信令。WebRTC建立连接的过程分为两步SDP协商和ICE候选人收集交换。SDPSession Description Protocol本质是“通话双方的媒体能力描述”包括音视频编解码方式、加密参数、传输协议等。A端创建offer并发送给B端B端创建answer返回A端。这个交换过程需要借助某种传输通道——通常就是WebSocket或HTTP请求对应服务端的角色就是“信令服务器”。以两个浏览器建立音视频通话为例核心流程如下A端创建RTCPeerConnection添加本地媒体流创建offerconst pc new RTCPeerConnection({ iceServers: [{ urls: stun:stun.l.google.com:19302 }] }); // 获取本地摄像头和麦克风 const stream await navigator.mediaDevices.getUserMedia({ video: true, audio: true }); stream.getTracks().forEach(track pc.addTrack(track, stream)); // 创建offer并设置本地描述 const offer await pc.createOffer(); await pc.setLocalDescription(offer); // 通过信令通道发送offer给对端 sendToPeer({ type: offer, sdp: offer });对端通过信令通道收到offer设置远程描述并创建answerawait pc.setRemoteDescription(offer); const answer await pc.createAnswer(); await pc.setLocalDescription(answer); sendToPeer({ type: answer, sdp: answer });同时双方都要通过onicecandidate回调收集ICE候选人并经由信令通道发给对端pc.onicecandidate (event) { if (event.candidate) { sendToPeer({ type: candidate, candidate: event.candidate }); } };双方都完成候选交换后浏览器自行筛选出最优数据通路进入连接状态pc.onconnectionstatechange () { console.log(连接状态:, pc.connectionState); };5.2 NAT穿透STUN与TURN的取舍前面提到WebRTC是P2P直连但很多设备其实处于NAT后面比如家庭路由器的内网环境双方所在的内网地址不能直接被对方访问。ICE框架的候选类型有三种host本机内网地址、srflx通过STUN服务器获取的公网映射地址、relay通过TURN服务器中转的地址。STUN服务器的作用是帮客户端发现自己的公网映射地址。工作逻辑类似“对面镜子”你问STUN服务器“我的公网IP是什么”它回“你的公网IP和端口是xxx”通信双方拿到对方的公网映射后尝试直接连通。但如果双方都处于严格的对称型NAT后面这种情况在移动运营商网络比较常见直接连不成功就必须走TURN服务器中转。TURN服务器是一个中继数据流量的媒体服务器所有音视频数据都经过它转发带宽成本很高。选型上的经验是搭WebRTC应用必须同时配好STUN和TURN。只用STUN成功率大概在70-85%很多打电话打不通的case都是对称NAT问题。生产环境建议用coturn或licode做TURN服务器并通过服务质量做好转发压力预估如果业务以音视频通话为主TURN的带宽费用是核心成本之一要专门评估。前端可以通过以下方式动态获取ICE候选信息方便排查问题pc.onicecandidate (event) { if (event.candidate) { console.log(event.candidate.candidate); // 完整候选字符串 console.log(event.candidate.type); // host / srflx / relay } };5.3 DataChannel不止音频视频的点对点传数据WebRTC不只是音视频它还有DataChannel可以实现任意二进制和文本数据的P2P传输不需要经过服务器。相比WebSocketDataChannel的优势是数据直接点对点传输没有服务器中转延迟和带宽成本非常适合大文件传输、实时游戏指令、协作白板的笔迹同步。// 创建DataChannel需在offer创建之前 const dc pc.createDataChannel(fileTransfer, { ordered: true }); // 另一端监听 pc.ondatachannel (event) { const receiveChannel event.channel; receiveChannel.onmessage (msg) { // 处理收到的数据 }; };ordered: true表示数据按发送顺序到达适合文本消息、操作指令这类需要保序的场景。如果需要传输实时音视频兼容数据可以设置ordered: false, maxRetransmits: 0让数据包不可靠传输保证低延迟优先。文件传输推荐ordered: true加maxPacketLifeTime在有序与延迟之间找一个均衡点。WebRTC传输大文件的另一个经验是把文件切片后逐个通过DataChannel发送同时做本地确认队列对端收到数据后发ACK。和WebSocket消息补偿逻辑类似只是传输通道换成了P2P。断线后的处理要麻烦一些因为P2P连接重建还要重新交换SDP断点续传状态需要自己维护。5.4 信令服务器的最佳实践为什么还是离不开WebSocketWebRTC虽然数据不走服务器但连接建立过程的信令交换绕不开服务端。自己实现信令服务器时最合理的落地方案就是结合前面讲的WebSocket——负责SNP交换和ICE候选人转发同时可以顺便承担在线状态管理、房间管理、身份鉴权等业务逻辑。信令服务器的核心职责有管理在线用户列表与在线状态创建和销毁通话房间转发offer/answer/candidate消息给目标用户在ICE连接失败时下发TURN服务器凭证实战中信令通道可以做得非常轻量本质就是一个带鉴权的消息转发服务。但一定要处理异常消息格式——收到非法的JSON或未知type字段要忽略而不是让整个连接崩溃。另外要注意的是信令本身并不可靠保证对端一定收到所以在超时未收到answer时要有重发offer或提示“用户无响应”的兜底逻辑。6. 四种方案横向对比与选型决策路径6.1 从延迟、流量、服务端压力三个维度做量化对比很多团队选型完全凭“WebSocket很流行所以用它”这是很危险的。我按项目诉求从数据量化角度做了个对比表可以更直观地看到差异。维度HTTP轮询SSEWebSocketWebRTC消息延迟取决于轮询间隔秒级毫秒级毫秒级毫秒级流量开销高固定请求响应头低一次连接多头推送低但帧头也有一点最低P2P无中转服务端并发压力高连接数请求数有大峰值中每个连接保持一个长连接中高长连接保有内存/文件描述符低数据不经过服务端但信令有压力方向性客户端拉取服务端推送到客户端双向实时双向实时实现复杂度极低低中高典型场景兼容旧系统的兜底方案通知推送、行情、日志流聊天、协作、游戏视频会议、P2P文件传输数据的差异背后是架构决策。流量能不能接受几十倍的开销服务端有没有扛住成千上万长连接的能力用户体验能不能容忍秒级延迟这几件事一次要想清楚。6.2 场景化的选型建议贴近具体业务场景来谈选型会清晰很多。如果你的业务是低频状态刷新比如订单状态每30秒才可能变一次且用户能接受几秒的延迟HTTP轮询就够了。强行上WebSocket不为性能反而增加了服务端连接管理成本。如果是服务端向单一用户推送通知比如待办提醒、系统广播、下单后的物流动态通知SSE是性价比最高的方案。实现成本低自动重连配合Last-Event-ID还能无痛做消息补偿。要注意的只是在HTTP/1.1下连接数的限制HTTP/2适配后没大问题。如果是实时双向交互比如IM聊天、在线协作编辑、多人游戏房间同步WebSocket是标配。但它不是银弹需要你额外投入心跳、重连、消息补偿、幂等这些长连接的配套功夫。如果是音视频通话、屏幕共享、点对点传大文件WebRTC是唯一选择。但注意WebRTC不适合纯文本小消息的应用——引入P2P连接管理和TURN成本对IM消息来说太重了不如直接用WebSocket。6.3 混合架构实际项目里很少有人只用一种成熟的前端实时系统往往是多种方案配合使用。拿一个典型的在线客服系统举例客服与访客之间的文字对话走WebSocket保证双向实时访客排队进度和系统通知走SSE服务端单向推送更新如果两个用户需要语音沟通就升级WebRTC建立通话作为兜底如果WebSocket连续重连失败自动降级为2秒一次的HTTP轮询。这种混合架构的关键在于抽象出统一的消息收发层// 统一消息入口屏蔽底层传输实现 class RealtimeClient { constructor() { this.transport null; // WebSocket或SSE或轮询 } async connect() { try { await this.connectWebSocket(); } catch (err) { this.connectSSE(); } } send(message) { if (this.transport instanceof WebSocketTransport) { this.transport.send(message); } else { // 降级模式下改用HTTP POST发送 fetch(/api/send, { method: POST, body: JSON.stringify(message) }); } } }这类降级思路在弱网环境尤其重要。很多用户处在网络很差的场景下WebSocket长时间心跳超时如果直接展示“无法连接”体验很差。降级成轮询信号后可能慢但功能可用业务不会完全中断。这个设计会多花大概一天时间但产出物健壮很多。7. 从前端视角看完这四种方案后的技术债复盘具体聊一聊我实际在项目里踩过的坑和最终的总结。说到技术选型翻车的多半不是因为技术本身不够好而是因为对业务场景的理解不够精确。一些经验是这样沉淀下来的。第一优先考虑“如果是先发”用户的数量和消息频率决定方案而不是优先考虑“技术的先进性”。早期一个数据大屏项目用了WebSocket因为总觉得轮询不够“高级”。结果服务端是PHP跑在Apache上WebSocket进程管理方案不稳定运维配置也很麻烦最后被迫换回SSE。这个项目里服务端本来就是单向推数据SSE比WebSocket省了一个量级的复杂度。第二每一个实时通信方案都要提前设计监控和可观测性。WebSocket项目最常见的线上事故是服务端连接数撑爆导致新的用户全部连不上。如果没有连接数、消息吞吐、心跳成功率的监控这类问题往往要用户投诉了才发现。当时我用了一个简单的统计接口每5秒记录一次在线连接数、消息量、重连次数配合告警后续问题能第一时间定位。第三端上一定要有状态机和日志体系。实时通信链路涉及连接层、协议层、业务层多个层次出问题时要能快速定位在哪个环节。我常用的做法是在每个关键节点输出一条结构化日志连接创建、握手成功、首次消息、心跳超时、断线重连配合时间线能很快还原问题现场。最后说一下如果完全从团队能力与业务阶段考虑开发资源紧张就选SSE加上普通接口组合不要一上来就上WebSocket业务已经有成熟的IM需求再全面切换WebSocket有音视频需求就把WebRTC作为独立模块建设不要试图用WebSocket承载音视频流。回头来看这四个技术本质上它们解决的是同一个问题的不同侧面。HTTP轮询解决的是“有没有”的问题SSE解决的是“服务端推”的问题WebSocket解决的是“双向实时”的问题WebRTC解决的是“点对点高效传输”的问题。理解清楚这个问题边界比机械背诵哪种协议更先进重要得多。下次再遇到“实时推送选什么”的问题先问自己业务真的需要实时双向吗服务端能承受多少连接传输的数据是什么类型答案会自然浮现。
返回列表