ARTICLE DETAIL

资讯详情

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

SSE与WebSocket怎么选?一文讲透原理、对比与实战

SSE与WebSocket怎么选?一文讲透原理、对比与实战 搞实时通信选型这两年遇见最多的问题就是SSE和WebSocket摆在一起不知道选哪个。我最近给一个AI对话项目做消息通道选型后端同事坚持用SSE流式输出前端同事说WebSocket更稳两边僵持了半天。其实这俩根本不是“谁替代谁”的关系底层机制、适用场景、抗风险能力完全不同盲目跟风只会给自己埋坑。我在一线做实时通信相关开发有十来年了SSE和WebSocket都在生产环境里真正扛过流量。这篇就把两种技术从原理、对比、场景选型到代码实现再到坑点排查完整过一遍。不管你是做AI应用、IM聊天、监控告警、行情推送还是物联网指令下发看完基本能定下来该用哪套。1. 两种技术的底层逻辑先弄清它们各自怎么跑的很多人搞混SSE和WebSocket是因为都叫“实时通信”但实现逻辑完全不是一回事。一个是在HTTP协议上做了个特殊用法一个是彻底脱离了HTTP语义、在TCP上另起炉灶。不理解这层后面所有选型都是拍脑袋。1.1 SSE坚守在HTTP上的“单向广播”SSE全称Server-Sent Events服务器发送事件。它本质上是HTTP协议的一种扩展用法。正常HTTP请求是客户端发请求、服务器响应一次就结束了SSE改变的地方在于服务器收到请求后不关闭连接持续向客户端输出数据流数据可以一段一段推出来直到服务器主动断开。关键点是SSE不需要任何新协议它只是利用了HTTP现有的能力加上一个特殊的Content-Type客户端发起请求时携带Accept: text/event-stream服务器响应头设置Content-Type: text/event-stream连接建立后服务器按固定的event-stream格式持续写数据数据格式长这样id: 1 event: message data: 第一段内容 id: 2 event: message data: 第二段内容每条数据用空行分隔data就是真正要推送的内容id用于断线重连时的续传标记event可以定义自定义事件类型。服务器还可以发送以冒号开头的注释行比如: heartbeat这种行客户端会忽略但能在连接空闲时维持活跃状态避免中间代理层把连接关了。要说清SSE还得提它的演进背景。早年的网页实时刷新靠Ajax轮询客户端隔几秒发一次请求问“有数据了吗”。后来有了长轮询服务器把请求先挂住有数据才返回。再后来HTTP/1.1普及了chunked编码可以分块传输数据SSE就是在这个基础上成熟起来的。所以一句话SSE不是一个新协议它是HTTP协议上一种“长响应 流式输出”的特殊用法。1.2 WebSocket从HTTP升级出的一条“全双工专线”WebSocket同样以HTTP请求作为起点但它不走普通HTTP响应的路线。客户端发一个带有升级标记的HTTP请求服务器如果同意就返回101 Switching Protocols状态码随后这条TCP连接彻底切换为WebSocket协议进入“专线模式”。这条专线建立后客户端和服务器处于完全对等的地位任何一方都能随时向对方发消息。数据以帧的形式传输既可以传文本也可以传二进制比如音频、视频、图片。它的设计初衷就是解决HTTP“一问一答”模式在实时交互场景下的低效问题。日常可以这样理解SSE是广播电台你只能听不能点播对话WebSocket是电话两头都能说而且是全天不断线的专线电话。1.3 一句话总结两者的本质区别我给团队培训时经常用一句话收束SSE在HTTP上做“单向、文本、自动重连”的流式推送WebSocket在TCP上做“全双工、长连接、支持二进制帧”的实时传输只要把这句话刻在脑子里后面所有对比和选型都不会跑偏。2. 逐项对比连接方式、数据格式、部署环境逐个拆光说原理还不够真正选型要看具体的对比维度。我按实际开发中大家最关心的几个点逐一展开。对比维度SSEWebSocket底层协议HTTPtext/event-stream基于HTTP握手升级的独立ws/wss协议通信方向单向服务器推送到客户端全双工两端随时互发数据格式纯文本UTF-8为主文本帧 二进制帧自动重连浏览器EventSource原生支持带断点续传无内置机制需自己实现心跳和重连浏览器支持主流浏览器均支持EventSource全部支持跨域限制受CORS限制握手时有Origin校验但连接建立后基本不受同源限制代理穿透天然穿过HTTP代理部署简单需要代理层配置升级头相对繁琐浏览器连接并发限制受HTTP/1.1同域名6连接限制基本不受此限制服务端实现复杂度极低几乎零依赖较复杂需处理握手、心跳、会话、广播2.1 连接模型与双工能力最核心的分水岭第一个决定性的差异就是通信方向。SSE只能服务器往客户端推客户端如果在同一个连接里向服务器发数据对不起做不到。但现实中很多业务不完全是单向的这时候就得用WebSocket。举个例子一个在线协同编辑文档A用户输入的每个操作都要实时同步给B用户同时还要接收B用户的操作。这个场景下如果选SSEA的操作得老老实实再走一个普通的HTTP POST请求推送和上行分成两条通道代码写起来别扭不说状态同步还容易错位。WebSocket一条连接全搞定。反过来如果业务本来就是纯推送比如告警通知、任务进度、行情刷新你硬上WebSocket就属于“杀鸡用牛刀”白白增加一大截维护成本。选择第一条原则先看数据流向单向优先考虑SSE双向频繁交互就上WebSocket。2.2 兼容性与代理穿透上了生产环境才知道差距前端浏览器兼容性两者其实都很好。老掉牙的IE确实不支持EventSource也几乎不支持WebSocket都需要polyfill或降级方案——但2025年的今天正常项目基本不用考虑IE了。真正容易出问题的是服务端部署环境。SSE走的是普通HTTP所有现成的HTTP负载均衡、网关、防火墙都可以直接透传几乎不用额外配置。WebSocket则需要代理层额外支持协议升级比如nginx里必须显式配置Upgrade和Connection头否则握手直接失败。这在实际运维中差别很大。我见过不少团队开发环境WebSocket跑得好好的一上测试环境走nginx反代就频繁掉线排查半天发现是代理层还是默认的HTTP配置没有开放WebSocket升级。2.3 连接数限制浏览器侧的隐形天花板这个点很多人没注意但对SSE影响很大。浏览器对同一域名下的HTTP/1.1连接数有硬性限制通常并发上限是6个。SSE的每个连接本质上就是一个占坑的HTTP请求一个页面如果开了两三条SSE流再加上图片、接口请求很容易撞满连接数上限。后面的请求全部排队等待表现出来就是页面卡顿、接口迟迟不返回。WebSocket不受这个限制因为它在连接建立后已经脱离了普通HTTP请求队列。一个浏览器挂几十条WebSocket连接是常事。所以在监控大屏、多路行情这类需要同时挂多个推送通道的场景SSE很容易撞墙要么拆子域名要么升级HTTP/2要么干脆选WebSocket。2.4 协议开销与二进制支持带宽敏感场景绕不开SSE的数据是文本格式每条消息都要带data:、空行这些额外内容纯文本还好一旦涉及二进制数据就非常尴尬。你总不能用Base64把大图大音频塞进SSE里传吧开销直接膨胀三分之一以上。WebSocket的二进制帧机制配合protobuf或MessagePack这类高效序列化做消息协议带宽利用率比SSE高得多。如果业务会高频推送音视频帧、点云数据、序列化对象WebSocket是唯一合理选择。2.5 断线重连SSE最容易被低估的原生优势SSE有一个WebSocket很难比肩的杀手级特性自动重连。浏览器原生EventSource在连接断开后会自行重新发起连接如果服务器在消息里带了id字段浏览器重连时会自动把Last-Event-ID带上服务器端就可以根据这个ID把断线期间漏掉的消息补推给客户端。这种“断点续传”能力是SSE协议自带的几乎不用写任何代码。WebSocket就惨了它没有任何内置的断线重连和消息补偿机制。不要说网络切换这种极端情况就是代理层空闲超时断开、服务端发布重启导致的连接断开都得靠业务代码自己去收尸。你需要自己实现心跳机制、重连策略、消息序列号、补偿拉取这一整套写下来工作量不小。我下面会专门给一套WebSocket心跳重连模板有需要可以直接抄。2.6 服务端生态Java、Python、Node生态的差异不同语言栈里这两种技术的实现复杂度差距很明显。给你看一下我实际了解的生态情况语言/框架SSE方案WebSocket方案Java / Spring BootSseEmitter几行代码搞定WebSocketHandler或ServerEndpoint还要配会话管理Python / DjangoStreamingHttpResponse即可django-channels要上ASGI异步框架Python / FlaskResponse stream_with_contextflask-sock或websockets库Gohttp.ResponseWriter直接写流gorilla/websocket或nhooyr.io/websocketNode.jsres.write持续输出ws库配合心跳包简单说几乎所有后端框架里SSE的入门门槛都低一截WebSocket需要处理的细节明显更多。3. 选型思路结合真实场景到底该选谁了解到这里你会发现两种技术各有优势没有绝对优劣。我把实际开发中最常见的四类场景拿出来分析给出我自己的选型结论。3.1 AI大模型流式对话SSE的主场最近两年最热的一个SSE使用场景就是大模型AI对话。用户输入Prompt后大模型是一个字一个字往外蹦的前端要实时渲染这些内容形成打字机效果后端到前端的这条通道就是标准的带外推送流。为什么这个场景是SSE主场因为数据流向非常清晰用户提问走一次普通的HTTP POST请求模型结果的返回走流式输出服务器逐段把token推给前端。整个过程中前端几乎不需要往已建立的流连接里写数据。其中有一个细节值得单独说中断控制。用户在AI对话里点击“停止生成”按钮本质上是想中断当前这条响应流。如果用原生EventSource你会发现它根本做不到因为它不支持发送自定义请求头也不支持POST请求体更没有一个“断开单条流”的干净方法。所以实际项目中AI对话的流式输出通常不是用EventSource实现的而是用fetchReadableStream来模拟SSE接收再配合AbortController实现主动中断。热词里提到的“通过sse流式输出实现大模型回答实时渲染配合abort”说的就是这个完整链路。我下面会给出这套方案的代码模板。3.2 后台任务进度、通知推送、监控告警SSE的低成本优势明显后台批量处理任务时前端需要实时显示进度百分比站内通知系统需要随时把新消息推到在线用户运维监控系统需要把最新指标推到监控大屏。这些场景的共同特点是数据流单向客户端不往服务器发指令或者指令下发走另一边普通的HTTP接口就够了。这类场景我强烈推荐SSE。实现成本极低维护成本也低天然支持断线重连部署时不挑代理环境。尤其在后端用Python Django或Flask的场景StreamingHttpResponse就可以实现完全不用引入channels那套ASGI复杂度。我有一次给一个数据处理平台做进度推送后端是Flask前后端加起来不到一百行代码SSE流就稳定跑了大半年一次故障都没有。换成WebSocket的话光心跳和重连逻辑就要多写几百行。3.3 聊天、协同编辑、行情、游戏WebSocket的绝对主场这些场景的共同特征是高频双向交互。聊天IM里你发一条消息给服务器服务器要把这条消息转发给群组里的所有人同时你还要实时接收别人发来的消息这是典型的全双工通信。协同编辑、多人在线游戏、tick级行情推送也一样每条操作都是双向高频的。这类业务硬用SSE代码会丑陋到无法维护。上行要走独立的HTTP请求通道下行要维护一条SSE流两套连接的状态同步、时序保证、错误处理复杂度叠加起来远超直接上WebSocket。选WebSocket的另一个好处是消息帧结构统一聊天消息、指令、心跳、错误码都可以约定在一个消息协议里前后端一套代码处理开发体验比“HTTP SSE”双通道清爽得多。3.4 Python项目里的“后台有数据要推给前端”与反向连接热词里有“python django websocket实现后台有数据前端推送”和“python反向websocket”这两条我结合自己的实践说一下。如果后台是Python Django业务场景是“数据库里有新数据后端主动推给前端刷新页面”你先别急着上django-channels。先问自己一句前端需要在这个通道里往回发数据吗如果不需要用Django的StreamingHttpResponse写一个SSE流就完事了零额外依赖部署也轻松。这在任务进度、数据看板、通知推送场景里性价比吊打channels。如果需要双向交互比如聊天、远端控制指令、协同编辑那就得上django-channels把项目改成ASGI启动配置ProtocolTypeRouter再写Channel Layer处理广播。复杂度确实上一个台阶但这是双向通信的必然代价。再解释一下“python反向websocket”。这个说法通常指Python不扮演WebSocket服务端而是作为客户端去主动连接一个WebSocket服务端。典型的场景是Python后台脚本作为WS客户端连上云端推送服务订阅行情或任务消息收到数据后处理或转发给内部系统。这在你写数据采集、消息桥接、自动化运维脚本时会非常常见。用websockets这个Python库实现起来非常简洁我下面会给出示例。3.5 快速决策表一句话定位你的场景业务场景首选方案说明AI大模型流式输出SSE单向流式渲染fetch ReadableStream模拟后台任务进度SSE单向推送成本最低带自动重连监控面板/大屏数据刷SSE注意HTTP/1.1并发限制可拆子域站内通知/告警推送SSE单向推送断线自动恢复IM聊天/群消息WebSocket全双工高频双向交互协同编辑文档WebSocket双向差分同步在线游戏/位置同步WebSocket低延迟 二进制帧tick级行情推送WebSocket高频推送 二进制序列化IoT设备指令下发WebSocket双向控制指令 状态上报4. 代码实操直接可用的SSE和WebSocket实现理论说再多不如直接抄代码。下面这几段都是我在真实项目里跑过的模板指标、目录结构、注意事项都标清楚了。4.1 后端SSE实现Spring Boot与Python各给一套如果你后端是Java Spring BootSSE实现简直便宜。一个SseEmitter就搞定了GetMapping(path /sse/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter stream() { SseEmitter emitter new SseEmitter(0L); // 0L表示不超时 ExecutorService executor Executors.newSingleThreadExecutor(); executor.execute(() - { try { for (int i 1; i 10; i) { emitter.send(SseEmitter.event() .id(String.valueOf(i)) .data(第 i 条消息)); Thread.sleep(1000); } emitter.complete(); } catch (Exception e) { emitter.completeWithError(e); } }); executor.shutdown(); return emitter; }注意这里的SseEmitter构造函数。默认情况下Spring Boot的SseEmitter在30秒内没有任何数据发送就会超时断开这在AI流式输出和低频推送场景都会造成问题。生产环境建议设置成0L永不超时但前提是你得处理好心跳否则代理层还是会把空闲连接干掉。SSE的心跳通常是一个注释行推送// 心跳线程每20秒发送一个注释行 emitter.send(SseEmitter.event().comment(ping));Python Flask端更简单from flask import Response, stream_with_context import time stream_with_context def event_stream(): for i in range(1, 11): yield fid: {i}\ndata: 第{i}条消息\n\n time.sleep(1) app.route(/sse/stream) def sse_stream(): return Response(event_stream(), mimetypetext/event-stream)Django类似把Response换成StreamingHttpResponse即可mimetype同样设置成text/event-stream。核心思路是一样的函数是一个生成器持续往外yield符合event-stream格式的文本。4.2 前端SSE接收EventSource与模拟SSE事件流推送用EventSource就能最干净地接收const eventSource new EventSource(/sse/stream); eventSource.onmessage (event) { console.log(收到消息, event.data); }; // 监听自定义事件 eventSource.addEventListener(customEvent, (event) { console.log(自定义事件, event.data); }); eventSource.onerror () { console.log(连接异常浏览器会自动重连); };但前面说了真正的AI对话流式场景前端更推荐用fetch ReadableStream模拟SSE。原因有二原生EventSource不支持自定义请求头也不支持POST请求体需要中断时原生EventSource没有可靠的单流中断API而AbortController是标准做法。核心代码长这样const controller new AbortController(); async function chat(prompt) { const response await fetch(/api/chat, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer ${token} }, body: JSON.stringify({ prompt }), signal: controller.signal }); const reader response.body.getReader(); const decoder new TextDecoder(); while (true) { const { done, value } await reader.read(); if (done) break; const chunk decoder.decode(value, { stream: true }); // 解析chunk中形如 data: xxx 的行逐步渲染到界面 parseAndRender(chunk); } } // 用户点击“停止生成”时 function stopChat() { controller.abort(); }这里有个细节TextDecoder的{ stream: true }参数必须带上否则一个中文字符被EOF截断时解码会直接变成乱码。我在项目里没加这个参数时AI回复里偶尔出现一个“”排查了半天才定位到是这个原因。4.3 后端WebSocket实现Java与PythonJava Spring Boot里用原生注解方式实现一个WS服务端很直接Component ServerEndpoint(/ws/chat) public class ChatEndpoint { private static final CopyOnWriteArraySetSession SESSIONS new CopyOnWriteArraySet(); OnOpen public void onOpen(Session session) { SESSIONS.add(session); System.out.println(连接建立: session.getId()); } OnMessage public void onMessage(String message, Session session) throws IOException { // 广播给所有在线客户端 for (Session s : SESSIONS) { if (s.isOpen()) { s.getBasicRemote().sendText(message); } } } OnClose public void onClose(Session session) { SESSIONS.remove(session); } OnError public void onError(Session session, Throwable error) { System.out.println(连接异常: error.getMessage()); } }这里用CopyOnWriteArraySet存放在线会话是为了在广播时避免并发修改异常。这个容器在写少读多的场景下性能不错代码也简洁。一个重要警告如果你的Spring Boot应用走了nginx反向代理必须确认nginx配置了WebSocket升级否则前端连上来直接失败或者秒断。nginx配置我放在下面一节的排障里讲。Python Django Channels的结构与Java差异很大简单给一个最小骨架# routing.py from django.urls import re_path from .consumers import ChatConsumer websocket_urlpatterns [ re_path(rws/chat/$, ChatConsumer.as_asgi()), ] # consumers.py class ChatConsumer(AsyncWebsocketConsumer): async def connect(self): await self.accept() async def receive(self, text_data): # 收到前端消息处理后group_send给房间 await self.channel_layer.group_send( chat_room, {type: chat.message, text: text_data}, ) async def chat_message(self, event): await self.send(text_dataevent[text])Channels本身的复杂度主要在ASGI配置和Channel Layer的选型上生产环境一般会用Redis做Channel Layer这样才能在多实例间广播消息。4.4 前端WebSocket接收完整可用的心跳重连模板WebSocket前端有一个绕不开的坎连接说断就断。移动端App切后台、代理空闲超时、服务端发布重启任何一步都会让连接消失而且WebSocket没有自动重连。这里给一个我在生产环境验证过的心跳加指数退避重连模板let ws null; let retryCount 0; let heartBeatTimer null; let lastPong Date.now(); const MAX_RETRY_DELAY 30000; function connect() { ws new WebSocket(wss://api.example.com/ws); ws.onopen () { retryCount 0; startHeartBeat(); }; ws.onmessage (event) { const data JSON.parse(event.data); if (data.type pong) { lastPong Date.now(); return; } handleMessage(data); }; ws.onclose () { stopHeartBeat(); const delay Math.min(1000 * Math.pow(2, retryCount), MAX_RETRY_DELAY); retryCount; setTimeout(connect, delay); }; ws.onerror (error) { console.error(WebSocket异常, error); }; } function startHeartBeat() { lastPong Date.now(); heartBeatTimer setInterval(() { const now Date.now(); // 超过45秒没收到pong认为连接已死手动关闭触发重连 if (now - lastPong 45000) { ws.close(); return; } if (ws.readyState WebSocket.OPEN) { ws.send(JSON.stringify({ type: ping })); } }, 15000); } function stopHeartBeat() { if (heartBeatTimer) { clearInterval(heartBeatTimer); heartBeatTimer null; } }心跳周期怎么定有个经验法则**取代理层空闲超时时间的一半。**比如nginx配了proxy_read_timeout 60s心跳间隔就设25到30秒这样在代理决定断开前总有心跳包维持连接活跃。如果代理层没有明显超时也建议至少30到60秒一次心跳因为移动网络和NAT设备都有自己的空闲连接回收机制。5. 常见问题与排查实录这一节全是我在实际项目里踩过的坑每一个都对应具体的报错或诡异现象。建议直接当作排查手册来用。5.1 SSE连接正常但迟迟不触发数据nginx缓冲和空闲超时热词里有一条很典型的报错stream disconnected before completion: idle timeout waiting for sse。这个报错的场景几乎都是SSE服务在nginx或API网关后面连接建立正常但一段时间没数据后被代理层强制断开了。原因是双重的。第一nginx默认开启proxy_buffering会把后端流过来的数据先缓存到一定大小再转发给客户端SSE这种逐条推送的数据就容易堆积在缓冲区出不去前端长时间收不到数据。第二即使关闭了缓冲nginx默认的proxy_read_timeout是60秒SSE连接长时间空闲不写数据代理层就会判定连接超时然后切断。解决方案是在nginx的SSE路由配置里做调整location /sse/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ; proxy_buffering off; proxy_cache off; proxy_read_timeout 1d; proxy_send_timeout 1d; }另外一个非常实用的经验后端在空闲时定期发送SSE注释行:来保活。数据量极小但能让所有中间层看到“这条连接还活着”大多数空闲超时问题能直接规避。5.2 WebSocket连接频繁断开心跳缺失和代理配置WebSocket频繁断开的征兆很典型连接建立后能正常收发几分钟然后突然断开过一段时间又自动重连。这里最大的嫌疑是代理层的空闲超时或者服务端根本没有心跳机制。先检查三样东西确认nginx里WebSocket路由配了Upgrade头而不是默认配置确认代理层读写超时足够大或者心跳足够频繁确认服务端和客户端都实现了心跳逻辑nginx里的标准WebSocket反代配置长这样location /ws/ { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }如果发现少了Connection upgrade这一行握手阶段就会异常或者连接一建起来就被代理当成普通HTTP请求处理。5.3 WebSocket握手失败与跨域问题WebSocket握手失败的典型表现是前端onopen不触发而onerror触发或者浏览器Console直接报400、403、404。排查顺序我建议这样先用一个通用测试工具验证服务端本身有没有问题。热词里有人搜“websocket test client”本质就是找一个能在不依赖浏览器环境的情况下直连WebSocket服务端的工具。我推荐用命令行工具wscat或者自己写一个几十行的Python脚本用websockets库直接连。如果命令行工具能连上问题就锁定在前端到代理层这段如果命令行工具也连不上问题一定在服务端。跨域导致的握手失败也很常见服务端会拒绝带有非法Origin的握手请求前端表现通常是403。解决方法是服务端显式允许合法域名或者开发环境下不校验Origin。要注意生产环境千万别直接放开*WebSocket一旦被跨域恶意连接等于给别人开了一个实时数据后门。5.4 SSE中文乱码与被动缓冲SSE本身是文本协议中文乱码几乎都是字符集问题。我在一个老项目里遇到过服务端明明返回的是UTF-8中文前端解析出来却是一串乱码。排查后发现是响应头没有带charsetutf-8某些旧代理按ISO-8859-1去解码了。解决方式就是强制指定字符集Java的Spring Boot可以这样改GetMapping(path /sse/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE ;charsetutf-8)Python端则是mimetype后面带上;charsetutf-8。另外阿里云、腾讯云的SLB/网关产品上SSE需要确认是否关闭响应缓冲否则也会出现“数据攒到一定量才一次性转发”的现象。5.5 通过WebSocket发送POST请求是什么操作热词里有一条“通过websocket发送post请求”看起来反直觉其实可以解释。WebSocket协议本身没有HTTP的Method、URL、Header这些概念但你可以把WebSocket消息当作一个“通用信封”在HTTP语义之上再造一个自定义路由协议。比如消息体约定为JSON{ type: POST, url: /api/orders, payload: { orderId: 123 } }WebSocket服务端收到这条消息后内部解析出type和url代替客户端去调用对应的HTTP接口再把结果通过WebSocket回传。但这种设计通常只在特殊场景才有价值比如客户端和服务器之间已经建立了WebSocket长连接且客户端所在网络无法直接访问HTTP接口时才会让服务端代为转发。如果客户端本来就能访问HTTP接口直接用fetch发POST简单直接完全没必要绕一圈WebSocket。5.6 HTTP/1.1连接数限制对SSE的连锁影响前面提过浏览器对同一域名HTTP/1.1并发连接数有6条左右的限制。SSE每条连接都占一个名额。如果一个页面既要看实时行情又要收任务进度又待着一条通知通道三个SSE流就把名额占了一半其他图片和接口请求全得排队。这个问题的解法有三种把不同的SSE通道拆到不同子域名比如push1.example.com和push2.example.com每个域名各自有连接数配额服务端协议升级到HTTP/2连接复用后限制大幅放宽多条推送合并为一条SSE流前端按消息类型分发我在监控大屏项目里实践过最省事的是合并通道其次是上HTTP/2。拆子域是最容易出事的做法因为又得处理跨域和证书问题。我个人在实际项目里有个习惯选型时先问自己三个问题数据方向是单向还是双向消息是纯文本还是夹杂大量二进制页面层级深不深、连接数容不容易爆这三个问题问完答案基本就定了。很多时候不用陷入“哪个更先进”的争论SSE和WebSocket就是两条赛道上的不同工具用对了地方都稳用错了地方都狼狈。最后再分享一个小技巧。如果团队里好几个人都在纠结这事就让后端同事统计一下现有接口的数据流向。你会发现90%的项目所谓的“实时通信”真正需要客户端反向发消息的场景不到一小半。剩下那些都只是需要把服务端数据及时送出去这种工作交给SSE能省掉的不只是代码量还有一整套心跳、重连、会话维护的运维负担。
返回列表