各位观众老爷,大家好!我是今天的主讲人,一个在代码海洋里摸爬滚打多年的老码农。今天咱们不谈风花雪月,就聊聊JS WebTransport
里的两个好兄弟:Datagrams
(数据报) 和 Streams
(流),看看它们在实时性和可靠性方面到底有啥门道。准备好了吗?咱们开车啦!
WebTransport:HTTP/3 的 UDP 马甲
首先,简单介绍一下WebTransport。你可以把它想象成HTTP/3穿上了UDP的马甲,专门为需要低延迟、高吞吐量的数据传输场景设计的。它允许我们在浏览器和服务器之间建立一个持久的双向连接,这对于游戏、音视频通话、实时数据推送等应用来说简直是福音。
Datagrams:风一样的男子
Datagrams,也就是数据报,就像UDP协议一样,它是一个无连接、不可靠的传输方式。你可以把它想象成一个快递小哥,他只负责把包裹送到目的地,至于包裹是否到达,是否顺序到达,他一概不负责。
- 实时性: Datagrams 的优势在于实时性。由于没有连接建立和维护的开销,数据可以立即发送,延迟非常低。这对于实时性要求高的场景非常重要,比如游戏里的玩家位置更新,如果延迟太高,玩家就会感觉卡顿。
- 可靠性: Datagrams 的劣势在于不可靠性。数据包可能会丢失、乱序或者重复。这意味着你需要自己在应用层处理这些问题。
Datagrams 的应用场景
- 游戏: 玩家位置、动作等实时数据更新。可以容忍少量数据丢失,但对延迟要求极高。
- 传感器数据: 实时采集的传感器数据,例如温度、湿度等。可以容忍少量数据丢失,但需要保证数据的实时性。
- 心跳检测: 用于检测连接是否存活。丢失几个心跳包问题不大,重要的是快速检测到连接断开。
Datagrams 的代码示例 (Browser)
// 假设已经建立了 WebTransport 连接,并且获得了 transport 对象
// 详细的连接建立过程请参考 WebTransport 官方文档
// 发送数据报
const encoder = new TextEncoder();
const data = encoder.encode("Hello, Datagrams!");
transport.datagrams.writable.getWriter().then(writer => {
writer.write(data);
writer.releaseLock(); // 释放锁
});
// 接收数据报
transport.datagrams.readable.getReader().then(reader => {
async function readLoop() {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
const decoder = new TextDecoder();
const message = decoder.decode(value);
console.log("Received Datagram:", message);
}
}
readLoop();
});
Datagrams 的代码示例 (Server – Node.js)
import { createServer } from 'node:http';
import { WebTransport } from '@failsafe/webtransport';
import { quicTransport } from '@failsafe/quictransport';
const httpServer = createServer();
const webTransport = new WebTransport({
// 在这里配置你的证书和密钥
// 请使用可信的证书,自签名证书可能导致连接问题
certFile: 'cert.pem',
keyFile: 'key.pem',
// 可选配置:
// maxIncomingUnidiStreams: 100, // 限制服务端接受的最大单向流数量
// maxIncomingBidiStreams: 100, // 限制服务端接受的最大双向流数量
});
httpServer.on('upgrade', webTransport.upgrade.bind(webTransport));
webTransport.on('session', session => {
console.log('New WebTransport session');
session.datagrams.addEventListener('datagramreceived', event => {
const datagram = event.datagram;
const message = new TextDecoder().decode(datagram);
console.log(`Received Datagram: ${message}`);
// Echo back the datagram
session.datagrams.send(datagram);
});
session.datagrams.addEventListener('datagramsenderror', error => {
console.error('Datagram send error:', error);
});
session.addEventListener('close', () => {
console.log('WebTransport session closed');
});
session.addEventListener('error', error => {
console.error('WebTransport session error:', error);
});
});
httpServer.listen(3000, () => {
console.log('WebTransport server listening on port 3000');
});
Streams:稳如老狗
Streams,也就是流,就像TCP协议一样,它是一个面向连接、可靠的传输方式。你可以把它想象成一个快递小哥,他不仅负责把包裹送到目的地,还保证包裹一定到达,并且按照发送的顺序到达。
- 实时性: Streams 的实时性相对较差。由于需要建立连接、进行拥塞控制和错误重传,延迟会比 Datagrams 高。
- 可靠性: Streams 的优势在于可靠性。数据不会丢失、乱序或者重复。这对于需要保证数据完整性的场景非常重要,比如文件传输、音视频流传输等。
Streams 的应用场景
- 文件传输: 需要保证文件完整性,不允许任何数据丢失。
- 音视频流传输: 虽然可以容忍少量数据丢失,但为了保证用户体验,通常会使用 Streams 来保证音视频流的稳定传输。
- 控制指令: 需要保证控制指令的可靠传输,例如远程控制机器人。
Streams 的类型
WebTransport 支持两种类型的 Streams:
- Unidirectional Streams (单向流): 只能由一方发送数据,另一方接收数据。就像一个单行道,只能单向行驶。
- Bidirectional Streams (双向流): 双方都可以发送和接收数据。就像一个双向车道,可以双向行驶。
Streams 的代码示例 (Browser – Unidirectional)
// 创建一个单向流
transport.createUnidirectionalStream().then(stream => {
const writer = stream.getWriter();
const encoder = new TextEncoder();
const data = encoder.encode("Hello, Unidirectional Stream!");
writer.write(data);
writer.close(); // 关闭写入流
});
// 接收单向流
transport.incomingUnidirectionalStreams.getReader().then(reader => {
async function readLoop() {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
const stream = value;
const streamReader = stream.getReader();
let result = await streamReader.read();
let decoder = new TextDecoder();
let message = decoder.decode(result.value);
console.log("Received Unidirectional Stream:", message);
}
}
readLoop();
});
Streams 的代码示例 (Server – Unidirectional)
//Server 端接收单向流
webTransport.on('session', session => {
session.incomingUnidirectionalStreams.addEventListener('datagramreceived', async event => {
const reader = event.stream.getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
console.log('Received data from unidirectional stream:', new TextDecoder().decode(value));
}
} catch (error) {
console.error('Error reading from unidirectional stream:', error);
} finally {
reader.releaseLock(); // 释放 reader
}
});
});
Streams 的代码示例 (Browser – Bidirectional)
// 创建一个双向流
transport.createBidirectionalStream().then(stream => {
const writer = stream.writable.getWriter();
const encoder = new TextEncoder();
const data = encoder.encode("Hello, Bidirectional Stream!");
writer.write(data);
// 接收数据
const reader = stream.readable.getReader();
async function readLoop() {
while (true) {
const { value, done } = await reader.read();
if (done) {
break;
}
const decoder = new TextDecoder();
const message = decoder.decode(value);
console.log("Received Bidirectional Stream:", message);
}
}
readLoop();
// 关闭写入流
writer.close();
});
Streams 的代码示例 (Server – Bidirectional)
//Server 端接收双向流
webTransport.on('session', async session => {
session.addEventListener('stream', async event => {
const stream = event.stream;
const reader = stream.readable.getReader();
const writer = stream.writable.getWriter();
try {
while (true) {
const { done, value } = await reader.read();
if (done) {
break;
}
const message = new TextDecoder().decode(value);
console.log('Received data from bidirectional stream:', message);
// Echo back the message
await writer.write(new TextEncoder().encode(`Server received: ${message}`));
}
} catch (error) {
console.error('Error reading/writing to bidirectional stream:', error);
} finally {
reader.releaseLock();
await writer.close();
}
});
});
Datagrams vs. Streams:选择困难症?
那么,在实际应用中,我们应该选择 Datagrams 还是 Streams 呢?这取决于你的具体需求。
特性 | Datagrams | Streams |
---|---|---|
连接 | 无连接 | 面向连接 |
可靠性 | 不可靠 (可能丢失、乱序、重复) | 可靠 (保证数据完整性和顺序) |
实时性 | 高 | 相对较低 |
开销 | 低 | 高 |
适用场景 | 实时性要求高,可以容忍少量数据丢失的场景 | 可靠性要求高,需要保证数据完整性的场景 |
例子 | 游戏、传感器数据、心跳检测 | 文件传输、音视频流传输、控制指令 |
总结
- Datagrams: 适合对实时性要求极高,但可以容忍少量数据丢失的场景。
- Streams: 适合对可靠性要求极高,需要保证数据完整性的场景。
一些额外的思考
- 可靠性与实时性的权衡: 在实际应用中,我们往往需要在可靠性和实时性之间进行权衡。例如,在音视频通话中,我们可以使用 Streams 来保证音视频流的稳定传输,但同时也可以使用 Datagrams 来传输一些控制指令,以提高响应速度。
- 应用层协议: 无论选择 Datagrams 还是 Streams,都需要设计合适的应用层协议来处理数据。例如,在使用 Datagrams 时,我们需要自己处理数据包的丢失、乱序和重复问题。
- 拥塞控制: WebTransport 使用 QUIC 协议作为底层传输协议,QUIC 协议自带拥塞控制机制,可以有效地避免网络拥塞。
最后的提醒
WebTransport 还在发展中,API 可能会发生变化。在使用 WebTransport 时,请务必参考最新的官方文档。
好了,今天的讲座就到这里。希望大家对 WebTransport 的 Datagrams 和 Streams 有了更深入的了解。记住,没有最好的技术,只有最适合的技术。根据你的实际需求,选择合适的传输方式,才能构建出高性能、高可靠的应用。
感谢大家的观看!下次再见!