📜 ⬆️ ⬇️

QUIC DataChannels: First Steps


QUIC-based DataChannels are considered an alternative to the current SCTP transport. The WebRTC workgroup at Google is already experimenting with them:


Let's try it out too. To do this, we will create a one-page application, similar to the example of a WebRTC channel for transmitting text - this is a fully working example (and without signaling servers), which, moreover, will make it easy to compare the approaches to the implementation of WebRTC DataChannels.

Before we begin, let's recall the basics of DataChannel .

Briefly about DataChannel


DataChannels in WebRTC allow participants to exchange arbitrary data. They can be both reliable - which is very useful when transferring files - and unreliable, which is acceptable for information about positions in games. The API is an RTCPeerConnection extension and looks like this:
')
 const dc = pc.createDataChannel("some label string"); // ,    – ,   – //    send dc.send("some string"); //    otherPc.addEventListener('datachannel', e => { const channel = e.channel; channel.onmessage = event => { console.log('received', event.data); }); }); 

On the official WebRTC samples page there are examples of sending strings and binary data .

DataChannel uses SCTP protocol. It works in parallel with the RTP transport for audio and video streams. Unlike UDP, which is commonly used by audio and video streams, SCTP offers many other features, like multiplexing channels over a single connection or reliable, partially reliable (that is, reliable, but disordered) and unreliable modes.

Google introduced QUIC in 2012 (you can read more about the history of the protocol and its nuances in our other material - the translator’s comment). Like WebRTC, the QUIC protocol has also been taken under the wing of the IETF and now it is HTTP / 3 . QUIC has a number of excellent innovations, such as: reducing latency, bandwidth calculation based on congestion control, forward delay correction (FEC), and implementation in user space (instead of the kernel) for faster rolling.

QUIC can be an alternative to RTCP for WebRTC - as a transport for DataChannel. The current experiment tries to avoid using the RTCPeerConnection API ( and SDP! ) Using a separate version of ICE transport. Think of it as a virtual connection that adds a bit of security and a lot of NAT traversal .

The video below explains this concept from Ian Swett of the Chrome networking team. And although this performance is already several years old, it still gives additional information on the topic:


First steps with QUIC


Fortunately, most of the code from the 2015 article remains relevant and easily adapts to the new API. Let's see.

Clone the code from here or try it here . Please note that Chrome (version 73+ now is Canary) must be running with special flags in order for the experiment to work locally:

 google-chrome-unstable --enable-blink-features=RTCQuicTransport,RTCIceTransportExtension 

ICE transport setup


The RTCIceTransport specification is based on ORTC, so the configuration is similar to the old code:

 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); }; //  ICE- 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); } }; //  ICE- ice1.start(ice2.getLocalParameters(), 'controlling'); ice2.start(ice1.getLocalParameters(), 'controlled'); ice1.gather(iceOptions); ice2.gather(iceOptions); 

Note that in this API there is no RTCIceGatherer, unlike ORTC. Because we already have everything you need to install ICE-transport.

QUIC transport setup


 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); }; //     QUIC quic2.addEventListener('quicstream', (e) => { console.log('QUIC transport 2 got a stream', e.stream); receiveStream = e.stream; }); 

Here, the experiment departs from the specification, which uses certificate-based identification. Instead, the public key is used, as stated in a post by Google Developers :
The RTCQuicTransport connection is configured with an API public key. At the moment we are not planning for this API to replace the original check. It will be replaced by an alarm that identifies deleted certificates to validate self-signed certificates - when QUIC starts supporting it in Chromium.
So far so good.

QUICStream to send and receive data


Using QUICStream is a bit more complicated than WebRTC DataChannel. The Streams API ( see MDN for details ), created by the WHATWG working group, was adopted, but not implemented .

We create sendStream only after the QUIC transport goes to the “connected” state - in a different state this would result in an error:

 quic1.onstatechange = function() { console.log('QUIC transport 1 state change', quic1.state); if (quic1.state === 'connected' && !sendStream) { sendStream = quic1.createStream('webrtchacks'); //   createDataChannel. document.getElementById('sendButton').disabled = false; document.getElementById('dataChannelSend').disabled = false; } }; 

Then we hang the handlers on the send button and the input field: after clicking on the button, the text from the input field is encoded in Uint8Array and written to the stream:

 document.getElementById('sendButton').onclick = () => { const rawData = document.getElementById('dataChannelSend').value; document.getElementById('dataChannelSend').value = ''; //  Uint8Array. ,       TextEncoder. const data = encoder.encode(rawData); sendStream.write({ data, }); }; 

The first entry will trigger an onquicstream event on a remote QUIC transport:

 //     QUIC quic2.addEventListener('quicstream', (e) => { console.log('QUIC transport 2 got a stream', e.stream); receiveStream = e.stream; receiveStream.waitForReadable(1) .then(ondata); }); 

... and then we wait for the data to be read:
 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); } 

All data from receiveStream will be read, decoded into text and placed in the output field. And so every time there will be data available for reading.

Conclusion and comments


I hope this example is easier to understand than a similar one on Google’s blog . This method is hardly suitable for P2P connections, the DataChannel on SCTP already does an excellent job for them. However, this may be an interesting alternative to web sockets with a QUIC server at the other end. Until this happens, a decent way to work with unreliable and disordered channels should be determined. In my opinion, the sentences from the above post look more like hacks than solutions.

It is also unclear how much feedback the developers are waiting for. “Introduce the specification already instead of scaling back shortcuts that will remain with us for several years,” sounds too obvious. Plus, the general opinion of the community tends to use the WHATWG streams, which makes it strange for developers to test their own API for reading data.

I would also like SCTP in Chromium to have additional functions. For example, this query about DataChannel - the most rated, by the way - has remained virtually untouched for three years. It is not entirely clear why the focus on QUIC takes place when there are still SCTP tasks; however, this should not stop anyone from QUIC testing and feedback on the results.

Voximplant comment


A word to our frontend- lead irbisadm :
For a long time, our SDKs use web socket for signaling. This is an excellent, time-tested standard, but there are some problems with it. First is TCP. And TCP is not so good and fast on mobile networks, plus it does not support roaming between networks. Secondly, it is often text (there is also a binary mode, but you will rarely see it).

We recently launched a closed beta test of the signal protocol on the DataChannel. This protocol is also not without drawbacks, but since it works in bad networks and when roaming, it conquers at a glance. Changed the network? No need to re-connect. ICE Restart in most cases will help find a new path for traffic. But, as I said, the protocol still has flaws: not all browsers support all protocol extensions, such as guaranteed delivery and support for packet order; Also, the protocol does not support gzip for text mode out of the box. But all these problems can be solved on the application side.

Source: https://habr.com/ru/post/441168/


All Articles