一句话总结:本文给出SSE vs WebSocket的标准答案,含对比表格、AI场景选型分析、完整代码,面试直接背。


一、面试原题

同程旅行/美团/字节等大厂AI前端面试高频题

“请简述一下SSE和WebSocket的区别,为什么在AI聊天场景中通常选择SSE?”

这道题考察3个能力:

  1. 基础协议理解(HTTP vs WebSocket)
  2. 场景分析能力(AI聊天的通信特征)
  3. 工程实践经验(是否落地过AI产品)

二、标准答案(面试直接背)

2.1 核心区别对比表

维度 SSE(Server-Sent Events) WebSocket
协议基础 HTTP/1.1(标准HTTP) WebSocket协议(RFC 6455)
通信模式 单向:服务端→客户端 全双工:双向通信
连接建立 普通HTTP请求,自动支持 需要握手升级(Upgrade: websocket)
自动重连 ✅ 原生支持(EventSource自动重连) ❌ 需手动实现心跳+重连
数据格式 文本(text/event-stream) 二进制或文本
浏览器兼容 现代浏览器都支持 现代浏览器都支持
穿透防火墙 ✅ 容易(HTTP端口) ⚠️ 部分企业防火墙会拦截
实现复杂度 低(前端几行代码) 中(需处理握手、心跳、重连)
适用场景 服务端推送、实时通知 双向实时通信、在线协作

2.2 一句话总结(面试开场)

“SSE是基于HTTP的单向推送技术,适合服务端向客户端持续推送数据;WebSocket是全双工协议,适合需要双向高频通信的场景。AI聊天选择SSE,是因为通信模式以服务端推送为主,且SSE实现更简单、兼容性更好。”

2.3 为什么AI聊天选SSE?(4个技术点)

1. 通信模式匹配

AI聊天的数据流是:用户发送一条消息 → LLM生成大量文本 → 服务端持续推送给前端

这是典型的"请求-持续响应"模式,不是双向高频通信。SSE的单向推送完全匹配。

2. 自动重连适合长连接

LLM生成可能持续30-60秒,期间网络可能抖动。SSE的EventSource原生支持自动重连:

const source = new EventSource('/api/chat');
// 连接断开时,浏览器自动重连,无需代码处理
source.onerror = (e) => {
  // 这里只是通知,浏览器会自动尝试重连
  console.log('连接异常,浏览器自动重连中...');
};

WebSocket断开需要手动写重连逻辑:

// WebSocket需要手动重连
let ws;
function connect() {
  ws = new WebSocket('wss://api.example.com');
  ws.onclose = () => {
    setTimeout(connect, 3000);  // 手动延迟重连
  };
}

3. HTTP兼容,穿透性更好

SSE走标准HTTP,企业内网、防火墙、代理服务器都能正常通过。WebSocket的ws:///wss://协议可能被企业防火墙拦截。

4. 实现简单,维护成本低

前端SSE代码:

const source = new EventSource('/api/chat');
source.onmessage = (e) => appendText(e.data);
// 5行代码搞定

前端WebSocket代码:

let ws = new WebSocket('wss://api.example.com');
ws.onopen = () => ws.send(JSON.stringify({type: 'init'}));
ws.onmessage = (e) => handleMessage(JSON.parse(e.data));
ws.onclose = () => reconnect();
ws.onerror = (e) => handleError(e);
// 需要处理握手、心跳、重连、错误,代码量3-5倍

2.4 什么场景必须用WebSocket?(展示深度)

面试中要展示"我知道SSE不是万能的":

场景 为什么用WebSocket
实时协作编辑(如腾讯文档) 需要前端主动推送给服务端(光标位置、编辑操作)
在线游戏 双向高频低延迟通信
股票行情推送+下单 既要接收行情,又要主动发送交易指令
IM即时通讯 需要发送消息+接收消息,双向对等

“AI聊天如果未来支持’实时语音输入’或’协同编辑AI回答’,可能需要WebSocket或SSE+HTTP混合架构。”


三、完整代码对比

3.1 SSE实现(前端30行)

class SSEChatClient {
  constructor(url) {
    this.url = url;
    this.source = null;
    this.onMessage = null;
    this.onError = null;
  }

  connect(message) {
    // POST请求开启SSE(需要fetch API配合)
    fetch(this.url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ message })
    }).then(response => {
      const reader = response.body.getReader();
      const decoder = new TextDecoder();

      const read = () => {
        reader.read().then(({ done, value }) => {
          if (done) {
            this.onMessage?.('[DONE]');
            return;
          }

          const text = decoder.decode(value);
          // 解析SSE格式:data: xxx\n\n
          const lines = text.split('\n\n');
          lines.forEach(line => {
            if (line.startsWith('data: ')) {
              const data = line.slice(6);
              if (data !== '[DONE]') {
                this.onMessage?.(data);
              }
            }
          });

          read();
        });
      };

      read();
    }).catch(err => this.onError?.(err));
  }

  disconnect() {
    this.source?.close();
  }
}

// 使用
const client = new SSEChatClient('/api/chat/stream');
client.onMessage = (token) => {
  document.getElementById('output').innerHTML += token;
};
client.connect('你好,请介绍自己');

3.2 WebSocket实现(前端80行)

class WebSocketChatClient {
  constructor(url) {
    this.url = url;
    this.ws = null;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 3000;
    this.onMessage = null;
    this.onError = null;
    this.heartbeatInterval = null;
  }

  connect() {
    this.ws = new WebSocket(this.url);

    this.ws.onopen = () => {
      console.log('WebSocket连接成功');
      this.reconnectAttempts = 0;
      this.startHeartbeat();
      // 发送初始化消息
      this.ws.send(JSON.stringify({ type: 'init', sessionId: generateId() }));
    };

    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      if (data.type === 'pong') return;  // 忽略心跳响应
      this.onMessage?.(data.content);
    };

    this.ws.onclose = () => {
      this.stopHeartbeat();
      this.attemptReconnect();
    };

    this.ws.onerror = (error) => {
      this.onError?.(error);
    };
  }

  startHeartbeat() {
    this.heartbeatInterval = setInterval(() => {
      if (this.ws?.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({ type: 'ping' }));
      }
    }, 30000);  // 30秒心跳
  }

  stopHeartbeat() {
    clearInterval(this.heartbeatInterval);
  }

  attemptReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      this.onError?.(new Error('重连次数超限'));
      return;
    }
    this.reconnectAttempts++;
    setTimeout(() => this.connect(), this.reconnectDelay);
  }

  send(message) {
    if (this.ws?.readyState === WebSocket.OPEN) {
      this.ws.send(JSON.stringify({ type: 'chat', content: message }));
    }
  }

  disconnect() {
    this.stopHeartbeat();
    this.ws?.close();
  }
}

// 使用
const client = new WebSocketChatClient('wss://api.example.com/chat');
client.onMessage = (token) => appendText(token);
client.connect();
client.send('你好');

四、后端Spring Boot实现

4.1 SSE后端(Spring WebFlux)

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
import java.time.Duration;

@RestController
@RequestMapping("/api/chat")
public class ChatController {

    @PostMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> chatStream(@RequestBody ChatRequest request) {
        return callLLM(request.getMessage())
            .delayElements(Duration.ofMillis(50))  // 模拟流式输出
            .map(token -> "data: " + token + "\n\n");  // SSE格式
    }

    private Flux<String> callLLM(String message) {
        // 调用LLM API,返回Flux<String>
        return webClient.post()
            .uri("https://api.openai.com/v1/chat/completions")
            .bodyValue(buildRequest(message))
            .retrieve()
            .bodyToFlux(String.class)
            .filter(line -> line.startsWith("data:"))
            .map(line -> extractToken(line));
    }
}

4.2 WebSocket后端(Spring Boot)

import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

public class ChatWebSocketHandler extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) {
        String payload = message.getPayload();
        // 解析消息类型
        ChatMessage chatMessage = parseMessage(payload);

        switch (chatMessage.getType()) {
            case "ping":
                session.sendMessage(new TextMessage("{\"type\":\"pong\"}"));
                break;
            case "chat":
                handleChat(session, chatMessage.getContent());
                break;
        }
    }

    private void handleChat(WebSocketSession session, String content) {
        // 调用LLM,流式推送给前端
        callLLM(content).subscribe(token -> {
            try {
                session.sendMessage(new TextMessage(
                    "{\"type\":\"token\",\"content\":\"" + token + "\"}"
                ));
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

五、面试答题模板

5.1 30秒快速回答(一面)

“SSE是基于HTTP的单向推送,适合服务端持续推送数据;WebSocket是全双工协议,适合双向高频通信。AI聊天选SSE是因为:1)通信模式以服务端推送为主;2)SSE自动重连适合长连接;3)HTTP兼容穿透性好;4)实现简单维护成本低。”

5.2 2分钟详细回答(二面)

"首先,SSE和WebSocket的核心区别在于通信模式和协议基础。SSE基于HTTP,是单向推送,服务端可以持续向客户端发送数据;WebSocket是独立协议,支持全双工双向通信。

AI聊天场景选择SSE,我有4个考量:

  1. 通信模式匹配:AI聊天是’用户提问→LLM生成→持续推送’,单向推送足够
  2. 自动重连:LLM生成可能持续几十秒,SSE原生支持断线重连
  3. 兼容性:SSE走HTTP,企业防火墙、代理都能通过
  4. 实现成本:前端EventSource几行代码,WebSocket需要处理握手、心跳、重连

但我也清楚SSE的局限,如果未来需要’协同编辑AI回答’或’实时语音输入’,可能需要WebSocket或混合架构。"

5.3 5分钟深度回答(三面+项目复盘)

在上面的基础上,加入:

  • “我在XX项目中实际对比过两种方案…”
  • “SSE的自动重连在弱网环境下表现…”
  • “我们最终选择了SSE,但做了XX优化…”
  • “如果让我重新设计,我会考虑XX…”

六、扩展问题预判

追问 答案要点
“SSE的自动重连机制是怎样的?” EventSource内部实现,指数退避,可配置retry时间
“SSE怎么支持POST请求?” 原生EventSource只支持GET,需要fetch+ReadableStream或库(如eventsource)
“SSE和WebSocket性能对比?” SSE连接开销小(HTTP),WebSocket帧头小(2-14字节),大流量时WebSocket更优
“如何实现SSE的断线续传?” Last-Event-ID头部,服务端记录offset,重连时从断点续传
“SSE在移动端表现如何?” iOS Safari支持好,Android Chrome支持好,但App WebView可能需要测试

七、总结

考察点 你的回答
基础概念 SSE=HTTP单向推送,WebSocket=全双工协议
选型依据 AI聊天=单向推送为主+长连接+简单实现=选SSE
深度思考 知道WebSocket适用场景,不盲目说SSE万能
工程实践 能写出SSE和WebSocket的完整代码
优化意识 提到断线续传、弱网处理、移动端适配

👤 关于作者

JavaAgent架构师 — 十年Java分布式架构老兵,专注AI Agent企业级落地。

主导过数字员工、SOP智能引擎等项目,开发过RPC框架、消息中间件、ORM框架。

📚 正在输出三个专栏

  • 《前端AI工程化》— SSE/流式渲染/Function Calling/企业级架构
  • 《Java体系也能玩转AI》— Spring AI/Agent框架/MCP/工作流
  • 《从0构建Agent系统》— 数字员工/SOP模型库/生产部署

让Java开发者不转Python,让前端工程师掌握AI核心竞争力。

📮 关注专栏|🔔 点赞收藏|💬 评论区见


🎯 这是《前端AI工程化》专栏面试题系列第1篇。

专栏覆盖:SSE/流式渲染/打字机效果/Function Calling/RAG优化/企业级架构

Logo

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

更多推荐