对于WebRTC,QUIC协议可能提供SCTP之外的替代方案作为DataChannel的传输方式,本文通过示例测试的方式将该方法与WebRTC DataChannels进行了比较。LiveVideoStack对文章内容进行了翻译。感谢bilibili高级工程师王盛提供的审校支持。
文 / Philipp Hancke
译 / 元宝,咪宝
审校 / 王盛
原文 https://webrtchacks.com/first-steps-with-quic-datachannel/
基于QUIC的DataChannels现在被视为基于SCTP传输的替代方案。谷歌的WebRTC工作人员正在对其进行实验:
我们来做一个简单的单页示例测试一下,类似于传输文本的WebRTC数据通道示例(https://webrtc.github.io/samples/src/content/datachannel/basic/)。它提供了一个完整的工作示例,不涉及信令服务器,还允许更容易地将该方法与WebRTC DataChannels进行比较。
在看实际代码之前,首先让我们回顾一下DataChannel的一些基础知识。
快速回顾DataChannel
WebRTC中的DataChannel允许在对等点之间交换任意数据。它们既可以是可靠的,对于文件传输之类的事情非常有用,也可以是不可靠的,例如可以用于在游戏中交换位置信息。这个API是WebRTC里RTCPeerConnection的扩展,如下图所示:
const dc = pc.createDataChannel("some label string");
// wait for this to be open, e.g. by adding an event listener, then call send
dc.send("some string");
// on the other side
otherPc.addEventListener('datachannel', e => {
const channel = e.channel;
channel.onmessage = event => {
console.log('received', event.data);
});
});
WebRTC示例页面提供了一些用于发送简单字符串(https://webrtc.github.io/samples/src/content/datachannel/basic/)以及二进制数据(如arraybuffers)的示例。
(https://webrtc.github.io/samples/src/content/datachannel/datatransfer/)
DataChannel使用一种称为SCTP的协议。它与用于语音和视频流的基于RTP的传输并行运行。与通常使用UDP传输语音和视频流不同,SCTP提供各种特性,例如在同一连接上多路复用多个信道,以及提供可靠、部分可靠(即可靠但无序)和不可靠的模式。
谷歌在2012年推出了QUIC。就像它对WebRTC的做法一样,它后来把QUIC带到了IETF,现在是HTTP/3,QUIC提供了许多出色的功能,包括减少延迟、基于带宽估计的拥塞控制、前向纠错(FEC)以及在用户空间比内核中实现更快的部署周期。
对于WebRTC,QUIC协议可能提供SCTP之外的替代方案作为DataChannel的传输方式。此外,当前的实验还试图避免使用RTCPeerConnection API(和SDP!(https://webrtchacks.com/webrtc-sdp-inaki-baz-castillo/)),使用独立版本的ICE传输。我们可以把它想象成一个虚拟连接,它增加了一些安全性和一堆的NAT遍历(https://webrtchacks.com/stun-helps-webrtc-traverse-nats/)。
下面这段来自WebRTC Boston的视频是Chrome网络团队的Ian Swett关于这个话题的介绍,这段视频已经有几年的历史了,但是它提供了一些额外的背景知识:
视频网址:https://youtu.be/mIvyOFu1c1Q
QUIC的第一步
幸运的是, 2015年发布的“使用ORTC的第一步”(https://webrtchacks.com/first-steps-ortc/)文章中的大部分代码仍然是具有相关性的,并且可以快速适应这个新的API。
复制代码(https://github.com/webrtchacks/first-steps-ortc)或尝试一下(https://webrtchacks.github.io/first-steps-ortc/quic.html)。请注意,需要使用特殊的标志来启动Chrome(当前为Canary的73+)才能在本地进行实验:
google-chrome-unstable --enable-blink-features=RTCQuicTransport,RTCIceTransportExtension
设置ICE传输
这个RTCIceTransport 规范(https://github.com/w3c/webrtc-ice)是以ORTC为模型的,因此建立ICE transport与我们已有的旧代码非常相似:
const ice1 = new RTCIceTransport();
ice1.onstatechange = function() {
console.log('ICE transport 1 state change', ice1.state);
};
const ice2 = new RTCIceTransport();
ice2.onstatechange = function() {
console.log('ICE transport 2 state change', ice2.state);
};
// Exchange ICE candidates.
ice1.onicecandidate = function(evt) {
console.log('1 -> 2', evt.candidate);
if (evt.candidate) {
ice2.addRemoteCandidate(evt.candidate);
}
};
ice2.onicecandidate = function(evt) {
console.log('2 -> 1', evt.candidate);
if (evt.candidate) {
ice1.addRemoteCandidate(evt.candidate);
}
};
// Start the ICE transports.
ice1.start(ice2.getLocalParameters(), 'controlling');
ice2.start(ice1.getLocalParameters(), 'controlled');
ice1.gather(iceOptions);
ice2.gather(iceOptions);
与ORTC不同,这个API没有 RTCIceGatherer 。这已足够以建立ICE传输了。
设置QUIC传输
const quic1 = new RTCQuicTransport(ice1);
quic1.onstatechange = function() {
console.log('QUIC transport 1 state change', quic1.state);
};
const quic2 = new RTCQuicTransport(ice2);
quic2.onstatechange = function() {
console.log('QUIC transport 2 state change', quic2.state);
};
// Add an event listener for the QUIC stream.
quic2.addEventListener('quicstream', (e) => {
console.log('QUIC transport 2 got a stream', e.stream);
receiveStream = e.stream;
});
在这一点上,实验偏离了使用证书指纹的规范(https://w3c.github.io/webrtc-quic/)。相反,在原始博客文章的注释中指出了PSK密钥:
注意:RTCQuicTransport连接使用PSK密钥API进行设置。我们目前不打算将此API保留在原始审判之前。一旦将此支持添加到Chromium中的QUIC,它将被信令远程证书指纹替换,以验证握手中使用的自签名证书。
到现在为止还挺好的。
使用QUICStream发送和接收数据
使用QUICStream比使用WebRTC DataChannel稍微要复杂一点。WHATWG流API(请在MDN上阅读更多关于它的信息(https://developer.mozilla.org/en-US/docs/Web/API/Streams_API))被考虑过,但没有实现(https://github.com/w3c/webrtc-quic/issues/2)。
我们在QUIC传输连接起来时才创建 sendStream,因为它在此之前会出错:
quic1.onstatechange = function() {
console.log('QUIC transport 1 state change', quic1.state);
if (quic1.state === 'connected' && !sendStream) {
sendStream = quic1.createStream('webrtchacks'); // similar to createDataChannel.
document.getElementById('sendButton').disabled = false;
document.getElementById('dataChannelSend').disabled = false;
}
};
并启用发送按钮和输入文本区域。单击发送按钮后,文本将从文本区域中抓取,以Uint8Array编码写入sendStream中:
document.getElementById('sendButton').onclick = () => {
const rawData = document.getElementById('dataChannelSend').value;
document.getElementById('dataChannelSend').value = '';
// we need a Uint8Array. Fortunately text is easy to convert using TextEncoder.
const data = encoder.encode(rawData);
sendStream.write({
data,
});
};
第一次写入将触发远程QUICTransport上的onquicstream事件:
// Add an event listener for the QUIC stream.
quic2.addEventListener('quicstream', (e) => {
console.log('QUIC transport 2 got a stream', e.stream);
receiveStream = e.stream;
receiveStream.waitForReadable(1)
.then(ondata);
});
我们将等待数据可以读取:
function ondata() {
const buffer = new Uint8Array(receiveStream.readBufferedAmount);
const res = receiveStream.readInto(buffer);
const data = decoder.decode(buffer);
document.getElementById('dataChannelReceive').value = data;
receiveStream.waitForReadable(1)
.then(ondata);
}
这将读取receiveStream中的所有可用数据,将其解码为文本,并更新输出文本区域。之后,它将再次等待更多数据变得可读。
总结和评论
希望这个示例比原始Google博客文章中(https://developers.google.com/web/updates/2019/01/rtcquictransport-api)提供的示例更容易理解和修改。客户端到客户端的连接很难成为这里的主要用例——基于SCTP的DataChannel已经很好地介绍了这一点。但是,这可能成为WebSockets的另一种有趣的替代方案,在另一端使用基于QUIC的服务器。在此之前,需要定义一种表示不可靠和无序通道的好方法。在我看来,博客文章中的建议非常像黑客。
除此之外,我还不清楚团队正在寻找什么样的外部反馈。“实施规范而不是再次采取持续多年的捷径”是相当明显的。此外,社区团体现在的共识似乎是使用WHATWG流,这使得开发人员测试自己开发的API来处理读取变得更加奇怪。
我也希望Chromium的SCTP能有一些额外的功能。例如,这个DataChannel请求最高级的Chromium原生问题为什么在三年内几乎没有改变。我不太明白为什么在SCTP上有工作要做的时候,要关注QUIC,但这不应该阻止任何人测试QUIC并提供反馈。
本文分享自 LiveVideoStack 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!