使用QUIC数据通道的第一步

基于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

 

 

填写常用邮箱,接收社区更新

WebRTC 中文社区由

运营