Стандартный путь переслать файл через интернет это облако: загрузить на сервер, получить ссылку, отправить ссылку. Работает везде, но съедает место на диске, требует доверия к хранилищу и гоняет байты два раза. WebRTC предлагает другой маршрут: браузеры соединяются напрямую, файл идёт от человека к человеку, а серверу остаётся роль знакомящего свата, который исчезает сразу после рукопожатия.
Такой обмен особенно хорош там, где ценны приватность и скорость: соседи в одной сети получают файл на скорости локальной сети, конфиденциальный документ не остаётся на чужом диске, а огромный архив не требует тарифа на хранение. Ниже разобраны все слои прямого обмена: канал, сигналинг, нарезка на части, сборка, контроль переполнения и честные ограничения технологии.
Зачем отдавать файлы напрямую когда рядом есть облака и что это даёт
Облачная пересылка стоит трёх ожиданий: загрузка на сервер, уведомление получателю, скачивание с сервера. На гигабайтном файле это гигабайт вверх и гигабайт вниз, даже если оба человека сидят в одном офисе. Прямой канал соединяет браузеры без посредника, и файл проходит путь один раз, между двумя машинами, минуя диски чужих дата-центров.
Приватность получается не декларацией, а конструкцией: канал зашифрован стандартными средствами транспорта, содержимое недоступно ни сигналинг-серверу, ни провайдеру, ни владельцу TURN-узла, который в идеале вообще не участвует. Для документов, которые не должны храниться на сторонних площадках даже сутки, это архитектурное свойство важнее любых обещаний в политике конфиденциальности.
Экономика тоже говорит в пользу прямого пути: отправитель не платит за хранение, сервис не несёт затрат на диски под чужие файлы, а масштабирование не упирается в трафик через центральный узел. Сервис прямого обмена растёт вместе с числом пар, а не с числом байтов, и это редкий случай, когда интересы пользователей и владельцев совпадают полностью.
Ограничения честно стоят рядом с достоинствами: оба человека обязаны быть в сети одновременно, пауза на неделю невозможна без третьего хранилища, а мобильные сети с частой сменой подключения разрывают долгие передачи. Прямой обмен это инструмент для живого диалога, а не для отложенной отправки.
Канал RTCDataChannel и надёжная побайтовая пересылка поверх DTLS
Транспортную основу прямого обмена даёт канал, который браузер создаёт на пиринговом соединении. Канал настраивается как надёжный и упорядоченный: потерянные байты пересылаются повторно, порядок фрагментов сохраняется, и на уровне приложения это выглядит как честный поток, в котором ничего не теряется и не переставляется.
const pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.example.net:3478" }]
});
const dc = pc.createDataChannel("files", { ordered: true });
dc.onopen = () => sendHandshake();
dc.onmessage = (e) => handleChunk(e.data);
Шифрование встроено в сам транспорт: канал работает поверх протокола защищённых дейтаграмм, и отдельного слоя шифрования приложению строить не нужно. Для файлов это критично, потому что произвольные двоичные фрагменты без защиты летели бы через ту же сеть, что и открытый трафик.
Канал принимает и текст, и байты, поэтому протокол обмена строится просто: первой уходит строка с описанием файла, за ней фрагменты бинарного содержимого, в конце маркер завершения. Различение по типу сообщения снимает необходимость в собственной обёртке и упрощает код до читаемого минимума.
Байты идут в канал как есть, без текстовой упаковки, потому что перекодировка в текст раздувает объём примерно на треть и съедает процессор на обеих сторонах. Канал умеет бинарные фрагменты с рождения, и честная реализация пользуется этим, а не переизобретает упаковку.
Помимо надёжного режима канал умеет ненадёжный, когда потерянные фрагменты не пересылаются. Для файлов такой режим не подходит, зато понимание этого различия избавляет от соблазна ускорить передачу в ущерб целостности.
Сигналинг соединения и роль сервера как места встречи двух браузеров
Чтобы два браузера нашли друг друга, им нужен один обмен короткими служебными сообщениями: описанием сессии и списком сетевых кандидатов. Этот обмен и называют сигналингом, и вот здесь в проекте появляется сервер, обычно пара страниц и сокет-канал, которая сводит людей по короткому коду комнаты и после соединения перестаёт участвовать.
Схема знакома каждому, кто видел пароли из шести символов: один человек делится кодом, второй вводит, сервер передаёт служебные сообщения туда-обратно, и как только пиринговое соединение установлено, поток файлов идёт напрямую. Красиво в этой схеме то, что сервер не видит ни байта содержимого, его роль исчерпана в первые секунды.
Жизнеспособность соединения держится на обходе сетевых ограничений. Простые сети соединяются через серверы обнаружения, симметричные ограничения пробиваются ретрансляционным узлом, и тогда трафик идёт через него, но зашифрованным туннелем, где узел видит лишь обёртку. Ретрансляция это плата за совместимость, и на неё стоит закладывать бюджет в тяжёлых корпоративных сетях.
Плата за простоту сигналинга это его надёжность: сервер комнат обязан переживать обрывы и восстановления страниц, и правила повторного входа в комнату пишутся заранее. Прямой канал стабилен, а вот знакомство перед ним обязано быть неприхотливым.
Нарезка файла чанками описание передаваемого и сборка на принимающей стороне
Файл не отправляется целиком: канал ограничен по размеру одного сообщения, и рабочая практика нарезает содержимое на фрагменты по шестнадцать килобайт. Описание файла уходит первым сообщением: имя, размер, тип содержимого, и принимающая сторона сразу знает, сколько байт ждать и как назвать результат.
const CHUNK = 16384;
async function sendFile(file, dc) {
dc.send(JSON.stringify({ name: file.name, size: file.size }));
let offset = 0;
while (offset < file.size) {
const slice = file.slice(offset, offset + CHUNK);
dc.send(await slice.arrayBuffer());
offset += CHUNK;
if (dc.bufferedAmount > 4 * 1024 * 1024) {
await drainBuffer(dc);
}
}
}
function drainBuffer(dc) {
return new Promise((resolve) => {
dc.bufferedAmountLowThreshold = 1024 * 1024;
dc.onbufferedamountlow = () => resolve();
});
}
Приём зеркально прост: строка разбирается как описание, байты копятся в список, счётчик растёт с каждым фрагментом, и когда он сравнивается с заявленным размером, содержимое собирается в единый объект и предлагается к сохранению.
let meta = null;
let received = 0;
const parts = [];
dc.onmessage = (e) => {
if (typeof e.data === "string") {
meta = JSON.parse(e.data);
return;
}
parts.push(e.data);
received += e.data.byteLength;
if (meta && received === meta.size) {
offerDownload(new Blob(parts), meta.name);
}
};
Порядок фрагментов гарантирует канал, поэтому пересборка тривиальна и не требует сортировки. Число шестнадцать килобайт не необычное, а проверенное практикой: большие фрагменты упираются в лимиты реализации, маленькие множат накладные расходы на служебные заголовки.
Контроль переполнения буфера bufferedAmount и честный прогресс передачи
Канал внутри браузера снабжён буфером отправки, и если заливать фрагменты быстрее, чем сеть уносит, буфер растёт без предела, вкладка съедает память, а система ложится. Спасает счётчик заполнения: он показывает, сколько байтов уже принято к отправке, и чтение этого счётчика между фрагментами превращает заливку в честный поток со скоростью сети.
Порог и событие опустошения делают контроль аккуратным: отправитель льёт фрагменты, пока заполнение ниже порога, замирает и ждёт события, когда буфер стечёт, и продолжает. Код из предыдущего раздела именно так и устроен, и этот рисунок повторяется почти в каждой серьёзной реализации прямого обмена.
Прогресс по отправке считается не по числу отправленных вызовов, а по подтверждённому потоку: сколько байтов ушло из буфера, столько честно передано. Разница между этими числами на быстрой машине и медленной сети составляет минуты, и интерфейс, который её игнорирует, показывает пользователю ложь в красивой полоске.
Приёмная сторона считает прогресс по собранным фрагментам, и на больших файлах полезно показывать обе полоски: сколько ушло у отправителя и сколько дошло до получателя. Расхождение полосок на живом сеансе само по себе сигналит о качестве сети между людьми.
Продвинутые реализации добавляют докачку после обрыва: при восстановлении соединения принимающая сторона сообщает, сколько байтов уже собрано, и отправитель продолжает с нужного смещения. Фрагменты фиксированного размера делают смещение тривиальным, и потеря сети превращается из катастрофы в паузу.
Ограничения NAT TURN лимиты канала и сценарии где прямой обмен не годится
Главное ограничение это одновременность: обмен живёт, пока живы обе стороны. Прервался браузер, уехал в сон ноутбук, оборвалась мобильная сеть, и передача останавливается без надежды на докачку, потому что продолжить сеанс с новыми кандидатами это отдельная и сложная задача, которую решают немногие сервисы.
Второе ограничение это скорость отдачи: домашние каналы асимметричны, и upload отправителя часто в разы уже download получателя, поэтому прямая передача через интернет упирается в самый медленный конец. Внутри одной локальной сети этого нет, и прямые каналы там показывают честные мегабайты в секунду.
Третье ограничение это ретрансляция в тяжёлых сетях: когда прямая дорога не находится, поток идёт через TURN, и там уже появляется цена за трафик и зависимость от чужого узла. Продукт обязан заранее понимать долю сессий с ретрансляцией и держать её в экономических рамках.
Четвёртое ограничение это мобильные браузеры: сворачивание вкладки замирает выполнение, таймеры засыпают, и долгая передача на телефоне требует либо экрана без затемнения, либо честного предупреждения. Продуктовый ответ это сегментация сценариев: короткие файлы на мобильных, длинные на настольных машинах.
Продуктовые сценарии от короткого кода комнаты до локальной сети устройств
Продукт прямого обмена собирается вокруг сцены знакомства: короткий код, кнопка выбрать файл, полоска прогресса и одна кнопка сохранить. Всё остальное это удержание соединения и честные состояния: ожидание второй стороны, передача, завершение, потеря связи. Продукт, который показывает эти состояния словами, воспринимается как надёжный даже в моменты слабой сети.
Локальный сценарий заслуживает отдельного внимания: устройства в одной сети обнаруживают друг друга быстрее, чем набирается код, и обмен между ноутбуком и телефоном в комнате укладывается в три касания. Сервисы такого жанра экономят людям флешки и кабели, и именно тут прямые каналы показывают максимум скорости.
Передача нескольких файлов оформляется как очередь: опись уходит целиком, файлы идут по одному, и интерфейс показывает общий прогресс по списку. Папки передаются как дерево описей либо собираются в единый архив на лету, и оба подхода умещаются в один и тот же протокол фрагментов.
Порядок сборки сервиса прямого обмена свёрнут в короткую лестницу:
- поднять сигналинг по коротким кодам комнат с восстановлением после обрыва;
- собрать пиринговое соединение и канал с надёжным упорядоченным режимом;
- договориться о протоколе: заголовок с именем и размером, фрагменты, маркер конца;
- встроить контроль буфера с порогом и событием опустошения;
- показать честный прогресс по подтверждённому потоку на обеих сторонах;
- обработать состояния связи как первый класс интерфейса;
- измерить долю сессий через ретрансляцию и заложить её в экономику.
Прямой обмен файлами через WebRTC возвращает интернету старую честную идею: соединить двух людей без надзирателя посередине. Технология не заменит облака для отложенных отправок, зато там, где важны приватность, скорость и отсутствие лишних хранений, она делает то, что облаку недоступно по построению, и потому занимает своё место в продуктовом арсенале.