先记住这个答案
我会先看数据是否需要双向实时流动。客户端发起一次请求,之后主要由服务端持续推送文本,例如消息通知、任务进度或流式回答,通常优先用 SSE,因为它基于普通 HTTP,浏览器的 EventSource 还提供事件解析和自动重连。双方都要频繁主动发消息,或者要传图片、音频等二进制数据时,WebSocket 的全双工能力更合适。它们不是谁更先进,而是谁更符合当前的通信方式。
- 单向文本推送优先
SSE - 高频双向通信或二进制传输选
WebSocket - “停止生成”这类偶发上行请求,不足以成为改用
WebSocket的理由
先问三个问题,就能完成大部分选型
选型时不要先比较协议名,而要先画出消息流向。谁在什么时机发消息,往往比“实时性”三个字更有判断价值。
- 只有服务端需要持续推送吗?
- 如果客户端只是建立连接,之后主要接收服务端事件,例如构建日志、订单状态、新闻更新或任务进度,优先考虑
SSE。它正好表达“服务端不断向浏览器发送文本事件”,实现和维护成本通常更低。 - 双方都要频繁、独立地发消息吗?
- 如果客户端随时都可能发送操作,服务端也要立即广播结果,例如在线协作中的光标和编辑操作、实时游戏指令或双向语音控制,选择
WebSocket更自然。一条连接可以同时收发消息,不需要再为上行操作单独建立请求链路。 - 数据只是偶尔更新吗?
- 如果页面几十秒甚至几分钟才更新一次,普通请求或轮询可能已经足够。实时功能不等于必须建立长连接;连接越多,服务端需要维护的状态和资源也越多。
两者真正有决定性的差异
SSE 是运行在 HTTP 响应上的文本事件流;WebSocket 则在握手完成后,通过独立的消息帧进行双向通信。两者都能让页面及时收到新数据,但它们解决的通信模型不同。
- 通信方向
SSE负责服务端到浏览器的单向推送,客户端要提交数据时仍然使用普通HTTP请求。WebSocket是全双工连接,双方可以在任意时刻主动发送消息。需要持续双向交互,是选择WebSocket最有分量的理由。- 数据类型
SSE传输的是UTF-8文本事件,特别适合日志、通知和文本增量。WebSocket同时支持文本和二进制消息,因此实时音频、图片分片或自定义二进制协议更适合使用它。- 重连与维护成本
- 浏览器的
EventSource能在连接断开后自动尝试重连,并可以结合事件id告诉服务端自己已经收到哪里。原生WebSocket没有自动重连,需要应用自己处理心跳、重连间隔、登录状态续期和消息补发。相应地,WebSocket给了应用更大的通信自由,也带来了更多连接管理工作。
用代码看懂 SSE:浏览器只负责接收
下面是一个任务进度页面。浏览器建立连接后,只监听服务端发来的 progress 和 done 事件;页面没有通过这条连接向服务端发送消息。
EventSource 接收任务进度JavaScriptconst source = new EventSource('/api/tasks/42/events');
source.addEventListener('progress', event => {
const { percent } = JSON.parse(event.data);
progressBar.value = percent;
});
source.addEventListener('done', () => {
source.close();
});这段代码最值得记住的不是方法名,而是只有“接收”。服务端可以依次发送 progress 事件,任务完成后再发送 done;如果用户要取消任务,可以另外调用一个普通的取消接口,没有必要为了这一次上行操作改成 WebSocket。
用代码看懂 WebSocket:同一连接既收又发
再看协同编辑。用户的每次修改要立即上传,其他人的修改也要随时推回来,因此同一个连接同时承担上行和下行消息。
const socket = new WebSocket('wss://example.com/rooms/42');
function sendEdit(change) {
if (socket.readyState !== WebSocket.OPEN) return;
socket.send(JSON.stringify({ type: 'edit', change }));
}
socket.addEventListener('message', event => {
const message = JSON.parse(event.data);
applyRemoteEdit(message.change);
});这里既有客户端主动调用 send(),也有客户端监听 message。这就是选择 WebSocket 的决定性原因:双方都需要在一条持续连接上独立、频繁地发送消息,而不仅仅是“服务端要实时推送”。
放进真实业务里,会更容易理解
不要把选型背成“简单场景用 SSE,复杂场景用 WebSocket”。同一个产品中也可能同时使用普通请求和实时通道,关键仍然是每条数据怎样流动。
- 消息通知、构建日志和任务进度
- 这些场景主要由服务端产生新状态,浏览器负责接收和展示,使用
SSE通常更直接。连接可以沿用现有的HTTP路由、状态码和代理体系;不过仍要检查反向代理是否缓冲响应,否则服务端已经发送的数据可能不会立即到达浏览器。 - AI 流式回答
- 用户先提交完整问题,服务端随后不断返回文本增量,本质上仍以单向下行为主,可以使用
SSE或采用相同事件格式的fetch流。用户点击“停止”时,另发一个取消请求通常就够了;只有需要持续上传语音、频繁修改参数或进行真正的双向并发流时,WebSocket的优势才明显。 - 协同编辑、游戏和实时语音
- 用户操作要不断上传,其他人的状态又要及时下发,双方都在持续产生消息。此时如果坚持使用
SSE,往往还要额外维护一套上行请求和状态关联;WebSocket用一条双向连接承载这类交互更合适。
选完协议,还要补上的边界
协议只负责传输消息,不会自动解决业务可靠性。无论选择哪一种,只要断线后不能丢数据,都需要给消息分配稳定的事件 ID 或序号,并由服务端保留一段可补发的数据,客户端负责去重和恢复。
EventSource和fetch流不要混为一谈- 自动重连是浏览器
EventSource的能力。项目如果为了发送POST请求或自定义请求头,改用fetch + ReadableStream接收事件流,就需要自己处理解析、重连和续传,不能继续假设浏览器会自动完成这些工作。 - 长连接都要经过真实基础设施验证
SSE可能受到代理缓冲和HTTP/1.1同源连接数限制;WebSocket可能被空闲超时断开,也需要负载均衡和服务端容量支持。选型完成后,应至少验证断网重连、代理超时、登录过期和多标签页连接数量,而不是只在本机开发环境里确认“能收到消息”。
容易答错的地方
- “
WebSocket一定比SSE更快” - 两者建立连接后都可以持续传输数据,实际延迟通常还受网络、服务端处理和业务逻辑影响。选型首先看通信方向与数据类型,不能因为
WebSocket功能更多,就默认它在单向文本推送里体验更好。 - “
SSE会自动重连,所以消息不会丢” - 自动重连只代表浏览器会再次尝试连接,不代表服务端一定保存了断线期间的消息。需要可靠恢复时,还要配合事件
id、服务端历史记录和客户端去重;否则连接虽然恢复,中间的数据仍可能缺失。 - “聊天功能就必须使用
WebSocket” - 如果聊天是低频发送、服务端单向流式回复,普通
POST加SSE或响应流也能完成。只有双方需要持续并发发送、低延迟广播或二进制传输时,WebSocket的复杂度才真正值得承担。
面试官还会怎么问?
AI 对话支持“停止生成”,是不是就该从 SSE 换成 WebSocket?
不一定。停止只是一次偶发的上行操作,可以中止当前读取,再调用独立的取消接口。若除了停止之外,客户端还要持续上传语音、频繁发送控制事件,并且这些事件要和下行内容并发传输,才更有理由改用 WebSocket。
EventSource 断线后自动重连,服务端怎样避免重复发送?
服务端可以为事件分配递增的 id,并保留一段事件记录。客户端重连时会携带最后收到的事件标识,服务端从下一条开始补发;客户端仍应按 id 去重,因为网络重试不能天然保证业务上的“恰好一次”。
原生 EventSource 不能设置自定义 Authorization 请求头,怎么处理鉴权?
同源场景可以使用安全的会话 cookie;跨域时要同时正确配置凭据和跨域响应。若必须使用自定义请求头或 POST 请求,可以改用 fetch 读取事件流,但事件解析、重连和续传也要由应用自己实现。
已经用了 WebSocket,还需要普通 HTTP 接口吗?
通常需要。登录、历史记录查询、文件上传等明确的请求—响应操作仍适合普通 HTTP;WebSocket 主要承载实时事件。把所有接口强行塞进一条长连接,会增加协议设计、错误处理和调试成本。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。