Перейти к содержанию
HexaTransfer
Вернуться к блогу
Технические погружения

WebRTC Передача файлов: Browser-to-Browser Руководство

Передавайте файлы directly between browsers using WebRTC. Data channels, signaling, и peer connection setup для real-time файл sharing.

WebRTC-передача файлов перемещает байты напрямую между двумя браузерами через RTCDataChannel, без сервера в пути данных после первоначального рукопожатия. Нужные компоненты: сигнальный канал (WebSocket или любой маленький ретранслятор) для обмена SDP-предложениями и ICE-кандидатами, STUN-серверы для обнаружения NAT, TURN-резерв для симметричных NAT, и канал данных, настроенный на надёжную упорядоченную доставку. Как только peer-соединение установлено, вы вызываете channel.send() с чанками по 16–256 КБ и наблюдаете поток байт по зашифрованной DTLS 1.2 ассоциации SCTP со скоростью, близкой к сетевой.

Как WebRTC обеспечивает peer-to-peer соединение

WebRTC — не магия. Это ICE плюс SDP плюс DTLS плюс SCTP, сложенные вместе. Отправитель создаёт RTCPeerConnection, открывает канал данных, генерирует SDP-предложение и отправляет его получателю через сигнальный канал. Получатель отвечает. Обе стороны обмениваются ICE-кандидатами (локальный IP, рефлексивный IP через STUN, ретрансляционный IP через TURN), пока не найдут работающий путь. DTLS 1.2 рукопожатие сквозное, SCTP работает поверх для надёжной потоковой передачи, и байты начинают передаваться.

Шифрование обязательно и встроено — отказаться нельзя. Это реальный выигрыш в безопасности: в отличие от WebSocket, не нужно помнить об оборачивании в прикладную криптографию для защиты от сетевых подслушивателей. Для настоящего E2EE против вашего собственного сигнального сервера — добавьте второй слой AES-256-GCM сверху, поскольку вредоносный сигнальный сервер может подменить свой DTLS-сертификат.

Сигнализация: часть, которую WebRTC не определяет

WebRTC намеренно оставляет сигнализацию за вами. WebSocket на вашем сервере подходит; как и общий код комнаты, опубликованный в Firebase Realtime DB, или даже вручную вставленные SDP-строки. Главное — чтобы оба пира в итоге обменялись предложением, ответом и потоком ICE-кандидатов.

Минимальный сигнальный сервер на Node.js:

const rooms = new Map();
wss.on('connection', (ws) => {
  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    if (msg.type === 'join') {
      const room = rooms.get(msg.room) ?? new Set();
      room.add(ws); rooms.set(msg.room, room);
    } else {
      for (const peer of rooms.get(msg.room) ?? []) {
        if (peer !== ws) peer.send(raw);
      }
    }
  });
});

Меньше 20 строк. Сервер никогда не видит байты файла — только метаданные SDP и ICE. Можно хостить на VPS за $5/мес или на Cloudflare Workers и обслуживать сотни параллельных передач.

Создание peer-соединения

Создайте соединение с публичным STUN Google плюс TURN-резервом:

const pc = new RTCPeerConnection({
  iceServers: [
    { urls: 'stun:stun.l.google.com:19302' },
    { urls: 'turn:turn.example.com:3478',
      username: 'user', credential: 'pass' }
  ]
});

Примерно 15–25% домашних подключений находятся за симметричными NAT, которые STUN не может пробить, поэтому TURN не опционален для продакшен-сервиса. Запускайте coturn на VPS с достаточной полосой для ретранслируемого трафика, или оплачивайте управляемый TURN-провайдер. Для российских пользователей: часть корпоративных сетей и МТС/Ростелеком CGNAT могут требовать TURN — закладывайте это в архитектуру.

Откройте канал данных на стороне инициатора перед созданием предложения:

const channel = pc.createDataChannel('file', {
  ordered: true, maxRetransmits: null
});
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
signaling.send({ type: 'offer', sdp: offer.sdp });

Ordered + неограниченные повторные передачи дают TCP-аналогичную надёжность. Неупорядоченный режим быстрее, но требует сборки на уровне приложения.

Чанкование файла для канала

SCTP имеет практический лимит 256 КБ на сообщение, старые браузеры плохо работают свыше 16 КБ. Безопасный размер по умолчанию — чанки по 16 КБ, читаемые из файла через File.slice():

const chunkSize = 16 * 1024;
let offset = 0;
channel.bufferedAmountLowThreshold = 1024 * 1024;
function sendNext() {
  while (offset < file.size && channel.bufferedAmount < 4 * 1024 * 1024) {
    const chunk = file.slice(offset, offset + chunkSize);
    chunk.arrayBuffer().then((buf) => channel.send(buf));
    offset += chunkSize;
  }
}
channel.onbufferedamountlow = sendNext;
sendNext();

Отметка bufferedAmount не позволяет ставить гигабайты в буфер отправки SCTP и исчерпывать память. Когда буфер падает ниже 1 МБ — пополняйте до 4 МБ. Это даёт пропускную способность около 40–80 МБ/с в локальных сетях и 5–20 МБ/с на типичном домашнем интернете.

Получение и запись байт на диск

На стороне ответчика прослушивайте входящий канал:

pc.ondatachannel = ({ channel }) => {
  const chunks = [];
  let received = 0;
  channel.onmessage = ({ data }) => {
    chunks.push(data);
    received += data.byteLength;
    updateProgress(received);
    if (received === expectedSize) finish(chunks);
  };
};

Для файлов крупнее 500 МБ не накапливайте в RAM. Используйте File System Access API для стриминга прямо на диск:

const handle = await window.showSaveFilePicker({ suggestedName: fileName });
const writable = await handle.createWritable();
channel.onmessage = async ({ data }) => writable.write(data);

Firefox и Safari пока не поддерживают showSaveFilePicker — используйте резерв Blob + URL.createObjectURL для этих браузеров, с ограничением 2 ГБ.

Отправка метаданных файла перед байтами

Получатель должен знать имя файла, размер и MIME-тип до начала байтового потока. Используйте небольшое JSON-рукопожатие по каналу данных:

channel.send(JSON.stringify({
  type: 'metadata', name: file.name,
  size: file.size, mime: file.type, sha256: fileHash
}));

Затем переключайтесь в бинарный режим. Получатель переключается в зависимости от того, является ли data строкой или ArrayBuffer. Включайте SHA-256 файла для проверки целостности после передачи, и опционально отпечаток ключа, если поверх добавляете прикладной AES-GCM.

Обработка сбоев соединения

Каналы данных WebRTC отказывают тремя способами: ICE не завершается (NAT, блокировка файрволом), DTLS рукопожатие падает (расхождение часов, проблемы сертификата), или соединение обрывается в середине передачи (сон ноутбука, смена сети). Слушайте pc.oniceconnectionstatechange и реагируйте на 'failed' или 'disconnected'. Chrome держит 'disconnected' несколько секунд перед переходом в 'failed'; Safari менее терпелив.

При сбое в середине передачи перезапустите ICE без перестройки всего соединения:

await pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
// повторно отправить через сигнализацию

Если перезапуск не помогает — откатывайтесь на возобновляемую загрузку через сервер. Гибридная архитектура P2P + сервер обрабатывает 15% сетевых условий, где чистый P2P не работает.

Добавление сквозного шифрования против вашего сервера

Встроенный DTLS WebRTC защищает от сетевых атак, но не от вредоносного или скомпрометированного сигнального сервера. Для настоящего E2EE пусть оба пира генерируют ECDH P-256 keypair, обмениваются публичными ключами через короткий внеполосный код (QR или 6-словная парольная фраза), выводят общий секрет через HKDF-SHA256 и шифруют каждое сообщение канала данных с AES-256-GCM перед вызовом send. Так даже если сигнальный сервер подменяет DTLS-сертификаты — файлы не видит.

HexaTransfer использует гибридную архитектуру — шифртекст хранится на сервере с клиентским AES-256-GCM, а не P2P — обменивая прямоту на обмен, толерантный к офлайну. Попробуйте на hexatransfer.com — бесплатно, без регистрации, до 10 ГБ.

Где WebRTC выигрывает и проигрывает

WebRTC-передача файлов блистает, когда оба пира онлайн одновременно, когда важна конфиденциальность от вашего собственного сервера, и когда файлы достаточно велики (100 МБ+), чтобы стоимость ретрансляционной полосы была болезненной. Проигрывает, когда пользователи хотят отправить и уйти, когда получатели открывают ссылку спустя часы, или когда получатели находятся в ограничительных корпоративных сетях, блокирующих STUN и TURN. Для универсального инструмента передачи чистый P2P комфортно покрывает примерно 60% случаев. Остальным 40% нужен резерв с серверной поддержкой — именно поэтому почти каждый «P2P файловый» продукт имеет ретранслятор в архитектуре где-то.

Безопасная отправка больших файлов со сквозным шифрованием

Передавайте файлы до 10 ГБ бесплатно со сквозным шифрованием. Регистрация не требуется. Ваши файлы шифруются в браузере перед загрузкой — никто другой не может их прочитать.

Отправить файл