WebTransport实时通信
What — 是什么
WebTransport 是基于 QUIC 协议的 Web 实时通信 API,提供低延迟、多路复用、双向数据传输能力。相比 WebSocket 基于单流 TCP,WebTransport 基于 QUIC 的多流架构,彻底解决队头阻塞问题,一个流丢包不影响其他流。适用于云游戏、实时视频、低延迟金融数据、物联网遥测等对延迟敏感的场景。
核心概念:
- WebTransport:浏览器端 API,通过 HTTPS 连接服务端,支持可靠(流)和不可靠(数据报)两种传输模式
- QUIC 连接:底层使用 HTTP/3 的 QUIC 协议,0-RTT 重连,无 TCP 队头阻塞
- 双向流(Bidirectional Stream):可靠的有序字节流,类似 WebSocket 但支持多路并行
- 单向流(Unidirectional Stream):单向可靠字节流,服务端或客户端单向发送
- 数据报(Datagram):不可靠无序的消息,类似 UDP,延迟最低但可能丢包
- 会话(Session):一个 WebTransport 连接即一个会话,承载多个流和数据报
与 WebSocket/WebRTC 对比:
┌────────────────────────────────────────────────────────────┐
│ Web 实时通信方案对比 │
├──────────┬──────────────┬──────────────┬──────────────────┤
│ 维度 │ WebSocket │ WebTransport │ WebRTC DataChannel│
├──────────┼──────────────┼──────────────┼──────────────────┤
│ 协议 │ TCP │ QUIC (UDP) │ SCTP over DTLS │
│ 传输模式 │ 可靠有序 │ 可靠+不可靠 │ 可靠+部分可靠 │
│ 多路复用 │ 单流 │ 多流 │ 多通道 │
│ 队头阻塞 │ 有(TCP) │ 无 │ 部分有(SCTP) │
│ 延迟 │ 中 │ 低 │ 低 │
│ 丢包处理 │ 重传(慢) │ 可靠=重传 │ 可配置 │
│ │ │ 不可靠=丢弃 │ │
│ 连接建立 │ 1-RTT │ 1-RTT/0-RTT │ 多RTT(ICE) │
│ 协议开销 │ 低 │ 中 │ 高 │
│ 浏览器 │ 全部 │ Chrome 97+ │ 全部 │
│ │ │ Edge 97+ │ │
│ │ │ Firefox 113+ │ │
│ 典型用途 │ 聊天/推送 │ 云游戏/直播 │ P2P 视频/文件 │
└──────────┴──────────────┴──────────────┴──────────────────┘
WebTransport 数据传输模式:
| 模式 | 可靠性 | 有序性 | 延迟 | 适用场景 |
|---|---|---|---|---|
| 双向流 | 可靠 | 有序 | 中 | 聊天消息、命令/控制 |
| 单向流 | 可靠 | 有序 | 中 | 文件传输、状态推送 |
| 数据报 | 不可靠 | 无序 | 最低 | 游戏输入、视频帧、传感器数据 |
浏览器支持:
| 浏览器 | 最低版本 | 备注 |
|---|---|---|
| Chrome | 97+ | 完整支持 |
| Edge | 97+ | 完整支持 |
| Firefox | 113+ | 部分支持 |
| Safari | 实验性 | 需手动开启 |
Why — 为什么
适用场景:
- 云游戏:游戏输入(数据报,最低延迟)+ 场景状态(流,可靠)
- 实时视频流:视频帧用数据报(丢帧可接受),控制信令用流
- 金融行情:Level 2 行情数据报(最新价优先,丢包跳过),交易指令用流
- 物联网遥测:传感器数据高频上报(数据报),设备控制指令(流)
- 协同编辑:操作日志用流(可靠),光标位置用数据报(丢包无妨)
- 直播弹幕:弹幕消息用数据报,礼物/打赏用流
选择 WebTransport 的时机:
| 场景 | 推荐 | 理由 |
|---|---|---|
| 聊天/IM/通知推送 | WebSocket | 可靠有序足够,兼容性好 |
| 实时音视频 P2P | WebRTC | P2P 直连,NAT 穿透 |
| 云游戏/低延迟流 | WebTransport | 数据报低延迟 + 多流无队阻 |
| 股票行情推送 | WebTransport | 高频数据报,丢包可接受 |
| 协同编辑 | WebTransport | 多流隔离,操作日志+光标分离 |
| 普通 HTTP API | fetch/SSE | 请求-响应模式,无需长连接 |
How — 怎么用
客户端 API
// 1. 建立连接
const transport = new WebTransport('https://example.com:4433/webtransport');
try {
await transport.ready;
console.log('WebTransport 连接成功');
} catch (error) {
console.error('WebTransport 连接失败:', error);
}
// 2. 监听关闭
transport.closed.then(({ closeCode, reason }) => {
console.log(`连接关闭: code=${closeCode}, reason=${reason}`);
}).catch((error) => {
console.error('连接异常关闭:', error);
});
// 3. 关闭连接
transport.close({ closeCode: 0, reason: '正常关闭' });
数据报(Datagram)— 不可靠低延迟
// 发送数据报
const writer = transport.datagrams.writable.getWriter();
const data = new TextEncoder().encode('Hello WebTransport');
await writer.write(data);
writer.releaseLock();
// 接收数据报
async function readDatagrams() {
const reader = transport.datagrams.readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log('收到数据报:', new TextDecoder().decode(value));
}
}
readDatagrams();
// 游戏输入场景:高频发送,丢包可接受
function sendGameInput(input: { x: number; y: number; action: string }) {
const writer = transport.datagrams.writable.getWriter();
const data = new TextEncoder().encode(JSON.stringify(input));
writer.write(data); // 不 await,丢包无所谓
writer.releaseLock();
}
// 游戏循环:60fps 发送输入
function gameLoop() {
const input = getLatestInput();
sendGameInput(input);
requestAnimationFrame(gameLoop);
}
双向流(Bidirectional Stream)— 可靠有序
// 创建双向流
const bidiStream = await transport.createBidirectionalStream();
// 发送数据
const writer = bidiStream.writable.getWriter();
await writer.write(new TextEncoder().encode('Hello from client'));
await writer.write(new TextEncoder().encode('Second message'));
// await writer.close(); // 关闭写入端
// 接收数据
async function readFromStream(readable: ReadableStream<Uint8Array>) {
const reader = readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log('收到流数据:', new TextDecoder().decode(value));
}
}
readFromStream(bidiStream.readable);
// 多流并行:不同业务使用不同流
async function setupMultipleStreams() {
// 聊天流
const chatStream = await transport.createBidirectionalStream();
// 文件传输流
const fileStream = await transport.createBidirectionalStream();
// 控制信令流
const controlStream = await transport.createBidirectionalStream();
// 各流独立,互不阻塞
readFromStream(chatStream.readable);
readFromStream(fileStream.readable);
readFromStream(controlStream.readable);
}
接收服务端流
// 接收服务端发起的单向流
async function acceptIncomingStreams() {
const reader = transport.incomingUnidirectionalStreams.getReader();
while (true) {
const { value: stream, done } = await reader.read();
if (done) break;
// 读取流数据
const streamReader = stream.getReader();
while (true) {
const { value, done: streamDone } = await streamReader.read();
if (streamDone) break;
console.log('服务端推送:', new TextDecoder().decode(value));
}
}
}
// 接收服务端发起的双向流
async function acceptIncomingBidiStreams() {
const reader = transport.incomingBidirectionalStreams.getReader();
while (true) {
const { value: bidiStream, done } = await reader.read();
if (done) break;
// 读取并响应
const reader_ = bidiStream.readable.getReader();
const { value } = await reader_.read();
console.log('服务端请求:', new TextDecoder().decode(value));
// 响应
const writer = bidiStream.writable.getWriter();
await writer.write(new TextEncoder().encode('Response from client'));
await writer.close();
}
}
完整封装
// utils/webtransport.ts
interface WebTransportOptions {
url: string;
onMessage?: (data: Uint8Array, stream: 'datagram' | 'stream') => void;
onClose?: (code: number, reason: string) => void;
onError?: (error: Error) => void;
reconnect?: boolean;
maxRetries?: number;
}
class ReliableWebTransport {
private transport: WebTransport | null = null;
private options: WebTransportOptions;
private retryCount = 0;
private datagramWriter: WritableStreamDefaultWriter<Uint8Array> | null = null;
private streamReaders: Set<ReadableStreamDefaultReader<Uint8Array>> = new Set();
constructor(options: WebTransportOptions) {
this.options = {
reconnect: true,
maxRetries: 5,
...options,
};
}
async connect() {
try {
this.transport = new WebTransport(this.options.url);
await this.transport.ready;
this.retryCount = 0;
console.log('WebTransport connected');
this.setupDatagramReader();
this.setupIncomingStreams();
this.transport.closed.then(({ closeCode, reason }) => {
this.options.onClose?.(closeCode, reason);
this.handleDisconnect();
}).catch((error) => {
this.options.onError?.(error);
this.handleDisconnect();
});
} catch (error) {
this.options.onError?.(error as Error);
this.handleDisconnect();
}
}
// 发送数据报(不可靠,低延迟)
async sendDatagram(data: Uint8Array) {
if (!this.transport) return;
if (!this.datagramWriter) {
this.datagramWriter = this.transport.datagrams.writable.getWriter();
}
try {
await this.datagramWriter.write(data);
} catch {
this.datagramWriter = null;
}
}
// 创建可靠流并发送
async sendStreamMessage(data: Uint8Array): Promise<boolean> {
if (!this.transport) return false;
try {
const bidiStream = await this.transport.createBidirectionalStream();
const writer = bidiStream.writable.getWriter();
await writer.write(data);
await writer.close();
return true;
} catch {
return false;
}
}
// 发送 JSON 数据报
async sendJsonDatagram(obj: any) {
const data = new TextEncoder().encode(JSON.stringify(obj));
await this.sendDatagram(data);
}
// 发送 JSON 流消息
async sendJsonStream(obj: any) {
const data = new TextEncoder().encode(JSON.stringify(obj));
return this.sendStreamMessage(data);
}
private async setupDatagramReader() {
if (!this.transport) return;
const reader = this.transport.datagrams.readable.getReader();
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
this.options.onMessage?.(value, 'datagram');
}
} catch {
// 连接断开
}
}
private async setupIncomingStreams() {
if (!this.transport) return;
const reader = this.transport.incomingUnidirectionalStreams.getReader();
try {
while (true) {
const { value: stream, done } = await reader.read();
if (done) break;
const streamReader = stream.getReader();
this.streamReaders.add(streamReader);
try {
while (true) {
const { value, done: streamDone } = await streamReader.read();
if (streamDone) break;
this.options.onMessage?.(value, 'stream');
}
} finally {
this.streamReaders.delete(streamReader);
}
}
} catch {
// 连接断开
}
}
private handleDisconnect() {
this.transport = null;
this.datagramWriter = null;
this.streamReaders.clear();
if (this.options.reconnect && this.retryCount < (this.options.maxRetries || 5)) {
this.retryCount++;
const delay = Math.min(1000 * Math.pow(2, this.retryCount), 30000);
console.log(`Reconnecting in ${delay}ms (attempt ${this.retryCount})`);
setTimeout(() => this.connect(), delay);
}
}
async close() {
this.options.reconnect = false;
this.datagramWriter?.releaseLock();
this.streamReaders.forEach(r => r.releaseLock());
this.transport?.close({ closeCode: 0, reason: 'Client close' });
this.transport = null;
}
}
React Hook 封装
// hooks/useWebTransport.ts
function useWebTransport(url: string) {
const [connectionState, setConnectionState] = useState<'connecting' | 'connected' | 'disconnected'>('disconnected');
const [lastMessage, setLastMessage] = useState<any>(null);
const wtRef = useRef<ReliableWebTransport | null>(null);
useEffect(() => {
const wt = new ReliableWebTransport({
url,
onMessage: (data, type) => {
const message = JSON.parse(new TextDecoder().decode(data));
setLastMessage({ message, type, timestamp: Date.now() });
},
onClose: () => setConnectionState('disconnected'),
onError: () => setConnectionState('disconnected'),
});
wtRef.current = wt;
setConnectionState('connecting');
wt.connect().then(() => setConnectionState('connected'));
return () => { wt.close(); };
}, [url]);
const sendDatagram = useCallback((data: any) => {
wtRef.current?.sendJsonDatagram(data);
}, []);
const sendStream = useCallback((data: any) => {
return wtRef.current?.sendJsonStream(data);
}, []);
return { connectionState, lastMessage, sendDatagram, sendStream };
}
// 使用:游戏场景
function GameComponent() {
const { connectionState, lastMessage, sendDatagram } = useWebTransport(
'https://game.example.com/webtransport'
);
// 发送游戏输入(数据报,低延迟)
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
sendDatagram({ type: 'input', key: e.key, timestamp: Date.now() });
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [sendDatagram]);
// 接收游戏状态
const gameState = lastMessage?.message;
if (connectionState !== 'connected') {
return <div>连接中...</div>;
}
return (
<div>
<div>游戏画面: {JSON.stringify(gameState)}</div>
</div>
);
}
服务端实现(Node.js)
// server.ts — 使用 @fails-components/webtransport
import { WebTransportServer } from '@fails-components/webtransport';
import { readFileSync } from 'fs';
const server = new WebTransportServer({
cert: readFileSync('./cert.pem'),
key: readFileSync('./key.pem'),
port: 4433,
});
server.on('session', (session) => {
console.log('New WebTransport session');
// 处理数据报
const datagramReader = session.datagrams.readable.getReader();
(async () => {
while (true) {
const { value, done } = await datagramReader.read();
if (done) break;
console.log('Datagram:', new TextDecoder().decode(value));
// 回送数据报
const writer = session.datagrams.writable.getWriter();
await writer.write(new TextEncoder().encode('Echo: ' + new TextDecoder().decode(value)));
writer.releaseLock();
}
})();
// 处理客户端发起的双向流
const bidiReader = session.incomingBidirectionalStreams.getReader();
(async () => {
while (true) {
const { value: bidiStream, done } = await bidiReader.read();
if (done) break;
const reader = bidiStream.readable.getReader();
const writer = bidiStream.writable.getWriter();
while (true) {
const { value, done: streamDone } = await reader.read();
if (streamDone) break;
console.log('Stream data:', new TextDecoder().decode(value));
await writer.write(new TextEncoder().encode('Ack: ' + new TextDecoder().decode(value)));
}
await writer.close();
}
})();
// 服务端主动推送(单向流)
setInterval(async () => {
try {
const stream = await session.createUnidirectionalStream();
const writer = stream.writable.getWriter();
await writer.write(new TextEncoder().encode(JSON.stringify({
type: 'heartbeat',
timestamp: Date.now(),
})));
await writer.close();
} catch {
// session 已关闭
}
}, 5000);
});
server.start();
console.log('WebTransport server running on port 4433');
实战:实时行情推送
// 行情数据用数据报(最新价,丢包跳过旧数据)
// 交易指令用流(可靠有序)
class MarketDataClient {
private wt: ReliableWebTransport;
private latestPrices: Map<string, number> = new Map();
constructor(url: string) {
this.wt = new ReliableWebTransport({
url,
onMessage: (data, type) => {
const message = JSON.parse(new TextDecoder().decode(data));
if (type === 'datagram' && message.type === 'tick') {
// 行情推送:只保留最新价格
this.latestPrices.set(message.symbol, message.price);
}
},
});
}
async connect() { await this.wt.connect(); }
// 订阅行情(流消息,可靠)
async subscribe(symbols: string[]) {
await this.wt.sendJsonStream({ type: 'subscribe', symbols });
}
// 获取最新价
getLatestPrice(symbol: string): number | undefined {
return this.latestPrices.get(symbol);
}
// 下单(流消息,可靠有序)
async placeOrder(order: { symbol: string; side: 'buy' | 'sell'; quantity: number }) {
return this.wt.sendJsonStream({ type: 'order', ...order });
}
async close() { await this.wt.close(); }
}
实战:协同编辑
// 操作日志用流(可靠,必须按序执行)
// 光标位置用数据报(丢包无妨,只关心最新位置)
class CollaborativeEditor {
private wt: ReliableWebTransport;
private cursorPositions: Map<string, { x: number; y: number }> = new Map();
constructor(url: string) {
this.wt = new ReliableWebTransport({
url,
onMessage: (data, type) => {
const message = JSON.parse(new TextDecoder().decode(data));
if (type === 'datagram' && message.type === 'cursor') {
// 光标位置:覆盖旧值
this.cursorPositions.set(message.userId, message.position);
} else if (type === 'stream' && message.type === 'operation') {
// 操作日志:按序执行
this.applyOperation(message.operation);
}
},
});
}
// 发送编辑操作(流,可靠有序)
async sendOperation(op: { type: 'insert' | 'delete'; position: number; text?: string }) {
await this.wt.sendJsonStream({ type: 'operation', operation: op });
}
// 发送光标位置(数据报,低延迟,丢包可接受)
sendCursorPosition(position: { x: number; y: number }) {
this.wt.sendJsonDatagram({ type: 'cursor', position });
}
private applyOperation(op: any) {
// 应用操作到文档模型
console.log('Apply operation:', op);
}
getCursorPositions() { return this.cursorPositions; }
}
数据报背压处理
// 数据报没有流控,发送过快可能丢包
// 需要自行控制发送速率
class RateLimitedDatagramSender {
private lastSendTime = 0;
private minInterval: number;
constructor(private writer: WritableStreamDefaultWriter<Uint8Array>, maxRate = 60) {
this.minInterval = 1000 / maxRate; // 60fps = 16.67ms
}
async send(data: Uint8Array) {
const now = performance.now();
const elapsed = now - this.lastSendTime;
if (elapsed < this.minInterval) {
return; // 跳过,发送太频繁
}
try {
await this.writer.write(data);
this.lastSendTime = performance.now();
} catch {
// 写入失败,连接可能断开
}
}
}
// 使用
const writer = transport.datagrams.writable.getWriter();
const sender = new RateLimitedDatagramSender(writer, 30); // 最多 30fps
function gameLoop() {
const input = getLatestInput();
sender.send(new TextEncoder().encode(JSON.stringify(input)));
requestAnimationFrame(gameLoop);
}
常见问题与踩坑
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 连接失败 | 未使用 HTTPS 或端口未开放 | WebTransport 要求 HTTPS + 服务端配置 |
| 数据报丢失 | 数据报不可靠,缓冲区溢出 | 关键数据用流,数据报做限流 |
| 浏览器不支持 | Firefox/Safari 支持不完善 | 检测 API 后降级到 WebSocket |
| 流未关闭 | 写入端未调用 writer.close() | 用完后必须 close,否则对端不知道结束 |
| 内存泄漏 | Reader/Writer 未释放 | 组件卸载时 releaseLock + close |
| 服务端配置复杂 | QUIC 需要 TLS 1.3 证书 | 使用 Caddy/Nginx 自动配置 |
| 大消息发送失败 | 数据报有大小限制(~64KB) | 大数据用流,数据报只发小包 |
| 0-RTT 重连数据丢失 | 重连时数据报可能被服务端拒绝 | 关键操作走流,不依赖 0-RTT |
最佳实践
- 关键数据(交易、操作日志)用流(可靠有序),实时数据(输入、位置)用数据报(低延迟)
- 数据报做发送限流,避免超过服务端处理能力
- 兼容性检测:
typeof WebTransport !== 'undefined',不支持时降级 WebSocket - 连接断开自动重连,指数退避避免雪崩
- Reader/Writer 使用后及时释放,防止内存泄漏
- 服务端用 Caddy/Nginx 管理 QUIC 证书和连接
- 数据报消息控制在 1KB 以内,大数据用流传输
- 使用
transport.ready和transport.closedPromise 管理连接状态
面试题
Q1: WebTransport 和 WebSocket 的核心区别是什么?
三个核心区别:① 传输协议:WebSocket 基于 TCP,WebTransport 基于 QUIC(UDP)。TCP 有队头阻塞——一个包丢失阻塞整条连接;QUIC 多流独立,一个流丢包不影响其他流。② 传输模式:WebSocket 只支持可靠有序的字节流;WebTransport 支持三种模式——双向流(可靠有序)、单向流、数据报(不可靠无序,延迟最低)。③ 连接开销:WebSocket 首次 1-RTT;WebTransport 首次 1-RTT,重连 0-RTT。选择:需要不可靠低延迟传输(游戏输入、视频帧)→ WebTransport;只需可靠通信(聊天、推送)→ WebSocket。
Q2: WebTransport 的数据报和流分别适合什么场景?
数据报(Datagram):不可靠无序,延迟最低。适合丢包可接受、只关心最新状态的数据——游戏输入(方向/按钮,旧输入无价值)、视频帧(丢帧可跳过)、光标位置(只关心最新)、传感器数据(旧数据无意义)。流(Stream):可靠有序,延迟稍高。适合必须到达且按序处理的数据——聊天消息、交易指令、操作日志、文件传输。混合使用:同一连接上不同业务用不同模式,如云游戏 = 输入用数据报 + 控制信令用流。
Q3: WebTransport 基于 QUIC,QUIC 的优势如何体现在 WebTransport 中?
QUIC 的四大优势直接惠及 WebTransport:① 无队头阻塞:QUIC 的每个 Stream 独立拥塞控制,WebTransport 的多个流互不影响(WebSocket 单流 TCP 一个包丢失全阻塞);② 0-RTT 重连:WebTransport 重连首包即带数据,WebSocket 重走 TCP+TLS 握手;③ 连接迁移:WiFi→4G 切换时 WebTransport 连接不断,WebSocket TCP 断开重连;④ 内置加密:QUIC 强制 TLS 1.3,WebTransport 天然安全。这些优势在网络不稳定(移动端)和弱网场景下尤为明显。
Q4: WebTransport 不可用时如何降级?
降级链路:WebTransport → WebSocket → HTTP 轮询。检测方式:
typeof WebTransport !== 'undefined'。策略:① 功能检测后选择传输方式——支持 WebTransport 用数据报发实时数据,否则用 WebSocket(模拟不可靠传输需自行实现);② 协议层封装:统一的消息发送接口,底层自动选择 WebTransport/WebSocket;③ 数据报降级:WebSocket 无数据报模式,可用”最新值覆盖”策略模拟——客户端只保留最新状态,收到新输入时丢弃旧的;④ 0-RTT 降级:WebSocket 无 0-RTT,重连时用本地缓存数据先渲染,连接建立后同步。
Q5: WebTransport 如何处理背压(Backpressure)?
流模式:WebTransport 的流基于 Web Streams API,天然支持背压——ReadableStream 消费速度跟不上生产速度时,WritableStream 的
writer.write()会阻塞(返回 pending Promise),生产端自动暂停,不会导致内存溢出。数据报:没有背压机制——数据报是”发射即忘”,如果发送速度超过网络带宽,数据会被丢弃。处理方式:① 发送端做限流(Rate Limiter),控制数据报发送频率(如 30fps);② 接收端只处理最新数据,丢弃过时的数据报;③ 监控丢包率,动态调整发送速率。
Q6: WebTransport 的安全问题有哪些?如何防护?
安全考量:① 连接洪泛(Connection Flooding):攻击者创建大量 WebTransport 连接耗尽服务端资源 → 限制单 IP 连接数、使用令牌桶限流;② 流洪泛(Stream Flooding):单个连接上创建大量流 → 限制单连接最大流数量(settings.maxStreams);③ 数据报洪泛:高频发送大数据报 → 限制数据报大小和频率;④ 0-RTT 重放:重连时首包数据可能被重放 → 非幂等操作不用 0-RTT,服务端做去重;⑤ 来源验证:WebTransport 要求 HTTPS,服务端应验证 Origin 头。浏览器侧限制了 WebTransport 只能在安全上下文(HTTPS)中使用,且遵循同源策略。
Q7: WebTransport 和 WebRTC DataChannel 的区别?
WebRTC DataChannel:基于 SCTP over DTLS,支持 P2P 直连(ICE/NAT 穿透),可靠和部分可靠模式,但 SCTP 有队头阻塞风险,连接建立复杂(ICE 候选收集多 RTT),适合 P2P 场景(视频通话、文件传输)。WebTransport:基于 QUIC,客户端-服务端架构(非 P2P),0-RTT 重连,真正的多流无队头阻塞,连接建立简单(1-RTT),适合客户端-服务端的低延迟场景(云游戏、实时流)。选择:P2P → WebRTC,C/S 低延迟 → WebTransport。
Q8: 如何监控 WebTransport 的连接质量?
三个维度:① 延迟:发送带时间戳的 PING 数据报,服务端回 PONG,计算 RTT = (收到PONG时间 - 发送PING时间) / 2;② 丢包率:数据报发送计数 vs 接收确认计数,丢包率 = (发送数 - 确认数) / 发送数;③ 吞吐量:单位时间内成功传输的字节数。实现:在数据报通道建立监控子协议,定期发送探测包,滑动窗口统计延迟和丢包。阈值告警:RTT > 100ms 或丢包率 > 5% 时切换为低画质/低帧率模式。服务端可通过 QUIC 的传输统计(
quic_transport_stats)获取更精确的指标。
相关链接:
- WebSocket与实时通信
- [[HTTP2与HTTP3]]
- WebRTC实时通信
- Fetch API与请求模式
- 前端性能监控体系