HTTP、WebSocket、SSE 到底怎么选?一篇讲透实战差异

6 阅读12分钟

前端必知:HTTP、WebSocket、SSE 到底怎么选?一篇讲透实战差异

在前端开发里,数据通信是绕不开的核心,而我们最常用到的三种通信方式——HTTPWebSocketSSE,看似都是数据传输,适用场景、底层逻辑却天差地别。

很多同学日常只知道用接口请求数据,遇到实时通信场景就盲目选WebSocket,反而导致资源浪费、兼容性问题。今天就结合实战,把这三者的原理、优缺点、核心差异讲清楚,重点拆解SSE前端三种实现方式(EventSource、fetch、axios)的选型逻辑,帮你在开发中精准选型。

一、先理清核心:三者的本质区别

1. HTTP:单向请求,被动响应(含长轮询、短轮询)

HTTP是我们最熟悉的短连接、单向通信协议,核心遵循请求-响应模式:客户端主动发起请求,服务端收到后返回数据,连接随即关闭。由于HTTP本身不支持服务端主动推送,为了实现“伪实时”通信,衍生出了长轮询和短轮询两种方案。

(1)短轮询

最简单的“伪实时”实现方式,客户端按照固定时间间隔(如1秒、3秒),持续向服务端发送HTTP请求,查询是否有新数据,服务端无论有无数据,都会立即返回响应,连接随即关闭。

  • 特点:实现简单、无需改造服务端,逻辑直观;

  • 缺陷:高频请求会浪费大量带宽和服务端资源,实时性取决于轮询间隔(间隔越长,实时性越差;间隔越短,资源消耗越大);

  • 适用:对实时性要求极低的场景(如后台数据定时刷新、非核心通知查询)。

(2)长轮询

对短轮询的优化,客户端发起HTTP请求后,服务端不会立即返回响应,而是保持连接(通常几秒到几十秒),直到有新数据产生、连接超时,才会返回响应;客户端收到响应后,立即发起下一次请求,循环往复。

  • 特点:相比短轮询,大幅减少请求次数,降低资源消耗,实时性优于短轮询;

  • 缺陷:服务端需要维护大量挂起的连接,对服务端压力较大;连接超时会导致无效请求,仍有一定资源浪费;

  • 适用:对实时性有一定要求,但又不想引入WebSocket/SSE的场景(如简单的消息通知、订单状态查询)。

长轮询 基础实现
// 客户端长轮询实现
function longPoll() {
  fetch('/api/long-poll', {
    method: 'GET',
    timeout: 30000 // 30秒超时,与服务端保持一致
  })
  .then(res => {
    if (res.ok) {
      return res.json()
    }
    throw new Error(`HTTP错误,状态码:${res.status}`)
  })
  .then(data => {
    // 处理服务端返回的新数据
    console.log('长轮询收到数据:', data)
    // 立即发起下一次长轮询
    longPoll()
  })
  .catch(err => {
    console.error('长轮询异常:', err)
    // 异常后延迟1秒重试,避免频繁请求
    setTimeout(longPoll, 1000)
  })
}
// 启动长轮询
longPoll()
(3)HTTP 常见错误码(前端必记)

HTTP状态码分为5大类,核心常用错误码及含义如下,开发中可快速定位问题:

  • 4xx(客户端错误) :请求有问题,服务端无法处理

  • 400 Bad Request:请求参数错误、格式不正确(最常见,如JSON格式错误、参数缺失);

  • 401 Unauthorized:未授权,需要登录验证(如未携带token、token过期);

  • 403 Forbidden:权限不足,已登录但无操作权限(如普通用户访问管理员接口);

  • 404 Not Found:请求的接口/资源不存在(路径错误、接口未部署);

  • 405 Method Not Allowed:请求方法错误(如用GET请求POST接口);

  • 5xx(服务端错误) :服务端自身异常

  • 500 Internal Server Error:服务端通用错误(如代码报错、服务器宕机);

  • 502 Bad Gateway:网关错误(如反向代理配置异常、后端服务未启动);

  • 503 Service Unavailable:服务暂时不可用(如服务器过载、维护中);

  • 504 Gateway Timeout:网关超时(后端服务响应太慢,超过网关等待时间)。

  • HTTP核心缺陷:无论长轮询还是短轮询,本质都是“客户端主动询问”,无法实现服务端主动推送,且存在资源浪费或服务端压力大的问题;

  • 常规适用:页面初始化加载数据、表单提交、接口查询等非实时场景。

2. WebSocket:全双工,长连接实时通信

WebSocket是长连接、全双工的通信协议,基于HTTP握手建立连接(客户端发起HTTP请求,携带Upgrade: websocket头部,服务端响应后切换为WebSocket协议),一旦建立连接,客户端和服务端可以双向自由发送数据,连接会持续保持,直到主动断开。

  • 特点:双向通信、低延迟、开销小,真正实现服务端与客户端实时交互;

  • 优势:实时性极强,支持文本、二进制数据(如图片、文件),适合高频交互场景;

  • 缺陷:相对较重,服务端需要单独维护长连接,对服务端性能有一定要求;兼容性略差(不支持IE9及以下),简单场景使用会增加开发成本。

补充:WebSocket 与 HTTPS 兼容及心跳机制(实战必看)

在实际开发中,WebSocket的使用需重点关注HTTPS兼容心跳机制,否则会出现连接异常、通信中断等问题,尤其是生产环境中不可忽视。

(1)WebSocket 与 HTTPS 兼容情况

WebSocket协议分为两种,分别对应HTTP和HTTPS,确保传输安全与兼容性:

  • ws:// :对应HTTP协议,明文传输,适用于本地开发、测试环境,生产环境不推荐(存在安全风险);
  • wss:// :对应HTTPS协议,加密传输,适用于生产环境,是目前主流用法;
  • 兼容性:wss:// 兼容所有支持WebSocket的现代浏览器(同WebSocket基础兼容性,IE10+、Chrome、Firefox、Edge等均支持),无额外兼容性成本;
  • 注意点:生产环境中,若前端页面使用HTTPS协议,WebSocket必须使用wss://(浏览器禁止HTTPS页面加载ws://资源,会报混合内容错误)。
(2)心跳机制:避免“协议层通、应用层断”

核心问题:WebSocket建立长连接后,可能出现“协议层显示连接正常,但应用层无法通信”的情况(如网络波动、服务器静默断开、防火墙超时拦截),此时客户端和服务端均无法感知,导致数据传输失败。

心跳机制原理:客户端和服务端约定固定时间间隔,互相发送“心跳包”(无实际业务意义的空消息或固定标识),若某一方在规定时间内未收到心跳响应,则判定连接异常,主动断开并重新连接,确保通信通畅。

WebSocket 基础实现(含HTTPS兼容+心跳机制)
// 客户端代码(兼容HTTPS,添加心跳机制)
// 1. 兼容HTTPS:根据页面协议自动选择ws/wss(生产环境推荐直接写wss://)
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//xxx.xxx.xxx:8080/ws`);

// 2. 心跳配置(可根据业务调整,常规10~30秒)
let heartbeatTimer = null; // 心跳发送定时器
let heartbeatTimeoutTimer = null; // 心跳超时定时器
const HEARTBEAT_INTERVAL = 15000; // 心跳发送间隔(15秒)
const HEARTBEAT_TIMEOUT = 5000; // 心跳超时时间(5秒)

// 连接成功:启动心跳
ws.onopen = () => {
  console.log('WebSocket连接成功');
  ws.send('客户端已连接');
  startHeartbeat(); // 启动心跳
};

// 接收服务端消息:重置心跳超时定时器
ws.onmessage = (e) => {
  console.log('收到消息:', e.data);
  // 若收到服务端心跳响应,重置超时定时器
  resetHeartbeatTimeout();
};

// 发送心跳包
function sendHeartbeat() {
  if (ws.readyState === WebSocket.OPEN) {
    ws.send('heartbeat'); // 心跳包(可自定义标识,服务端对应识别)
  }
}

// 启动心跳
function startHeartbeat() {
  // 清除原有定时器,避免重复启动
  clearInterval(heartbeatTimer);
  clearTimeout(heartbeatTimeoutTimer);
  
  // 每隔15秒发送一次心跳
  heartbeatTimer = setInterval(() => {
    sendHeartbeat();
    // 发送心跳后,启动超时定时器,5秒内未收到响应则判定异常
    heartbeatTimeoutTimer = setTimeout(() => {
      console.error('WebSocket心跳超时,连接异常');
      ws.close(); // 主动关闭连接
    }, HEARTBEAT_TIMEOUT);
  }, HEARTBEAT_INTERVAL);
}

// 重置心跳超时定时器
function resetHeartbeatTimeout() {
  clearTimeout(heartbeatTimeoutTimer);
}

// 连接关闭:清除定时器,重新连接(可选,根据业务需求)
ws.onclose = () => {
  console.log('WebSocket连接关闭');
  clearInterval(heartbeatTimer);
  clearTimeout(heartbeatTimeoutTimer);
  // 可选:自动重连(避免手动刷新)
  setTimeout(() => {
    window.location.reload(); // 简单重连方式,可根据业务优化
  }, 3000);
};

// 连接异常:清除定时器
ws.onerror = (err) => {
  console.error('WebSocket连接异常:', err);
  clearInterval(heartbeatTimer);
  clearTimeout(heartbeatTimeoutTimer);
};

说明:服务端需对应实现心跳包识别逻辑(收到“heartbeat”消息后,返回响应),否则客户端会误判连接异常;心跳间隔和超时时间需与服务端保持一致,避免出现误判。

3. SSE:服务端单向推送,轻量长连接(重点:前端三种实现方式选型)

SSE(Server-Sent Events,服务端推送事件)是轻量级、单向长连接协议,基于HTTP协议实现,无需额外握手流程,客户端发起一次HTTP请求后,服务端保持连接,持续向客户端推送文本数据,客户端无法主动向服务端发送消息。

前端实现SSE核心有三种方式:EventSource(原生专属)fetch(原生通用)axios(封装便捷) ,三者适配不同业务场景,选型核心看开发成本、自定义需求、项目技术栈,以下详细拆解每种方式的实现、优缺点及选型逻辑。

(1)三种实现方式基础实战(附代码)
① EventSource:SSE原生专属实现(最简洁)
// 客户端代码(EventSource实现SSE)
if (window.EventSource) { // 兼容判断(不支持IE)
  // 建立SSE连接,默认GET请求,可携带query参数
  const source = new EventSource('/api/sse/message?userId=123');
  
  // 监听普通消息(服务端无指定event字段时触发)
  source.onmessage = (e) => {
    console.log('收到SSE消息:', JSON.parse(e.data));
  };
  
  // 监听指定事件(服务端返回event: customEvent时触发)
  source.addEventListener('customEvent', (e) => {
    console.log('收到自定义事件消息:', JSON.parse(e.data));
  });
  
  // 监听连接打开
  source.onopen = () => {
    console.log('SSE连接成功(EventSource)');
  };
  
  // 监听连接错误(自动重连失败、网络中断等)
  source.onerror = (err) => {
    console.error('SSE连接异常(EventSource):', err);
    // 手动关闭连接(可选,避免无效重连)
    if (source.readyState === 2) {
      source.close();
    }
  };
  
  // 手动关闭连接(如页面销毁时)
  // window.addEventListener('beforeunload', () => source.close());
} else {
  console.log('浏览器不支持EventSource,建议改用fetch/axios实现SSE');
}
② fetch:原生通用实现(高自定义)
// 客户端代码(fetch实现SSE)
async function fetchSSE() {
  try {
    const response = await fetch('/api/sse/message', {
      method: 'GET',
      headers: {
        'Content-Type': 'text/event-stream', // 必传,标识SSE请求
        'Authorization': 'Bearer ' + localStorage.getItem('token') // 可自定义请求头(如鉴权)
      },
      credentials: 'include', // 跨域时携带cookie(可选)
      cache: 'no-cache' // 禁止缓存,避免重复请求
    });
    
    if (!response.ok) {
      throw new Error(`SSE请求失败,状态码:${response.status}`);
    }
    
    // 解析响应流(核心:逐行读取服务端推送数据)
    const reader = response.body.getReader();
    const decoder = new TextDecoder('utf-8');
    let result;
    
    while (!(result = await reader.read()).done) {
      const chunk = decoder.decode(result.value);
      // 分割多行消息(服务端推送可能批量返回,需拆分)
      const messages = chunk.split('\n\n').filter(msg => msg.trim());
      messages.forEach(msg => {
        // 解析SSE格式(data: 消息体,event: 事件名,id: 消息ID)
        const lines = msg.split('\n');
        let data = '';
        let event = 'message';
        lines.forEach(line => {
          if (line.startsWith('data:')) {
            data += line.slice(5).trim(); // 拼接多行data(服务端可能分多行返回)
          } else if (line.startsWith('event:')) {
            event = line.slice(6).trim();
          }
        });
        if (data) {
          // 触发对应事件(模拟EventSource的事件机制)
          const customEvent = new CustomEvent(event, { detail: JSON.parse(data) });
          window.dispatchEvent(customEvent);
        }
      });
    }
  } catch (err) {
    console.error('SSE连接异常(fetch):', err);
    // 异常重连(自定义重连逻辑,如延迟1秒重试)
    setTimeout(fetchSSE, 1000);
  }
}

// 启动SSE连接
fetchSSE();

// 监听SSE消息(普通消息)
window.addEventListener('message', (e) => {
  console.log('收到SSE消息(fetch):', e.detail);
});

// 监听自定义事件消息
window.addEventListener('customEvent', (e) => {
  console.log('收到自定义事件消息(fetch):', e.detail);
});
③ axios:封装请求实现(适配项目技术栈)
// 客户端代码(axios实现SSE)
import axios from 'axios';

function axiosSSE() {
  // 注意:axios默认不支持流解析,需配置responseType: 'stream'
  axios.get('/api/sse/message', {
    responseType: 'stream', // 必传,指定响应为流
    headers: {
      'Content-Type': 'text/event-stream',
      'Authorization': 'Bearer ' + localStorage.getItem('token')
    },
    timeout: 0, // 禁用超时(SSE是长连接,超时会中断)
    withCredentials: true // 跨域携带cookie(可选)
  }).then(response => {
    console.log('SSE连接成功(axios)');
    // 解析响应流(与fetch逻辑一致,逐行读取)
    const decoder = new TextDecoder('utf-8');
    let data = '';
    
    // 监听流数据(axios的stream通过on('data')触发)
    response.data.on('data', (chunk) => {
      data += decoder.decode(chunk);
      // 分割消息(遇到\n\n表示一条完整消息)
      const messageEndIndex = data.indexOf('\n\n');
      while (messageEndIndex !== -1) {
        const message = data.slice(0, messageEndIndex).trim();
        data = data.slice(messageEndIndex + 2); // 截取剩余未解析内容
        
        // 解析SSE格式
        const lines = message.split('\n');
        let msgData = '';
        let event = 'message';
        lines.forEach(line => {
          if (line.startsWith('data:')) {
            msgData += line.slice(5).trim();
          } else if (line.startsWith('event:')) {
            event = line.slice(6).trim();
          }
        });
        if (msgData) {
          // 触发事件
          const customEvent = new CustomEvent(event, { detail: JSON.parse(msgData) });
          window.dispatchEvent(customEvent);
        }
      }
    });
    
    // 监听流结束(服务端主动关闭连接)
    response.data.on('end', () => {
      console.log('SSE连接结束(axios)');
      // 重连逻辑(可选)
      setTimeout(axiosSSE, 1000);
    });
    
    // 监听流错误
    response.data.on('error', (err) => {
      console.error('SSE连接异常(axios):', err);
      setTimeout(axiosSSE, 1000);
    });
  }).catch(err => {
    console.error('SSE请求失败(axios):', err);
    setTimeout(axiosSSE, 1000);
  });
}

// 启动SSE连接
axiosSSE();

// 监听消息
window.addEventListener('message', (e) => {
  console.log('收到SSE消息(axios):', e.detail);
});
(2)SSE前端三种实现方式选型说明(核心重点)

选型核心:优先匹配项目技术栈、自定义需求、开发成本,无需盲目追求“高自定义”,简单场景用原生EventSource,复杂场景用fetch/axios,以下是详细选型表格及逻辑拆解。

实现方式核心优势核心缺陷适配场景选型建议
EventSource1. 原生SSE专属API,无需手动解析流、处理重连;2. 代码极简,开发成本最低;3. 自动处理消息拆分、事件监听,原生适配SSE格式。1. 仅支持GET请求,无法自定义请求头(如鉴权token);2. 兼容性一般(不支持IE,现代浏览器均支持);3. 自定义能力弱,无法控制重连间隔、超时逻辑。1. 简单SSE场景(无需鉴权、无复杂自定义);2. 小型项目、快速迭代需求;3. 仅需基础推送,无需控制重连、请求参数。无复杂需求首选,开发效率最高。
fetch1. 原生API,无需引入第三方依赖;2. 支持GET/POST请求,可自定义请求头、请求参数;3. 流解析可控,可自定义消息处理、重连逻辑;4. 适配现代浏览器,兼容性优于EventSource(可做降级处理)。1. 需手动解析SSE流、拆分消息,代码量略多;2. 需手动实现重连、超时控制,开发成本高于EventSource;3. 低版本浏览器(如IE)需兼容处理。1. 需要自定义请求头(如鉴权)、请求方法;2. 需要控制重连间隔、超时逻辑;3. 无axios依赖,追求原生实现的项目。无axios,需自定义需求选它。
axios1. 适配已有axios项目,统一请求拦截、响应拦截;2. 支持自定义请求头、请求参数,鉴权逻辑可复用;3. 流解析逻辑可封装复用,适配复杂项目;4. 错误处理更完善,可统一管理异常。1. 需引入axios依赖(增加项目体积);2. 需手动配置responseType: 'stream',手动解析流;3. 禁用超时(timeout: 0),需手动处理连接异常;4. 在纯浏览器环境中,Axios 对 responseType: 'stream' 的支持依赖于底层的 XHR 实现,兼容性不如 fetch 或 EventSource 直接。1. 项目已集成axios(主流中大型项目);2. 需要统一请求拦截、鉴权逻辑;3. 复杂SSE场景,需复用项目请求配置。项目用axios,优先选它,统一请求管理。
(3)补充注意事项(实战避坑)
  • 无论哪种方式,SSE请求必须设置请求头Content-Type: text/event-stream,否则服务端无法识别为SSE请求,会直接返回完整响应而非流数据;
  • EventSource默认自带重连机制(断开后自动重试),fetch和axios需手动实现重连逻辑(避免断连后无法接收消息);
  • 跨域场景:三种方式均需服务端配置CORS(允许对应请求头、请求方法),EventSource跨域时无法携带cookie,需用fetch/axios并配置credentials/withCredentials;
  • 数据格式:SSE仅支持文本数据(JSON、字符串等),三种方式均需解析数据(如JSON.parse),无法直接传输二进制数据;
  • 兼容性降级:IE浏览器不支持EventSource和fetch,可通过axios+polyfill适配,现代浏览器优先用EventSource/fetch;
  • 主动中断SSE连接(AbortController):SSE作为长连接,在组件卸载、用户手动关闭推送、页面跳转、搜索防抖等场景,必须主动中断连接,否则会导致内存泄漏、服务端资源浪费。AbortController是浏览器原生标准API,可完美适配fetch/axios实现的SSE,使用时需创建控制器实例,在请求配置中传入signal参数,需中断时调用abort()方法,并在catch中捕获AbortError避免误判网络异常;EventSource不支持AbortController,需通过调用close()方法手动关闭连接。

二、直观对比:三张表看懂差异

表1:HTTP、WebSocket、SSE 核心差异

特性HTTP(含长/短轮询)WebSocketSSE
连接方式短连接(常规)、伪长连接(长轮询)长连接,持续保持长连接,持续保持
通信方向客户端→服务端(单向),伪实时推送双向通信服务端→客户端(单向)
数据格式任意(JSON/表单/二进制)文本/二进制仅文本
实时性差(短轮询)、中(长轮询)极高
资源开销高(短轮询)、中(长轮询)
兼容性全兼容现代浏览器+IE10+(wss://兼容同WebSocket)现代浏览器,不支持IE(可通过fetch/axios降级)
开发成本低(常规)、中(长轮询)中(需额外处理心跳、HTTPS兼容)低-中(EventSource最低,fetch/axios略高)

表2:SSE三种前端实现方式核心差异(补充)

特性EventSourcefetchaxios
请求方法支持仅GETGET/POST等所有方法GET/POST等所有方法
自定义请求头不支持支持支持
自动重连支持(原生)需手动实现需手动实现
依赖情况无依赖(原生)无依赖(原生)需依赖axios
代码复杂度极低中等中等

三、总结:选型核心原则

1.  普通非实时业务:无脑用HTTP(常规请求),满足绝大多数场景,开发成本最低;

  1. 轻量单向实时推送(SSE) : - 无复杂需求、无需鉴权:优先EventSource(开发最快、最简洁); - 无axios依赖、需自定义请求:选fetch(原生通用、高灵活); - 项目已用axios、需统一请求管理:选axios(适配技术栈、复用拦截逻辑);

3.  高频双向实时交互:选WebSocket,极致实时性,适配复杂交互场景(如聊天、在线协作);注意生产环境用wss://兼容HTTPS,添加心跳机制避免通信异常;

4.  对实时性有要求、但不想引入新协议:用HTTP长轮询(折中方案,兼顾开发成本和实时性)。

不用盲目追求“实时”就用WebSocket,很多单向推送的场景,SSE更轻量;也不用盲目追求“高自定义”,简单场景EventSource足以满足需求。根据业务场景、项目技术栈精准选型,才能写出高效、简洁的代码。