WebSocket 与 WebRTC 实时通信技术详解:从即时消息到多人视频通话
前言
在互联网应用中,"实时性"是一个绕不开的话题。从微信消息的即时送达,到 Zoom 会议的多人视频通话,再到在线游戏的毫秒级操作同步,背后都离不开实时通信技术的支撑。
本文将围绕 WebSocket 和 WebRTC 这两大核心技术,从原理到实战,系统地梳理实时通信的技术体系。
一、为什么需要实时通信?
传统的 HTTP 协议是请求-响应模式:客户端发请求,服务器返回响应,一次通信结束。这种模式存在天然的局限:
| 场景 | HTTP 的问题 |
|---|---|
| 聊天消息 | 客户端不知道什么时候有新消息,只能不断轮询 |
| 多人协作 | 其他人的编辑无法实时推送到我的屏幕 |
| 视频通话 | 根本无法支持,延迟太高、开销太大 |
| 在线游戏 | 操作同步要求毫秒级响应,轮询完全不可接受 |
为了解决"服务器主动推送"和"低延迟双向通信"的需求,WebSocket 和 WebRTC 应运而生。
二、WebSocket:服务器与客户端的双向通道
2.1 什么是 WebSocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它通过 HTTP 协议完成握手升级后,客户端和服务器之间就建立了一条持久的双向通道,双方可以随时互相发送数据。
HTTP 轮询(低效):
客户端 ──请求──→ 服务器 "有新消息吗?" "没有"
客户端 ──请求──→ 服务器 "有新消息吗?" "没有"
客户端 ──请求──→ 服务器 "有新消息吗?" "有!给你"
(大量无意义的请求浪费带宽)
WebSocket(高效):
客户端 ←═══双向通道═══→ 服务器
(连接一次,持续通信,服务器有消息直接推送)
2.2 握手过程
WebSocket 的连接从一个标准的 HTTP 请求开始,通过 Upgrade 头将协议从 HTTP 升级为 WebSocket:
# 客户端发起握手请求
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# 服务器同意升级
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
握手完成后,底层的 TCP 连接保持不断,双方通过 WebSocket 帧格式传输数据。
2.3 代码示例
客户端(浏览器):
const ws = new WebSocket('ws://localhost:3000');
ws.onopen = () => {
console.log('连接已建立');
ws.send(JSON.stringify({ type: 'chat', content: '你好!' }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('收到消息:', data);
};
ws.onclose = () => console.log('连接已关闭');
ws.onerror = (err) => console.error('连接异常:', err);
服务端(Node.js + ws 库):
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000 });
wss.on('connection', (ws) => {
console.log('新客户端连接');
ws.on('message', (message) => {
const data = JSON.parse(message);
// 广播给所有在线客户端
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(JSON.stringify(data));
}
});
});
ws.on('close', () => console.log('客户端断开'));
});
2.4 Socket.IO:WebSocket 的增强封装
原生 WebSocket API 功能较为基础,生产环境中通常使用 Socket.IO 来获得更多开箱即用的能力:
| 特性 | 原生 WebSocket | Socket.IO |
|---|---|---|
| 自动重连 | 需要手动实现 | 内置支持 |
| 心跳检测 | 需要手动实现 | 内置支持 |
| 房间/命名空间 | 不支持 | 原生支持 |
| 降级方案 | 无 | 自动降级为 Long Polling |
| 二进制支持 | 支持 | 支持 |
| 广播 | 需要手动遍历 | io.to(room).emit() |
// Socket.IO 服务端 - 房间功能示例
const io = require('socket.io')(server);
io.on('connection', (socket) => {
// 加入房间
socket.on('joinRoom', (roomId) => {
socket.join(roomId);
io.to(roomId).emit('userJoined', { userId: socket.id });
});
// 房间内广播消息
socket.on('sendMessage', ({ roomId, message }) => {
io.to(roomId).emit('newMessage', {
userId: socket.id,
message,
timestamp: Date.now()
});
});
});
2.5 WebSocket 的典型应用场景
| 场景 | 说明 |
|---|---|
| 即时通讯 | 微信网页版、Slack、Discord 文字聊天 |
| 实时协作 | 在线文档协同编辑(如腾讯文档、Notion) |
| 实时推送 | 股票行情、新闻推送、通知系统 |
| 在线游戏 | 棋牌类对战、回合制游戏的操作同步 |
| 物联网 | 设备状态实时上报和远程控制 |
三、WebRTC:浏览器之间的直连通信
3.1 什么是 WebRTC
WebRTC(Web Real-Time Communication)是一套浏览器原生支持的实时通信 API,允许浏览器之间直接建立点对点(P2P)连接,传输音频、视频和任意数据,无需经过中间服务器转发。
WebSocket 通信路径:
用户A ──→ 服务器 ──→ 用户B
↑ ↓
所有数据都经过服务器
WebRTC 通信路径:
用户A ←────────────→ 用户B
直连通信
(建立连接时需要信令服务器,之后数据直传)
3.2 WebRTC 的三大核心 API
| API | 功能 | 典型用途 |
|---|---|---|
| MediaStream | 获取摄像头、麦克风的音视频流 | 采集本地音视频 |
| RTCPeerConnection | 建立 P2P 连接,传输音视频流 | 视频通话 |
| RTCDataChannel | P2P 传输任意数据 | 文件传输、游戏数据 |
3.3 连接建立过程(信令交换)
WebRTC 的 P2P 连接不能凭空建立——两个浏览器在互联网上如何找到彼此?这需要通过一个信令服务器(Signaling Server) 来帮助双方交换连接信息。
┌────────┐ ┌────────┐
│ 用户A │ │ 用户B │
└───┬────┘ └───┬────┘
│ ① 创建 Offer(SDP) │
│─────────────→ 信令服务器 ──────────────→│
│ │ ② 创建 Answer(SDP)
│←────────────── 信令服务器 ←─────────────│
│ │
│ ③ 交换 ICE Candidate(网络地址信息) │
│←──────────── 信令服务器 ───────────────→│
│ │
│ ④ P2P 连接建立!直接通信 │
│←═══════════ 直连通道 ═══════════════→│
│ 音视频流 / 数据 不再经过服务器 │
关键概念:
- SDP(Session Description Protocol):描述媒体能力,包括支持的编解码器、分辨率、带宽等信息
- ICE(Interactive Connectivity Establishment):用于在复杂的网络环境中找到两端之间的最佳通信路径
- STUN 服务器:帮助客户端发现自己的公网 IP 和端口(大部分用户在 NAT 后面)
- TURN 服务器:当 P2P 直连不通时(对称 NAT),通过 TURN 服务器中转数据
3.4 代码示例:1 对 1 视频通话
// ===== 获取本地音视频流 =====
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
document.getElementById('localVideo').srcObject = localStream;
// ===== 创建 PeerConnection =====
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }, // 免费的 STUN 服务器
{
urls: 'turn:your-turn-server.com:3478', // TURN 备用
username: 'user',
credential: 'pass'
}
]
});
// 将本地流添加到连接中
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
// 收到对方的视频流时显示
pc.ontrack = (event) => {
document.getElementById('remoteVideo').srcObject = event.streams[0];
};
// 收集 ICE 候选地址,通过信令服务器转发
pc.onicecandidate = (event) => {
if (event.candidate) {
signalingServer.send({
type: 'ice-candidate',
candidate: event.candidate
});
}
};
// ===== 发起方:创建 Offer =====
async function call() {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signalingServer.send({ type: 'offer', sdp: offer });
}
// ===== 接收方:处理 Offer 并回复 Answer =====
async function handleOffer(offer) {
await pc.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
signalingServer.send({ type: 'answer', sdp: answer });
}
// ===== 处理 Answer =====
async function handleAnswer(answer) {
await pc.setRemoteDescription(new RTCSessionDescription(answer));
}
// ===== 处理 ICE 候选 =====
async function handleIceCandidate(candidate) {
await pc.addIceCandidate(new RTCIceCandidate(candidate));
}
3.5 NAT 穿透:P2P 最大的挑战
大多数用户的设备都在路由器(NAT)后面,没有独立的公网 IP。WebRTC 通过 ICE 框架逐步尝试找到可用的连接路径:
尝试顺序(从快到慢、从省到贵):
1. Host Candidate(局域网直连)
同一局域网内的设备直接通信
↓ 不行?
2. Server Reflexive Candidate(STUN 穿透)
通过 STUN 服务器发现公网地址,尝试 NAT 穿透
↓ 还不行?
3. Relay Candidate(TURN 中转)
通过 TURN 服务器中转所有数据(最后的保底方案)
| 穿透方式 | 成功率 | 延迟 | 成本 |
|---|---|---|---|
| 局域网直连 | 仅限同一网络 | 最低 | 无 |
| STUN 穿透 | ~70-80% | 低 | STUN 服务器成本极低 |
| TURN 中转 | ~100% | 较高 | TURN 服务器带宽成本高 |
实际经验:约 80% 的场景可以通过 STUN 实现 P2P 直连,约 20% 需要 TURN 中转(主要是对称 NAT 和严格防火墙环境)。
四、多人视频通话的三种架构
当视频通话从 1v1 扩展到多人时,简单的 P2P 不再适用。业界有三种主流架构:
4.1 Mesh(全网状直连)
用户A
↗ ↖
↙ ↘
用户B ←──→ 用户C
↘ ↗
↖ ↙
用户D
每个用户和其他所有人各建一条 P2P 连接。
- 连接数:N 个人需要 N×(N-1)/2 条连接
- 每人上行:N-1 路视频流
- 适合人数:2~4 人
- 优点:不需要媒体服务器,成本为零
- 缺点:人数稍多客户端带宽和 CPU 就吃不消
4.2 SFU(选择性转发单元)— 当前主流
┌──────────────┐
│ SFU 服务器 │
│ (只转发, │
│ 不编解码) │
└─┬──┬──┬──┬──┘
│ │ │ │
┌───┘ │ │ └───┐
↓ ↓ ↓ ↓
用户A 用户B 用户C 用户D
每个用户只上传 1 路视频流到 SFU 服务器,服务器选择性地将其他人的流转发给你。
- 每人上行:1 路
- 每人下行:N-1 路
- 服务器工作:只做转发,不做编解码,压力小
- 适合人数:5~50 人
- 代表产品:Zoom、腾讯会议、Google Meet、Discord
SFU 的增强技术 — Simulcast(联播):
发送方同时编码 3 个分辨率的视频:
用户A 上传 ─→ 高清 1080p ─┐
中清 720p ─┤─→ SFU 服务器
低清 360p ─┘
│
├─→ 用户B(大屏/宽带好)→ 收 1080p
├─→ 用户C(正常网络) → 收 720p
└─→ 用户D(手机/弱网) → 收 360p
SFU 根据每个接收方的网络状况和窗口大小,智能选择转发哪个分辨率的流。
4.3 MCU(多点控制单元)
┌──────────────┐
│ MCU 服务器 │
│ (解码→混合→ │
│ 重新编码) │
└─┬──┬──┬──┬──┘
│ │ │ │
┌───┘ │ │ └───┐
↓ ↓ ↓ ↓
用户A 用户B 用户C 用户D
服务器将所有人的视频解码、混合成一个画面、重新编码后发给每个人。
- 每人上行:1 路
- 每人下行:1 路(已合成的画面)
- 服务器工作:重度计算(编解码 + 画面合成),需要 GPU 支持
- 适合场景:大型会议(上百人)、对客户端性能要求低的场景
- 缺点:服务器成本高、延迟增加、画面布局不灵活
4.4 三种架构对比
| 对比维度 | Mesh | SFU | MCU |
|---|---|---|---|
| 服务器成本 | 无 | 低(只转发) | 高(要转码) |
| 客户端上行 | N-1 路 | 1 路 | 1 路 |
| 客户端下行 | N-1 路 | N-1 路 | 1 路 |
| 延迟 | 最低 | 低 | 较高 |
| 支持人数 | 3~4 人 | 5~50 人 | 50~数百人 |
| 画面灵活性 | 高 | 高 | 低(固定布局) |
| 技术复杂度 | 简单 | 中等 | 高 |
实际产品的策略:根据通话人数动态切换架构。
2 人 → Mesh(零成本直连)
3~6 人 → SFU(低成本转发)
50+ 人 → SFU + Simulcast 或 MCU
五、WebSocket vs WebRTC:如何选择
| 对比项 | WebSocket | WebRTC |
|---|---|---|
| 通信模型 | 客户端 ↔ 服务器 | 客户端 ↔ 客户端(P2P) |
| 传输协议 | TCP | UDP 为主(SRTP/SCTP) |
| 延迟 | 较低 | 更低(直连 + UDP) |
| 音视频支持 | 不原生支持 | 原生支持(编解码内置) |
| 数据传输 | 文本/二进制 | 音视频流 + DataChannel |
| 连接建立 | 简单(HTTP 升级) | 复杂(信令 + ICE 协商) |
| 服务器角色 | 必须全程参与 | 建立后可退出(P2P 场景) |
| NAT 穿透 | 不需要 | 需要 STUN/TURN |
| 可靠性 | TCP 保证送达 | 可选可靠/不可靠传输 |
| 适用场景 | 消息推送、状态同步 | 音视频通话、低延迟数据 |
选型决策树
你的需求是什么?
│
├─ 音视频通话 ──→ WebRTC
│
├─ 实时消息/状态同步 ──→ WebSocket
│
├─ 低延迟游戏(FPS、竞速)──→ WebRTC DataChannel(UDP 模式)
│
├─ 棋牌/回合制游戏 ──→ WebSocket(需要服务端校验)
│
├─ 文件 P2P 传输 ──→ WebRTC DataChannel
│
└─ 多人协作编辑 ──→ WebSocket(需要服务端 OT/CRDT 协调)
六、实战场景:在线棋类游戏的通信设计
以一个支持 PvE(人机对战)和 PvP(在线对战)的棋类游戏为例,展示 WebSocket 和 WebRTC 如何协作。
6.1 整体架构
┌──────────────────────────────────────────────────┐
│ 前端 │
│ ┌──────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ PvE 模式 │ │ 房间对战 │ │ 在线匹配 │ │
│ │(纯本地)│ │ │ │ │ │
│ └──────────┘ └─────┬─────┘ └──────┬───────┘ │
│ │ WebSocket │ │
└──────────────────────┼───────────────┼────────────┘
│ │
┌────────▼───────────────▼───────┐
│ Node.js 后端 │
│ ┌───────────┐ ┌────────────┐ │
│ │ 房间管理 │ │ 匹配系统 │ │
│ │ 走棋校验 │ │ 挑战管理 │ │
│ └───────────┘ └────────────┘ │
│ ┌──────────────────────────┐ │
│ │ WebSocket Server │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ 信令服务(可选,用于 │ │
│ │ WebRTC 语音聊天) │ │
│ └──────────────────────────┘ │
└────────────────────────────────┘
6.2 走棋数据传输:指令 + 快照混合
// 正常走棋:传指令(轻量、支持动画)
socket.emit('makeMove', {
roomId: 'room_001',
action: {
type: 'move',
from: { x: 2, y: 3 },
to: { x: 4, y: 3 },
captured: { x: 3, y: 3 }
},
digest: {
seq: 15,
hash: 'a3f8c1' // 棋盘状态哈希,用于一致性校验
}
});
// 异常恢复:传完整快照(断线重连、观战加入)
socket.on('fullSync', (state) => {
restoreBoard(state.board);
updateUI(state);
});
| 场景 | 传输内容 | 原因 |
|---|---|---|
| 正常走棋 | 操作指令 + 哈希 | 数据小、能做动画 |
| 断线重连 | 完整快照 | 恢复完整状态 |
| 观战加入 | 完整快照 | 中途进入需要全量 |
| 哈希不一致 | 请求快照修复 | 纠错兜底 |
6.3 可选的语音聊天:WebRTC
如果需要边下棋边语音聊天,WebSocket 负责信令交换,WebRTC 负责音频直传:
// 用已有的 WebSocket 连接充当信令服务器
socket.on('webrtc-offer', async (offer) => {
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
socket.emit('webrtc-answer', answer);
});
// 只传音频,不传视频(棋类游戏不需要摄像头)
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: false
});
stream.getTracks().forEach(track => pc.addTrack(track, stream));
七、开源方案推荐
WebSocket 生态
| 项目 | 语言 | 特点 |
|---|---|---|
| Socket.IO | Node.js | 最流行,自带房间/重连/心跳 |
| ws | Node.js | 轻量原生 WebSocket 库 |
| Spring WebSocket | Java | Spring 生态,企业级 |
| Gorilla WebSocket | Go | Go 生态中最成熟 |
WebRTC 媒体服务器(SFU)
| 项目 | 语言 | 特点 |
|---|---|---|
| mediasoup | Node.js + C++ | 轻量高性能,API 友好 |
| Janus | C | 功能全面,插件丰富 |
| LiveKit | Go | 开箱即用,SDK 完善 |
| Pion | Go | 纯 Go 实现,灵活可嵌入 |
STUN/TURN 服务器
| 项目 | 说明 |
|---|---|
| coturn | 最流行的开源 TURN 服务器 |
| Google STUN | stun:stun.l.google.com:19302,免费公共 STUN |
| Twilio TURN | 商业方案,全球节点 |
八、总结
| 维度 | WebSocket | WebRTC |
|---|---|---|
| 一句话定位 | 服务器与客户端的实时双向通道 | 浏览器之间的 P2P 直连通信 |
| 核心价值 | 服务器主动推送、状态同步 | 低延迟音视频、P2P 数据传输 |
| 技术复杂度 | 低 | 高(信令、NAT 穿透、编解码) |
| 最佳场景 | 聊天、通知、协作、游戏同步 | 视频通话、直播、P2P 文件传输 |
两者不是替代关系,而是互补关系。在实际项目中,WebSocket 往往作为 WebRTC 的信令通道存在,二者协同工作才能构建完整的实时通信体系。
选型口诀:文字消息走 WebSocket,音视频走 WebRTC,需要裁判走服务器,面对面聊走 P2P。
本文基于 WebSocket RFC 6455 协议和 WebRTC 1.0 规范编写,示例代码适用于现代浏览器和 Node.js 环境。
更多推荐




所有评论(0)