前言

在互联网应用中,"实时性"是一个绕不开的话题。从微信消息的即时送达,到 Zoom 会议的多人视频通话,再到在线游戏的毫秒级操作同步,背后都离不开实时通信技术的支撑。

本文将围绕 WebSocketWebRTC 这两大核心技术,从原理到实战,系统地梳理实时通信的技术体系。


一、为什么需要实时通信?

传统的 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 环境。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐