DataChannels QUIC: Primeiros Passos


DataChannels baseados em QUIC são considerados uma alternativa ao transporte SCTP atual. O grupo de trabalho do Google WebRTC já está fazendo experiências com eles:


Vamos tentar também. Para fazer isso, criaremos um aplicativo de uma página semelhante ao exemplo de um canal WebRTC para transmissão de texto - este é um exemplo completo (sem servidores de sinalização), que, além disso, facilitará a comparação de abordagens para a implementação de WebRTC DataChannels.

Antes de começarmos, vamos relembrar o básico do DataChannel .

Brevemente sobre o DataChannel


Os DataChannels do WebRTC permitem que os participantes troquem dados arbitrários. Eles podem ser confiáveis ​​- o que é muito útil na transferência de arquivos - e não confiáveis, o que é aceitável para informações sobre posições em jogos. A API é uma extensão do RTCPeerConnection e tem a RTCPeerConnection aparência:

 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); }); }); 

Na página oficial de amostras do WebRTC, existem exemplos de envio de strings e dados binários .

O DataChannel usa SCTP . Funciona em paralelo com o transporte RTP para fluxos de áudio e vídeo. Ao contrário do UDP, que é comumente usado por fluxos de áudio e vídeo, o SCTP oferece muitas outras funcionalidades, como canais de multiplexação em uma única conexão ou modos confiáveis, parcialmente confiáveis ​​(ou seja, confiáveis, mas desordenados) e não confiáveis.

O Google introduziu o QUIC em 2012 (mais sobre o histórico do protocolo e suas nuances podem ser encontradas em nosso outro material - nota do tradutor). Como o WebRTC, o protocolo QUIC também foi adotado sob a asa da IETF e agora é HTTP / 3 . O QUIC possui várias grandes inovações, como: latência reduzida, cálculo da largura de banda com base no controle de congestionamento, correção direta de atraso (FEC) e implementação no espaço do usuário (em vez do kernel) para uma rolagem mais rápida.

O QUIC pode ser uma alternativa ao RTCP para WebRTC - como um transporte para o DataChannel. A experiência atual está tentando evitar o uso da API RTCPeerConnection ( e SDP! ) Usando uma versão separada do transporte ICE. Pense nisso como uma conexão virtual que adiciona um pouco de segurança e muita passagem NAT .

No vídeo abaixo, Ian Swett, da equipe de redes do Chrome, explica esse conceito. E embora esse discurso tenha vários anos, ele ainda fornece informações adicionais sobre o tópico:


Primeiros passos com o QUIC


Felizmente, a maior parte do código do artigo de 2015 permanece relevante e se adapta facilmente à nova API. Vamos descobrir.

Clone o código aqui ou tente aqui . Observe que o Chrome (versão 73+ agora é Canary) deve ser executado com sinalizadores especiais para que o experimento funcione localmente:

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

Configuração de Transporte ICE


A especificação RTCIceTransport é baseada no ORTC, portanto, a instalação é semelhante ao código antigo:

 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); 

Observe que esta API não possui RTCIceGatherer, ao contrário do ORTC. Porque já temos tudo o que precisamos para instalar o transporte ICE.

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

Aqui, o experimento parte de uma especificação que usa autenticação baseada em certificado. Em vez disso, a chave pública é usada, como diz a postagem do Google Developers :
A conexão RTCQuicTransport é configurada com uma chave pública da API. No momento, não planejamos que essa API substitua a validação original. Ele será substituído por um certificado remoto que sinaliza para validar certificados autoassinados - quando o QUIC começa a oferecer suporte no Chromium.
Até agora, tudo bem.

QUICStream para envio e recebimento de dados


O uso do QUICStream é um pouco mais complicado que o WebRTC DataChannel. A API do Streams ( veja detalhes no MDN ) criada pelo grupo de trabalho WHATWG foi aceita, mas não implementada .

Criamos o sendStream somente após o transporte QUIC entrar no estado "conectado" - em um estado diferente, isso levaria a um erro:

 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; } }; 

Em seguida, anexamos os manipuladores ao botão enviar e ao campo de entrada: depois de clicar no botão, o texto do campo de entrada é codificado no Uint8Array e gravado no fluxo:

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

A primeira entrada acionará o evento onquicstream no transporte QUIC remoto:

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

... e então esperamos que os dados sejam legíveis:
 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); } 

Todos os dados de receiveStream serão lidos, decodificados em texto e colocados no campo de saída. E assim, sempre que dados legíveis aparecem.

Conclusão e Comentários


Espero que este exemplo seja mais fácil de entender do que o semelhante no blog do Google . Este método dificilmente é adequado para conexões P2P, o DataChannel no SCTP já está indo bem para elas. No entanto, essa pode ser uma alternativa interessante aos soquetes da web com um servidor QUIC na outra extremidade. Até que isso aconteça, você deve determinar uma maneira decente de trabalhar com canais não confiáveis ​​e desordenados. Na minha opinião, as sugestões do post acima parecem mais hacks do que decisões.

Também não está claro que feedback os desenvolvedores estão esperando do lado de fora. "Apresente a especificação já em vez de esculpir os atalhos novamente, que permanecerão conosco por vários anos", parece óbvio demais. Além disso, a opinião geral da comunidade tende a usar fluxos WHATWG, o que coloca uma luz estranha nos desenvolvedores pedindo para testar sua própria API para ler dados.

Também gostaria que o SCTP no Chromium tivesse recursos adicionais. Por exemplo, essa consulta sobre o DataChannel - a mais bem classificada, a propósito - permaneceu quase intocada por três anos. Não está totalmente claro por que há um foco no QUIC quando ainda existem tarefas SCTP; no entanto, isso não deve impedir ninguém de testar o QUIC e obter feedback sobre os resultados.

Comentado por Voximplant


Uma palavra para o nosso frontend lead irbisadm :
Por um longo tempo, nossos SDKs foram usados ​​para sinalizar um soquete da Web. Esse é um excelente padrão testado pelo tempo, mas há alguns problemas com ele. O primeiro é o TCP. E o TCP não é tão bom e rápido nas redes móveis, além de não suportar roaming entre redes. Em segundo lugar, geralmente é textual (também existe um modo binário, mas você raramente o vê).

Recentemente, lançamos um teste beta fechado do protocolo de sinalização no DataChannel. Este protocolo também não está isento de desvantagens, mas, como funciona em redes ruins e em roaming, conquista à primeira vista. Você mudou a rede? Não há necessidade de recriar a conexão. ICE Restart na maioria dos casos ajudará a encontrar uma nova maneira de tráfego. Mas, como eu disse, o protocolo ainda tem desvantagens: nem todos os navegadores suportam todas as extensões de protocolo, como entrega garantida e suporte a pedidos de pacotes; também o protocolo não suporta gzip para o modo de texto pronto para uso. Mas todos esses problemas podem ser resolvidos no lado da aplicação.

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


All Articles