WebRTC Prześlij plików: Browser-to-Browser Poradnik
Prześlij pliki directly between browsers using WebRTC. Data channels, signaling, i peer connection setup dla real-time plik sharing.
WebRTC przenosi pliki bezpośrednio między dwoma przeglądarkami przez RTCDataChannel bez serwera siedzącego w ścieżce danych po początkowym handshake — co CSIRT NASK wymienia jako jeden z wzorców prywatnych transferów, gdzie żaden pośrednik nie widzi zawartości. Potrzebne elementy: kanał sygnalizacyjny (WebSocket lub dowolny mały przekaźnik) do wymiany ofert SDP i kandydatów ICE, serwery STUN do odkrywania NAT, fallback TURN dla symetrycznych NAT i kanał danych skonfigurowany dla niezawodnego uporządkowanego dostarczania. Gdy połączenie peer jest gotowe, wysyłasz channel.send() fragmenty po 16 KB–256 KB i patrzysz jak bajty płyną przez skojarzenie SCTP zaszyfrowane przez DTLS 1.2 z prędkością bliską prędkości sieci.
Jak WebRTC faktycznie dostarcza Ci peer-to-peer
WebRTC to nie magia — to ICE plus SDP plus DTLS plus SCTP ułożone razem. Nadawca tworzy RTCPeerConnection, otwiera kanał danych, generuje ofertę SDP i wysyła ją do odbiorcy przez twój kanał sygnalizacyjny. Odbiorca odpowiada. Obie strony wymieniają następnie kandydatów ICE (lokalny IP, refleksywny IP przez STUN, przekaźnikowy IP przez TURN) dopóki nie znajdą działającej ścieżki. DTLS 1.2 wykonuje handshake end-to-end, SCTP jedzie na wierzchu dla niezawodnego strumieniowania i twoje bajty zaczynają płynąć.
Szyfrowanie jest obowiązkowe i wbudowane. Nie możesz z niego zrezygnować. To znacząca wygrana bezpieczeństwa — w przeciwieństwie do WebSockets, nie musisz pamiętać o owijaniu czegokolwiek w kryptografię na poziomie aplikacji, żeby chronić przed podsłuchiwaczami sieciowymi. Dla prawdziwego szyfrowania end-to-end przeciwko własnemu serwerowi sygnalizacyjnemu, nałóż drugą rundę AES-256-GCM na wierzchu, bo złośliwy serwer sygnalizacyjny mógłby podmienić swój własny certyfikat DTLS.
Sygnalizacja: część, której WebRTC nie definiuje
WebRTC celowo pozostawia sygnalizację tobie. WebSocket na twoim serwerze jest w porządku; podobnie jest ze współdzielonym kodem pokoju opublikowanym do bazy Firebase realtime, lub nawet ręcznie wklejanymi ciągami SDP. Ważne jest, żeby oba węzły w końcu wymieniły ofertę, odpowiedź i strumień kandydatów ICE.
Minimalny serwer sygnalizacyjny w Node:
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);
}
}
});
});
Poniżej 20 linii. Serwer nigdy nie widzi bajtów pliku — tylko SDP i metadane ICE. Możesz hostować to na VPS za 5 dolarów lub Cloudflare Workers i obsługiwać setki jednoczesnych transferów.
Ustanawianie połączenia peer
Utwórz połączenie z publicznym STUN Google plus fallback TURN:
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'turn:turn.example.com:3478',
username: 'user', credential: 'pass' }
]
});
Około 15–25% połączeń mieszkaniowych siedzi za symetrycznymi NAT, których STUN nie może przedziurawić, więc TURN nie jest opcjonalny dla usługi produkcyjnej. Uruchom coturn na VPS z wystarczającą przepustowością dla przekazywanego ruchu lub zapłać za zarządzanego dostawcę TURN jak Xirsys lub Twilio.
Otwórz kanał danych po stronie oferty przed tworzeniem oferty:
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 + nieograniczone retransmisje dają niezawodność równoważną TCP. Tryb nieuporządkowany jest szybszy, ale potrzebuje ponownego składania na poziomie aplikacji.
Fragmentowanie pliku dla kanału
SCTP ma praktyczny limit wiadomości 256 KB, a starsze przeglądarki mają problem powyżej 16 KB. Bezpieczne domyślne to fragmenty 16 KB odczytywane z pliku przez 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();
Znak wodny bufferedAmount zapobiega kolejkowaniu gigabajtów do bufora nadawania SCTP i wyczerpaniu pamięci. Gdy bufor spadnie poniżej 1 MB, uzupełnij do 4 MB. To daje przepustowość blisko 40–80 MB/s w sieciach lokalnych i 5–20 MB/s na typowym połączeniu mieszkaniowym.
Odbieranie i zapisywanie bajtów na dysk
Po stronie odpowiadającego słuchaj na przychodzącym kanale:
pc.ondatachannel = ({ channel }) => {
const chunks = [];
let received = 0;
channel.onmessage = ({ data }) => {
chunks.push(data);
received += data.byteLength;
updateProgress(received);
if (received === expectedSize) finish(chunks);
};
};
Dla plików większych niż 500 MB nie gromadź w RAM. Używaj File System Access API do strumieniowania bezpośrednio na dysk:
const handle = await window.showSaveFilePicker({ suggestedName: fileName });
const writable = await handle.createWritable();
channel.onmessage = async ({ data }) => writable.write(data);
Firefox i Safari nie obsługują jeszcze showSaveFilePicker, więc wróć do Blob + URL.createObjectURL pobierania dla tych przeglądarek, ograniczonego do 2 GB.
Wysyłanie metadanych pliku przed bajtami
Odbiorca potrzebuje znać nazwę pliku, rozmiar i typ MIME przed rozpoczęciem strumienia bajtów. Użyj małego handshake JSON na kanale danych:
channel.send(JSON.stringify({
type: 'metadata', name: file.name,
size: file.size, mime: file.type, sha256: fileHash
}));
Następnie przełącz się na tryb binarny. Odbiorca przełącza się na podstawie tego, czy data to string czy ArrayBuffer. Uwzględnij SHA-256 pliku dla weryfikacji integralności po transferze i opcjonalnie odcisk klucza jeśli nakładasz aplikacyjne AES-256-GCM na wierzchu.
Radzenie sobie z błędami połączenia
Kanały danych WebRTC zawodzą na trzy sposoby: ICE nigdy nie kończy (NAT, blokowanie zapory), handshake DTLS zawodzi (odchylenie zegara, problemy certyfikatu) lub połączenie zrywa się w trakcie transferu (uśpienie laptopa, zmiana sieci). Słuchaj na pc.oniceconnectionstatechange i działaj na 'failed' lub 'disconnected'.
Przy błędzie w połowie transferu restartuj ICE bez przebudowywania całego połączenia:
await pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
// ponownie wyślij przez sygnalizację
Jeśli restart zawiedzie, wróć do wznawiania uploadu przez twój serwer — hybrydowa architektura P2P + serwer. Niektóre narzędzia transferu WebRTC jak Wormhole i justbeamit używają tego wzorca, bo obsługuje 15% warunków sieciowych, gdzie czyste P2P po prostu nie działa.
Dodawanie szyfrowania end-to-end przeciwko własnemu serwerowi
Wbudowany DTLS WebRTC chroni przed atakującymi sieciowymi, ale nie przed złośliwym lub skompromitowanym serwerem sygnalizacyjnym. Dla prawdziwego E2EE obie strony generują parę kluczy ECDH P-256, wymieniają klucze publiczne przez krótki kod poza pasmem (QR lub fraza 6-słowna), wyprowadzają wspólny sekret przez HKDF-SHA256 i szyfrują każdą wiadomość kanału danych przez AES-256-GCM przed wywołaniem send. Nawet jeśli serwer sygnalizacyjny podmieni certyfikaty DTLS, nie może odczytać twoich plików.
HexaTransfer używa hybrydowej architektury — szyfrogram przechowywany po stronie serwera z AES-256-GCM po stronie klienta, zamiast P2P — handlując bezpośredniością na rzecz asynchronicznego udostępniania offline. Wypróbuj na https://hexatransfer.com — za darmo, bez konta, maks. 10 GB.
Gdzie WebRTC wygrywa i przegrywa
Transfer plików przez WebRTC świeci, gdy oba węzły są jednocześnie online, gdy prywatność przed własnym serwerem ma znaczenie i gdy pliki są wystarczająco duże (100 MB+), że koszty przepustowości przekaźnika byłyby bolesne. Przegrywa gdy użytkownicy chcą wysłać i odejść, gdy odbiorcy otwierają link godziny później lub gdy odbiorcy są w restrykcyjnych sieciach firmowych blokujących STUN i TURN. Dla ogólnego narzędzia transferu, czyste P2P wygodnie pokrywa może 60% przypadków użycia. Pozostałe 40% potrzebuje fallbacku opartego na serwerze — dlatego prawie każdy produkt "P2P transfer plików" ma gdzieś przekaźnik w architekturze.
Wysyłaj duże pliki bezpiecznie z szyfrowaniem end-to-end
Przesyłaj pliki do 10 GB za darmo z szyfrowaniem end-to-end. Bez rejestracji. Twoje pliki są szyfrowane w przeglądarce przed przesłaniem — nikt inny nie może ich odczytać.
Wyślij plik