跳到主要内容

WebSocket 教程

WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它让浏览器和服务器之间能够建立持久化的双向通信通道。本教程将系统讲解 WebSocket 协议原理、浏览器客户端 API、以及多种语言的服务端实现。

什么是 WebSocket

WebSocket 协议于 2011 年由 IETF 标准化(RFC 6455),它解决了传统 HTTP 请求-响应模式在实时通信场景下的局限性。

HTTP 的局限性

在 WebSocket 出现之前,Web 应用实现实时通信主要依靠以下方式:

短轮询(Short Polling):客户端定时向服务器发送请求,询问是否有新数据。这种方式浪费带宽和服务器资源,因为大多数请求可能都没有新数据。

长轮询(Long Polling):客户端发送请求后,服务器保持连接打开,直到有数据可返回或超时。虽然比短轮询高效,但每次返回数据后都需要重新建立连接。

这两种方式都存在共同的问题:通信只能由客户端发起,服务器无法主动推送数据。

WebSocket 的优势

WebSocket 提供了真正的全双工通信能力:

  • 全双工通信:客户端和服务器可以同时发送消息,不需要等待对方响应
  • 持久连接:一次握手建立连接后,连接保持打开状态,无需重复建立
  • 低延迟:消除了 HTTP 请求头的开销,消息实时传递
  • 轻量级:数据帧头部只有 2-10 字节,远小于 HTTP 请求头

WebSocket 与 HTTP 的关系

WebSocket 虽然名字里带有 "Web",但它与 HTTP 是两种不同的协议。不过,WebSocket 的设计巧妙地利用了 HTTP 来完成初始握手。

握手过程

WebSocket 连接的建立始于一个 HTTP 请求,这个请求包含特殊的头部字段,表明客户端希望升级到 WebSocket 协议:

GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

服务器如果支持 WebSocket,会返回 101 Switching Protocols 响应:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

握手完成后,连接从 HTTP 协议切换到 WebSocket 协议,之后的通信都使用 WebSocket 的数据帧格式。

协议标识

WebSocket 使用与 HTTP 不同的 URL 方案:

  • ws://:非加密连接,默认端口 80
  • wss://:加密连接(类似 HTTPS),默认端口 443
const socket = new WebSocket('ws://example.com/chat');
const secureSocket = new WebSocket('wss://example.com/chat');

适用场景

WebSocket 特别适合需要实时、双向通信的应用场景:

即时通讯

聊天应用是 WebSocket 最典型的应用场景。用户发送的消息需要实时传递给对方,同时还要接收其他用户的消息。

const socket = new WebSocket('wss://chat.example.com');

socket.onmessage = (event) => {
const message = JSON.parse(event.data);
displayMessage(message);
};

function sendMessage(text) {
socket.send(JSON.stringify({
type: 'message',
content: text,
timestamp: Date.now()
}));
}

实时数据推送

股票行情、体育比分、实时监控等场景需要服务器持续推送最新数据。

const stockSocket = new WebSocket('wss://stocks.example.com/quotes');

stockSocket.onmessage = (event) => {
const quotes = JSON.parse(event.data);
updateStockDisplay(quotes);
};

协作编辑

在线文档编辑、白板协作等应用需要实时同步多个用户的操作。

const docSocket = new WebSocket('wss://docs.example.com/collab');

docSocket.onmessage = (event) => {
const change = JSON.parse(event.data);
applyRemoteChange(change);
};

function broadcastChange(change) {
docSocket.send(JSON.stringify({
type: 'edit',
...change
}));
}

在线游戏

多人在线游戏需要低延迟的双向通信来同步玩家状态和游戏事件。

物联网(IoT)

设备状态监控、远程控制等物联网应用需要实时接收设备数据并发送控制指令。

不适用场景

WebSocket 并非万能的解决方案,以下场景可能更适合其他技术:

RESTful API:传统的 CRUD 操作仍然适合使用 HTTP REST API,因为这类请求通常是独立的、无状态的。

文件上传/下载:大文件传输更适合使用 HTTP,因为 HTTP 有更好的缓存机制和断点续传支持。

简单的数据获取:如果只需要偶尔获取数据,使用 HTTP 请求更简单直接。

需要 HTTP 缓存的场景:WebSocket 连接是持久的,无法利用 HTTP 缓存机制。

WebSocket 与 HTTP/2 的关系

随着 HTTP/2 的普及,开发者经常问:WebSocket 是否会被 HTTP/2 取代?答案是否定的,两者可以共存并各有优势。

HTTP/2 带来的改进

HTTP/2 引入了多项重要改进:多路复用解决了队头阻塞问题,头部压缩减少了传输开销,服务器推送允许服务器主动发送资源。这些改进让 HTTP 在很多场景下效率大幅提升。

为什么 WebSocket 仍然重要

HTTP/2 的服务器推送只能将资源推送到浏览器缓存,应用程序无法直接接收这些推送数据。换句话说,HTTP/2 没有为应用层提供接收服务器推送的 API。这正是 WebSocket 的价值所在——它提供了真正的应用层双向通信能力。

技术选型建议

选择 WebSocket 的场景

  • 高吞吐量的双向数据交换(如多人在线游戏)
  • 上下游数据流量接近对称的场景
  • 需要最小化协议开销的场景

选择 HTTP/2 + SSE 的场景

  • 主要是服务器向客户端推送数据
  • 希望利用现有 HTTP 基础设施
  • 需要与代理、防火墙更好兼容
// HTTP/2 环境下 WebSocket 仍然独立工作
// WebSocket 不会自动享受 HTTP/2 的多路复用优势
const socket = new WebSocket('wss://example.com/ws');

// SSE 在 HTTP/2 下可以享受多路复用
const eventSource = new EventSource('/api/events');

WebSocket over HTTP/2 (RFC 8441)

传统上,WebSocket 使用 HTTP/1.1 的 Upgrade 机制建立连接。这种方式存在一个问题:WebSocket 连接与 HTTP/2 连接是独立的,无法共享同一个 TCP 连接。2018 年发布的 RFC 8441 标准解决了这个问题,允许 WebSocket 在 HTTP/2 连接上运行。

工作原理:RFC 8441 扩展了 HTTP/2 的 CONNECT 方法,引入了新的伪头部字段 :protocol。客户端通过发送带有 :protocol: websocket 的 CONNECT 请求来建立 WebSocket 连接,而不是使用传统的 GET 请求加 Upgrade 头部。

// 传统 WebSocket 握手 (HTTP/1.1)
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

// WebSocket over HTTP/2 握手
:method = CONNECT
:protocol = websocket
:scheme = https
:path = /chat
:authority = server.example.com
sec-websocket-version = 13

主要优势

  1. 连接复用:WebSocket 可以与普通 HTTP/2 请求共享同一个 TCP 连接,减少连接开销。

  2. 多路复用:多个 WebSocket 连接可以在同一 HTTP/2 连接上并行运行,每个 WebSocket 使用独立的流(stream)。

  3. 更高效的握手:不需要完整的 HTTP/1.1 Upgrade 握手过程,减少了建立连接的延迟。

浏览器支持:Chrome 从版本 63 开始支持 WebSocket over HTTP/2。当页面通过 HTTP/2 加载时,浏览器会自动尝试使用 HTTP/2 建立 WebSocket 连接(如果服务器支持)。开发者无需修改代码,浏览器会自动处理协议协商。

客户端                         服务器
| |
|-------- SETTINGS ----------->| (SETTINGS_ENABLE_CONNECT_PROTOCOL = 1)
| |
|------- CONNECT ------------>| (:protocol = websocket)
| (WebSocket headers) |
| |
|<------ 200 OK --------------| (WebSocket established)
| |
|<-----> WebSocket Data <----->| (在 HTTP/2 流上传输)

服务器配置要点:要支持 WebSocket over HTTP/2,服务器需要:

  • 发送 SETTINGS_ENABLE_CONNECT_PROTOCOL 设置参数(值为 1)
  • 支持扩展的 CONNECT 方法,能处理 :protocol 伪头部

Nginx 从 1.25.0 版本开始支持此功能,需要在配置中启用:

server {
listen 443 ssl http2;

location /ws {
proxy_pass http://backend;
proxy_http_version 2.0;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}

浏览器连接限制

理解浏览器的连接限制对于构建大规模 WebSocket 应用至关重要。

HTTP/1.1 的连接限制

在 HTTP/1.1 环境下,浏览器对每个域名有并发连接数限制。这是为了防止浏览器过度占用服务器资源。根据 HTTP/1.1 规范(RFC 2616),建议客户端对每个服务器最多保持 2 个持久连接,但现代浏览器通常放宽到 6 个左右。

浏览器每域名最大连接数
Chrome6
Firefox6
Safari6
Edge6

WebSocket 的连接限制

好消息是:WebSocket 连接不受 HTTP/1.1 连接限制的约束。这意味着即使页面已经建立了 6 个 HTTP 连接,仍然可以创建新的 WebSocket 连接。

但是,浏览器对 WebSocket 连接仍有其他限制:

  1. 内存限制:每个连接都会占用内存,过多连接会影响页面性能
  2. 系统资源限制:操作系统对进程打开的文件描述符有限制
  3. 浏览器实现限制:部分浏览器可能对 WebSocket 连接总数有上限

实践建议

// 不推荐:为每个功能创建单独的 WebSocket 连接
const chatSocket = new WebSocket('wss://example.com/chat');
const notificationSocket = new WebSocket('wss://example.com/notifications');
const presenceSocket = new WebSocket('wss://example.com/presence');

// 推荐:使用单一连接多路复用
const socket = new WebSocket('wss://example.com/ws');

// 通过消息类型区分不同业务
socket.send(JSON.stringify({ channel: 'chat', data: message }));
socket.send(JSON.stringify({ channel: 'notification', subscribe: true }));
socket.send(JSON.stringify({ channel: 'presence', status: 'online' }));

// 统一处理不同类型的消息
socket.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.channel) {
case 'chat':
handleChatMessage(msg.data);
break;
case 'notification':
showNotification(msg.data);
break;
case 'presence':
updatePresence(msg.data);
break;
}
};

移动端特殊考虑

在移动设备上使用 WebSocket 需要特别注意一些问题。

网络不稳定

移动网络环境不稳定,连接经常断开。需要实现健壮的重连机制:

class MobileWebSocket {
constructor(url, options = {}) {
this.url = url;
this.options = {
maxReconnectAttempts: 10,
baseDelay: 1000,
maxDelay: 30000,
...options
};
this.reconnectAttempts = 0;
this.connect();
}

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

this.socket.onopen = () => {
this.reconnectAttempts = 0;
console.log('连接成功');
};

this.socket.onclose = (event) => {
// 非 1000 关闭码通常意味着需要重连
if (event.code !== 1000 && this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.scheduleReconnect();
}
};
}

scheduleReconnect() {
// 指数退避 + 随机抖动,避免重连风暴
const delay = Math.min(
this.options.baseDelay * Math.pow(2, this.reconnectAttempts),
this.options.maxDelay
) + Math.random() * 1000;

this.reconnectAttempts++;
console.log(`${Math.round(delay / 1000)} 秒后第 ${this.reconnectAttempts} 次重连`);

setTimeout(() => this.connect(), delay);
}
}

应用切换到后台

移动应用切换到后台时,系统可能会暂停或断开 WebSocket 连接。需要处理页面可见性变化:

document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
// 页面重新可见,检查连接状态
if (socket.readyState !== WebSocket.OPEN) {
console.log('页面恢复可见,重新连接...');
reconnect();
}
} else {
// 页面隐藏,可以选择暂停心跳或保持连接
// 取决于应用需求
}
});

电量消耗

持续活跃的 WebSocket 连接会增加电量消耗。可以考虑:

  1. 降低心跳频率:移动端可以使用更长的心跳间隔
  2. 智能休眠:页面不可见时降低活动频率
  3. 批量发送:合并多个小消息减少传输次数
// 根据页面可见性调整心跳策略
class AdaptiveHeartbeat {
constructor(socket) {
this.socket = socket;
this.activeInterval = 30000; // 前台:30 秒
this.backgroundInterval = 120000; // 后台:120 秒
this.currentInterval = this.activeInterval;
this.setupVisibilityHandler();
this.startHeartbeat();
}

setupVisibilityHandler() {
document.addEventListener('visibilitychange', () => {
this.currentInterval = document.visibilityState === 'visible'
? this.activeInterval
: this.backgroundInterval;
this.restartHeartbeat();
});
}

startHeartbeat() {
this.timer = setInterval(() => {
if (this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify({ type: 'ping' }));
}
}, this.currentInterval);
}

restartHeartbeat() {
clearInterval(this.timer);
this.startHeartbeat();
}
}

浏览器支持

WebSocket API 在现代浏览器中得到广泛支持。根据 Can I Use 的数据,WebSocket 在全球浏览器中的支持率超过 98%。

所有主流浏览器的较新版本都支持 WebSocket:

  • Chrome 4+
  • Firefox 4+
  • Safari 5+
  • Edge 12+
  • Opera 11+

特性检测与降级

对于不支持 WebSocket 的旧浏览器或特殊网络环境,应该实现特性检测和降级方案:

// 基本特性检测
if ('WebSocket' in window) {
// 浏览器支持 WebSocket
connectWebSocket();
} else {
// 降级到其他方案
console.log('浏览器不支持 WebSocket,使用替代方案');
connectLongPolling();
}

降级方案选择

降级方案适用场景优点缺点
长轮询通用降级实现简单,兼容性好延迟高,资源消耗大
SSE服务器推送原生支持自动重连仅单向通信
Socket.IO需要兼容性自动降级,API 统一库较大,协议非标准
HTTP 流大量数据推送相对高效实现复杂

使用 Socket.IO 实现自动降级

// Socket.IO 会自动选择最佳传输方式
import { io } from 'socket.io-client';

const socket = io('http://localhost:8080', {
transports: ['websocket', 'polling'] // 优先使用 WebSocket
});

socket.on('connect', () => {
console.log('连接成功,传输方式:', socket.io.engine.transport.name);
});

手动实现降级

class RealtimeConnection {
constructor(url) {
this.url = url;
this.socket = null;
this.pollInterval = null;

if ('WebSocket' in window) {
this.connectWebSocket();
} else {
this.connectLongPolling();
}
}

connectWebSocket() {
this.socket = new WebSocket(this.url);

this.socket.onmessage = (event) => {
this.onMessage(JSON.parse(event.data));
};

this.socket.onclose = () => {
// WebSocket 连接失败,尝试降级
if (this.pollInterval === null) {
console.log('WebSocket 失败,降级到长轮询');
this.connectLongPolling();
}
};
}

connectLongPolling() {
const poll = async () => {
try {
const response = await fetch(this.url.replace('ws', 'http'));
const data = await response.json();
this.onMessage(data);
} catch (error) {
console.error('轮询失败:', error);
}
};

poll();
this.pollInterval = setInterval(poll, 5000);
}

onMessage(data) {
// 由子类或使用者重写
console.log('收到消息:', data);
}

send(data) {
if (this.socket && this.socket.readyState === WebSocket.OPEN) {
this.socket.send(JSON.stringify(data));
} else {
// 长轮询模式下使用 HTTP POST
fetch(this.url.replace('ws', 'http'), {
method: 'POST',
body: JSON.stringify(data)
});
}
}

close() {
if (this.socket) {
this.socket.close();
}
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
}
}

代理和防火墙问题

某些企业网络或防火墙可能会阻止 WebSocket 连接。检测方法:

async function testWebSocketConnection(url, timeout = 5000) {
return new Promise((resolve) => {
const socket = new WebSocket(url);
const timer = setTimeout(() => {
socket.close();
resolve({ success: false, reason: 'timeout' });
}, timeout);

socket.onopen = () => {
clearTimeout(timer);
socket.close();
resolve({ success: true });
};

socket.onerror = () => {
clearTimeout(timer);
resolve({ success: false, reason: 'error' });
};
});
}

// 使用
testWebSocketConnection('wss://example.com/ws').then(result => {
if (!result.success) {
console.log('WebSocket 不可用,使用降级方案');
// 切换到长轮询
}
});

本教程内容

本教程将系统讲解 WebSocket 的各个方面:

  1. 协议原理:深入理解 WebSocket 协议的工作机制,包括握手过程、数据帧格式、连接状态管理等
  2. 浏览器客户端:学习使用 JavaScript WebSocket API,处理连接、消息、错误和关闭事件,以及新的 WebSocketStream API
  3. Node.js 服务端:使用 ws 库和 Socket.IO 构建高性能 WebSocket 服务器
  4. Python 服务端:使用 websockets 库和 FastAPI 实现 WebSocket 服务
  5. Java 服务端:使用 Jakarta WebSocket 和 Spring Boot 实现 WebSocket 服务
  6. Go 服务端:使用 gorilla/websocket 构建 Go 语言 WebSocket 服务
  7. 最佳实践:连接管理、心跳机制、错误处理、安全考虑、性能优化等
  8. 技术对比:WebSocket 与 SSE、长轮询、WebRTC 等实时技术的对比分析
  9. 故障排除:常见问题诊断和解决方案

快速开始

如果你迫不及待想体验 WebSocket,这里有一个最简单的示例:

服务端(Node.js)

const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', (ws) => {
console.log('客户端已连接');

ws.on('message', (message) => {
console.log('收到消息:', message);
ws.send('服务器收到: ' + message);
});

ws.send('欢迎连接 WebSocket 服务器');
});

console.log('WebSocket 服务器运行在 ws://localhost:8080');

客户端(浏览器)

const socket = new WebSocket('ws://localhost:8080');

socket.onopen = () => {
console.log('连接已建立');
socket.send('你好,服务器');
};

socket.onmessage = (event) => {
console.log('收到消息:', event.data);
};

socket.onerror = (error) => {
console.error('连接错误:', error);
};

socket.onclose = () => {
console.log('连接已关闭');
};

运行服务端代码后,在浏览器控制台执行客户端代码,你就能看到 WebSocket 的基本通信效果。

小结

本章介绍了 WebSocket 的基本概念、与 HTTP 的区别、适用场景以及浏览器支持情况。WebSocket 通过在单个 TCP 连接上提供全双工通信,解决了传统 HTTP 在实时通信场景下的局限性,成为现代 Web 应用实现实时功能的重要技术。

在接下来的章节中,我们将深入探讨 WebSocket 协议的工作原理,以及如何在不同编程语言中实现 WebSocket 客户端和服务端。