做Java后端开发的同学,大概率都遇到过这样的困惑:要实现“实时推送”或“流式输出”——比如AI对话的打字机效果、后台任务日志实时刷新、股票行情更新,到底该用SSE还是WebSocket?两者都能实现“服务端向客户端主动推送数据”,但用法、场景、复杂度天差地别。选对了,开发效率翻倍、运维成本骤降;选错了,不仅代码冗余,还可能出现连接不稳定、资源浪费的问题。

单向流式输出,选SSE;双向实时交互,选WebSocket。

比如AI对话的流式返回(只有服务端推给客户端)、实时通知、日志推送,用SSE足够简单高效;而实时聊天、在线游戏、协同编辑(客户端和服务端要互相发消息),必须用WebSocket。

一、核心差异:一张表看懂所有区别

很多人混淆SSE和WebSocket,本质是没抓住它们的核心定位——SSE是“轻量级单向推送工具”,WebSocket是“全功能双向通信协议”。

对比维度 SSE(Server-Sent Events) WebSocket
通信方向 单向:仅服务端 → 客户端推送 双向:客户端 ↔ 服务端,可同时发送
协议基础 基于HTTP协议,无需协议升级,复用现有HTTP架构 独立于HTTP的新协议,需通过HTTP握手升级为ws/wss协议
数据格式 仅支持UTF-8文本(二进制需手动编码为Base64) 支持文本、二进制(音频、视频、文件等),灵活性极高
连接维护 内置自动重连机制,连接断开后客户端自动重试(可设置间隔) 无内置重连,需手动实现心跳(ping/pong)、重连逻辑
实现复杂度 极低:客户端原生EventSource API,服务端只需输出指定格式文本流 较高:需处理握手、帧解析、心跳、重连,依赖专门库(如Netty)
资源占用 基于HTTP长连接,头部开销略高,但实现简单,服务端开发成本低 单连接资源占用低(二进制帧开销小),但服务端需维护大量长连接,并发要求高
兼容性 现代浏览器均支持(IE除外),兼容性达97% 几乎所有现代浏览器支持(含IE10+),兼容性达99%+
适用场景 AI流式输出、实时通知、日志推送、行情单向更新 实时聊天、在线游戏、协同编辑、音视频通话、IoT双向交互

补充一句:SSE的“单向”不是说客户端完全不能向服务端发消息,而是客户端要发消息,必须额外发起HTTP请求(如POST),不能通过SSE连接本身发送;而WebSocket的连接建立后,双方可以随时互相发消息,无需额外请求。

二、深度拆解:为什么这个场景选它,不选它?

1. 什么时候优先选SSE?(性价比之王)

SSE的核心优势是“简单、轻量、零额外配置”,尤其适合「服务端单向推送,客户端偶尔反馈」的场景,不用为了“推送”这个简单需求,去承担WebSocket的复杂开发和维护成本。

典型场景:

  • AI流式输出:比如LangChain4j调用Ollama模型,实现AI回答的“打字机效果”——服务端拿到模型的token流,持续推给客户端,客户端只需要接收并展示,无需向服务端反馈,用SSE最简洁,不用处理复杂的双向通信。

  • 实时通知:后台任务完成提醒、新消息通知、系统告警(如服务器CPU使用率超标),服务端主动推送给客户端,客户端无需回复,SSE的自动重连的特性,能保证通知不丢失。

  • 日志实时推送:后台脚本、定时任务的运行日志,实时推送给前端页面,方便开发者查看,无需频繁刷新页面,SSE的文本流格式刚好适配日志输出。

核心优势:开发快、维护简单,无需修改现有HTTP架构(代理、防火墙无需额外配置),内置重连机制,容错率高。

避坑点:不支持二进制传输,若需要推送图片、音频等,不适合选SSE;客户端向服务端发消息需额外处理,不适合高频交互场景。

2. 什么时候必须选WebSocket?

WebSocket的核心优势是“双向通信、低延迟、高灵活”,只要场景需要「客户端和服务端高频双向交互」,或者「传输二进制数据」,WebSocket是唯一选择,没有替代方案。

典型场景:

  • 实时聊天:网页版IM、群聊、一对一聊天,客户端发送消息给服务端,服务端转发给其他客户端,双方需要实时响应,WebSocket的全双工特性的能实现“发消息即收到”,延迟极低。

  • 在线游戏:多人联机小游戏、实时竞技游戏,客户端需要实时向服务端发送操作指令(如移动、攻击),服务端实时推送其他玩家的状态,必须用WebSocket的双向通信。

  • 协同编辑:腾讯文档、飞书文档,多个用户同时编辑,一个用户的操作实时推送给其他用户,客户端和服务端需要频繁交互,WebSocket能保证操作同步无延迟。

  • 二进制传输:实时音视频通话、实时文件同步、IoT设备数据采集(传感器输出二进制数据),WebSocket原生支持二进制传输,无需额外编码,效率更高。

核心优势:双向通信、低延迟、支持二进制,适合高频交互场景,资源利用率高。

避坑点:开发复杂度高,需手动处理心跳、重连、帧解析;部分老旧代理/防火墙可能不支持WebSocket协议升级,需额外配置。

三、后端如何快速实现?

1. SSE实现

SSE无需引入额外依赖,SpringBoot原生支持,核心是设置响应格式为“text/event-stream”,并持续输出指定格式的文本流。


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalDateTime;

@RestController
public class SseController {

    // AI流式输出示例(模拟LangChain4j调用模型的token流)
    @GetMapping(value = "/sse/ai-stream", produces = "text/event-stream;charset=UTF-8")
    public void aiStream(HttpServletResponse response) throws IOException, InterruptedException {
        // 禁用缓存,确保流式输出
        response.setContentType("text/event-stream;charset=UTF-8");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Connection", "keep-alive");

        // 模拟AI模型的token流(实际开发中替换为LangChain4j的流式调用)
        String[] tokens = {"你", "好", "!", "我", "是", "AI", "助", "手", ",", "请", "提", "问"};
        
        for (String token : tokens) {
            // 严格遵循SSE格式:data: 内容\n\n(两个换行符分隔)
            response.getWriter().write("data: " + token + "\n\n");
            // 刷新缓冲区,确保客户端实时接收
            response.getWriter().flush();
            // 模拟token生成延迟,实现打字机效果
            Thread.sleep(300);
        }

        // 推送结束,关闭连接
        response.getWriter().write("data: [end]\n\n");
        response.getWriter().flush();
    }
}
    
前端代码(原生JS,无需额外依赖)

// 建立SSE连接
const sse = new EventSource('/sse/ai-stream');

// 接收服务端推送的消息
sse.onmessage = function(evt) {
    // 拿到推送的内容(AI的单个token)
    const token = evt.data;
    if (token === '[end]') {
        // 推送结束,关闭连接
        sse.close();
        return;
    }
    // 追加到页面,实现打字机效果
    document.getElementById('ai-result').innerText += token;
};

// 监听连接错误
sse.onerror = function(e) {
    console.error("SSE连接异常:", e);
    sse.close();
};

关键注意点:SSE的消息格式必须严格遵循“data: 内容\n\n”,两个换行符不能少,否则客户端无法识别;客户端无需手动重连,连接断开后会自动重试(可通过retry字段设置重连间隔)。

2. WebSocket实现

WebSocket需要引入SpringBoot的WebSocket依赖,实现简单的双向通信,这里以实时聊天为例。

第一步:引入依赖(pom.xml)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
    
第二步:配置WebSocket

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    // 注入ServerEndpointExporter,自动注册@ServerEndpoint注解的端点
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
    
第三步:实现WebSocket端点(聊天功能)

import org.springframework.stereotype.Component;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/websocket/chat")
@Component
public class ChatWebSocket {

    // 存储所有在线会话(线程安全)
    private static final CopyOnWriteArraySet&lt;Session&gt; sessions = new CopyOnWriteArraySet<>();

    // 客户端连接成功时触发
    @OnOpen
    public void onOpen(Session session) {
        sessions.add(session);
        sendMessage(session, "连接成功,可发送消息!");
    }

    // 接收客户端消息时触发
    @OnMessage
    public void onMessage(String message, Session session) {
        // 广播消息(发给所有在线客户端)
        for (Session s : sessions) {
            sendMessage(s, "用户消息:" + message);
        }
    }

    // 发送消息工具方法
    private void sendMessage(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
    
第四步:前端代码(原生JS)

// 建立WebSocket连接(ws是未加密,wss是加密,对应https)
const ws = new WebSocket("ws://localhost:8080/websocket/chat");

// 连接成功触发
ws.onopen = function() {
    console.log("WebSocket连接成功");
};

// 接收服务端消息
ws.onmessage = function(evt) {
    const message = evt.data;
    // 追加到聊天窗口
    document.getElementById('chat-content').innerText += message + "\n";
};

// 发送消息
function sendMessage() {
    const input = document.getElementById('chat-input');
    const message = input.value;
    if (message) {
        ws.send(message);
        input.value = "";
    }
}

// 关闭连接
function closeWebSocket() {
    ws.close();
}
    

关键注意点:WebSocket连接地址是ws://(非加密)或wss://(加密),不能用http/https;需手动实现心跳机制(避免连接被网关断开)和重连逻辑,生产环境建议结合Netty优化性能。

四、决策指南

  1. 需求是「单向流式输出」(只有服务端推给客户端)→ 选SSE;

  2. 需求是「双向实时交互」(客户端和服务端互相发消息)→ 选WebSocket;

  3. 需要「传输二进制数据」(音频、视频、文件)→ 选WebSocket;

  4. 追求「开发快、维护简单」,且不需要双向交互 → 选SSE;

  5. 需要「低延迟、高频交互」(如聊天、游戏)→ 选WebSocket;

  6. 老旧网络环境(不支持WebSocket协议升级)→ 选SSE。

总结

SSE和WebSocket没有“谁更好”,只有“谁更合适”:SSE是“轻骑兵”,主打一个简单高效,适合单向流式推送场景,不用复杂配置,上手即能用;WebSocket是“重型坦克”,功能强大,适合双向实时交互、二进制传输场景,能扛住高频交互的需求。对于Java后端开发者来说,掌握两者的差异和选型逻辑,能少走很多弯路——比如做AI流式输出时,用SSE比WebSocket节省50%的开发时间;做实时聊天时,WebSocket是唯一能满足需求的方案。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐