Хабрахабр

[Перевод] DataChannels на QUIC: первые шаги

DataChannels, основанные на QUIC, считаются альтернативой нынешнему SCTP-транспорту. Рабочая группа WebRTC в Google уже экспериментирует с ними:

Давайте мы тоже попробуем. Для этого мы создадим одностраничное приложение, похожее на пример WebRTC-канала для передачи текста – это полностью рабочий пример (причем без сигнальных серверов), который, к тому же, позволит легко сравнить подходы к реализации WebRTC DataChannels.
Прежде чем мы начнем, давайте вспомним основы DataChannel.

Вкратце о DataChannel

DataChannels в WebRTC позволяют участниками обмениваться произвольными данными. Они могут быть как надежными – что очень полезно при передаче файлов – так и ненадежными, что приемлемо для информации о позициях в играх. API представляет из себя расширение RTCPeerConnection и выглядит так:

const dc = pc.createDataChannel("some label string");
// подождите, когда канал откроется – например, навесив хендлер –
// затем вызывайте метод send
dc.send("some string"); // на другой стороне
otherPc.addEventListener('datachannel', e => );
});

На официальной странице WebRTC samples есть примеры отправки строк и бинарных данных.

Он работает параллельно с RTP-транспортом для аудио- и видеопотоков. DataChannel использует протокол SCTP. надежного, но неупорядоченного) и ненадежного режимов. В отличие от UDP, который обычно используется аудио- и видеопотоками, SCTP предлагает много другой функциональности вроде мультиплексирования каналов по одному соединению или надежного, частично надежного (т.е.

переводчика). Google представил QUIC в 2012 (подробно про историю протокола и его нюансы можно почитать в другом нашем материале – прим. У QUIC есть ряд отличных нововведений, как то: снижение задержки, расчет полосы пропускания на основе управления перегрузкой, прямая коррекция задержек (FEC) и реализация в пространстве пользователя (вместо ядра) для более быстрой раскатки. Подобно WebRTC, протокола QUIC также был взят под крыло IETF и теперь это HTTP/3.

Текущий эксперимент пытается избежать использования RTCPeerConnection API (и SDP!), используя отдельную версию ICE-транспорта. QUIC может стать альтернативой RTCP для WebRTC – как транспорт для DataChannel. Думайте об этом как о виртуальном соединении, которое добавляет немного безопасности и много NAT traversal.

И хотя этому выступлению уже несколько лет, оно все же дает дополнительную информацию по теме: В видео ниже – объяснение этой концепции от Ian Swett из команды Chrome networking.

Первые шаги с QUIC

К счастью, большая часть кода из статьи 2015 года остается актуальной и легко адаптируется под новое API. Давайте разберемся.

Обратите внимание, что Chrome (версия 73+ сейчас – это Canary) должен быть запущен со специальными флагами, чтобы эксперимент сработал локально: Склонируйте код отсюда или попробуйте его здесь.

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

Настройка ICE-транспорта

Спецификация RTCIceTransport основана на ORTC, поэтому настройка схожа со старым кодом:

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

Заметьте, что в этом API нет RTCIceGatherer, в отличие от ORTC. Потому что у нас уже все необходимое, чтобы установить 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);
}; // добавляем хендлер для потока QUIC
quic2.addEventListener('quicstream', (e) => { console.log('QUIC transport 2 got a stream', e.stream); receiveStream = e.stream;
});

Здесь эксперимент отходит от спецификации, в которой используется идентификация на основе сертификата. Вместо этого в ход идет открытый ключ, как сказано в посте Google Developers:

На данный момент мы не планируем, чтобы это API заменило оригинальную проверку. Соединение RTCQuicTransport настраивается с открытым ключом API. Оно будет заменено сигнализацией о факте идентификации удаленных сертификатов, чтобы валидировать самоподписанные сертификаты – когда QUIC начнет поддерживать это в Chromium.

Пока все идет неплохо.

QUICStream для отправки и получения данных

Задействовать QUICStream немного сложнее, чем WebRTC DataChannel. Streams API (см. подробности на MDN), созданное рабочей группой WHATWG, было принято, но не внедрено.

Мы создаем sendStream только после того, как QUIC-транспорт переходит в состояние «connected» – в другом состоянии это бы привело к ошибке:

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

Затем навешиваем обработчики на кнопку отправки и поле ввода: после клика по кнопке, текст из поля ввода кодируется в Uint8Array и пишется в поток:

document.getElementById('sendButton').onclick = () => { const rawData = document.getElementById('dataChannelSend').value; document.getElementById('dataChannelSend').value = ''; // нужен Uint8Array. Хорошо, что это легко сделать с помощью TextEncoder. const data = encoder.encode(rawData); sendStream.write({ data, });
};

Первая запись вызовет событие onquicstream на удаленном QUIC-транспорте:

// добавляем хендлер для потока QUIC
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. Такой способ едва ли подходит для P2P-соединений, для них уже отлично справляется DataChannel на SCTP. Тем не менее, это может стать интересной альтернативой веб-сокетам с QUIC-сервером на другом конце. Пока это не случилось, следует определить достойный способ работы с ненадежными и неупорядоченными каналами. По моему мнению, предложения из вышеупомянутого поста больше выглядят хаками, нежели решениями.

«Внедрите уже спецификацию вместо того, чтобы снова лепить шорткаты, которые останутся с нами на несколько лет», – звучит слишком очевидно. Также неясно, какой обратной связи извне ждут разработчики. Плюс, общее мнение коммьюнити склоняется к тому, чтобы использовать потоки WHATWG, что выставляет в странном свете разработчиков, просящих потестировать собственное API для чтения данных.

Например, вот этот запрос про DataChannel – самый рейтинговый, к слову – остается практически нетронутым уже три года. Еще бы мне хотелось, чтобы SCTP в Chromium имел дополнительные функции. Не совсем понятно, почему имеет место фокус на QUIC, когда еще есть задачи по SCTP; однако это не должно никого останавливать от тестирования QUIC и обратной связи о результатах.

Комментарий Voximplant

Слово нашему Frontend-лиду irbisadm:

Это отличный, проверенный временем стандарт, но с ним есть некоторые проблемы. Достаточно давно наши SDK используют для сигнализации web socket. А TCP не то чтобы хорош и быстр на мобильных сетях, плюс никак не поддерживает роуминг между сетями. Во-первых, это TCP. Во-вторых, он зачастую текстовый (бинарный режим тоже есть, но его нечасто увидишь).

Это тоже протокол не без минусов, но так как он работает в плохих сетях и при роуминге, то покоряет с первого взгляда. Недавно мы запустили закрытое бета-тестирование сигнального протокола на DataChannel. Не нужно пересоздавать подключение. Поменяли сеть? Но, как я и говорил, у протокола есть пока недостатки: не все браузеры поддерживают все расширения протокола, такие как гарантированная доставка и поддержка порядка пакетов; также протокол не поддерживает gzip для текстового режима из коробки. ICE Restart в большинстве случаев поможет найти новый путь для трафика. Но все эти проблемы можно решить на стороне приложения.

Показать больше

Похожие публикации

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»