基于QUIC的数据通道目前正在被考虑用来替换基于SCTP的传输协议。Google的WebRTC小伙伴们正在对此进行试验。
让我们对此进行测试。我们将进行一个简单的单页示例,类似于传输文本的WebRTC数据通道示例。它提供了一个完整的工作示例,不涉及信令服务器,还允许更轻易的将此方法与WebRTC数据通道进行对比。
在查看代码前,首先我们回顾一下数据通道的基本知识。
数据通道快速回顾
WebRTC中的数据通道允许对等体之间交换任意数据。它们可以是可靠的,对于传输文件非常有用,也可以是不可靠的,比如被用来在游戏中交换位置信息。API是WebRTCs 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示例界面提供了一些用于发送简单字符串以及二进制数据的示例。
数据通道使用称为SCTP的协议。这与用于语音和视频交流的基于RTP的传输并行运行。与通常使用语音和视频流的UDP不同,SCTP提供各种功能,例如在同一连接上复用多个通道,以及提供可靠,部分可靠和不可靠的模式。
Google在2012年推出了QUIC。就像它对WebRTC的做法一样,它后来把QUIC带到了IETF,现在是HTTP/3. QUIC提供了许多漂亮的功能,包括减少延迟,基于带宽估计的拥塞控制,前向纠错,以及用户空间与内核的实现,以加快部署周期。
对于WebRTC,QUIC协议可能提供SCTP的替代方案作为数据通道的传输。此外,当前的实验试图避免使用RTCPeerConnection API,使用独立版本的ICE传输。可以把它想象成一个虚拟连接,增加了一些安全性和NAT穿透。
下面的WebRTC波士顿视频,关于这个主题的Chrome网络团队的Ian Swett已经有几年了,但是给出了一些额外的背景知识:
QUIC的第一步
幸运的是,2015年从ORTC博客发布的第一步中的大部分代码仍然具有相关性,并且可以快速适应这个新的API。让我们直接讨论它。
在此处克隆代码或在此处尝试。请注意,需要使用特殊标记启动Chrome才能在本地启用实验。
google-chrome-unstable --enable-blink-features=RTCQuicTransport,RTCIceTransportExtension
建立ICE传输
RTCIce规范是在ORTC之后建模的,因此设置ICE传输与我们的旧代码非常相似。
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; });
在这一点上,实验偏离了使用证书指纹的规范。相反,在原始博客文章的注释中提到了预共享密钥:
注意:RTCQuic传输连接使用预共享密钥API进行设置。我们目前不打算将此API保留在原始试验前。一旦将此支持添加到Chromium中的QUIC,它将被信令远程证书替换,以验证握手中使用的自签名证书。
使用QUIC流发送接收数据
使用QUIC流比使用WebRTC数据通道要复杂一点。WHATWG流API已被考虑但未实现。
我们在QUIC传输连接时创建发送流,因为它在此之前会引起错误。
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并写入流中。
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, }); };
第一次写入将触发远程QUIC传输上的onquicstreram事件。
// 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); }
这将会从接收流中读取数据,解码为文本,更新输出文本区域。之后,它将再次等待更多数据变为可读。
总结
希望此示例比起初Google博客文章中提到的示例更容易理解和修改。客户端到客户端的连接几乎不会成为主要用例,基于SCTP的数据通道已经很好的包括了这一点。然而,这可能成为另一端基于QUIC的服务器的WebSockets的有趣替代方案。在此之前,需要定义表示不可靠和无序通道的好方法。博客中的建议与我的想法相似。
除此之外,我还不清楚团队正在寻找什么样的外部反馈。此外,社区团体现在的共识似乎是使用WHATWG流,这使得开发人员要测试一个自行开发的API来处理读取甚至更奇怪的问题。
我还希望Chromium的SCTP有一些额外的功能。例如,此数据通道请求最高星级Chromium原生问题几乎有三年未被涉及。我不太明白为什么在SCTP上有工作要关注QUIC,但这不应该阻止任何人测试QUIC并提供反馈。
你对API有反馈意见么?官方提交方式有些模糊,在这里留下评论,至少我们会读到它。
原文标题:First steps with QUIC DataChannels
作者:“Philipp Hancke“