WebRTC 文件传输: Browser-to-Browser 教程
使用WebRTC在浏览器之间直接传输文件。数据通道、信令与点对点连接配置,实现实时文件共享。
WebRTC 文件传输通过 RTCDataChannel 在两个浏览器之间直接传输字节,初次握手完成后数据路径中没有服务器。你需要的组件:用于交换 SDP 提议和 ICE 候选地址的信令通道(WebSocket 或任何小型中继),用于 NAT 发现的 STUN 服务器,对称 NAT 的 TURN 回退,以及配置为可靠有序传输的数据通道。点对点连接建立后,你调用 channel.send() 发送16KB-256KB的分块,字节在 DTLS 1.2 加密的 SCTP 关联上以接近网线速度流动。
WebRTC 如何真正实现点对点
WebRTC 不是魔法——它是 ICE 加 SDP 加 DTLS 加 SCTP 的堆叠。发送方创建 RTCPeerConnection,打开数据通道,生成 SDP 提议,通过信令通道发给接收方。接收方应答。双方然后交换 ICE 候选地址(本地 IP、通过 STUN 的反射 IP、通过 TURN 的中继 IP),直到找到可用路径。DTLS 1.2 端到端握手,SCTP 在其上提供可靠流式传输,字节开始流动。
加密是强制内置的,无法关闭。这是显著的安全优势——不像 WebSocket,你不需要记得在应用层再裹一层加密来对抗网络窃听者。对于针对你自己信令服务器的真正 E2EE,需要在上层叠加第二轮 AES-256-GCM,因为恶意信令服务器可能替换其 DTLS 证书。
信令:WebRTC 没有定义的那部分
WebRTC 故意将信令留给你来实现。服务器上的 WebSocket 没问题;共享房间码发布到 Firebase 实时数据库也行;甚至手动粘贴 SDP 字符串也可以。重要的是两个节点最终交换了一个提议、一个应答和一系列 ICE 候选地址。
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);
}
}
});
});
不到20行。服务器从不接触文件字节——只处理 SDP 和 ICE 元数据。可以托管在每月35元的 VPS 或 Cloudflare Workers 上,支持数百个并发传输。
建立点对点连接
使用 Google 公共 STUN 加 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%的家用连接位于 STUN 无法穿透的对称 NAT 之后,因此 TURN 对于生产服务不是可选的。在 VPS 上运行 coturn 并配备足够带宽处理中继流量,或使用 Xirsys 或 Twilio 等托管 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 });
有序加无限重传提供 TCP 级别的可靠性。无序模式更快,但需要应用层重组。
为数据通道分块文件
SCTP 的实际限制是每消息256KB,旧版浏览器超过16KB就会有问题。通过 File.slice() 读取文件并使用16KB分块作为安全默认:
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 水位线防止你将 GB 级数据塞入 SCTP 发送缓冲区耗尽内存。缓冲区降至1MB以下时,补充到4MB。这在局域网上提供接近40-80MB/s的吞吐量,在典型家用宽带上为5-20MB/s。
接收字节并写入磁盘
在应答方监听传入通道:
pc.ondatachannel = ({ channel }) => {
const chunks = [];
let received = 0;
channel.onmessage = ({ data }) => {
chunks.push(data);
received += data.byteLength;
updateProgress(received);
if (received === expectedSize) finish(chunks);
};
};
对于超过500MB的文件,不要在内存中积累。使用 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 下载,上限为2GB。
在字节流之前发送文件元数据
接收方在字节流开始前需要知道文件名、大小和 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 在进入 'failed' 前保持 'disconnected' 几秒;Safari 没那么有耐心。
传输中途失败时,无需重建整个连接即可重启 ICE:
await pc.restartIce();
const offer = await pc.createOffer({ iceRestart: true });
// 通过信令重新发送
重启失败时,回退到通过服务器的断点续传上传——混合 P2P + 服务器架构。Wormhole 和 justbeamit 等 WebRTC 传输工具使用这种模式,因为它能处理纯 P2P 完全无法工作的15%网络条件。
针对自己服务器的端到端加密
WebRTC 内置的 DTLS 防御网络攻击者,但不防御恶意或被攻陷的信令服务器。对于真正的 E2EE,让双方各生成一个 ECDH P-256 密钥对,通过带外短码(二维码或6个单词的密码短语)交换公钥,用 HKDF-SHA256 派生共享密钥,并在调用 send 前用 AES-256-GCM 加密每条数据通道消息。这样即使信令服务器替换了 DTLS 证书,也无法读取你的文件。
HexaTransfer 使用混合架构——密文存储在服务端,客户端 AES-256-GCM 加密——用直接性换取支持离线共享的能力。免费试用 hexatransfer.com——无需注册,单次最大10GB。
WebRTC 的优势与劣势
WebRTC 文件传输在两个节点同时在线、对自己服务器的隐私很重要,以及文件足够大(100MB以上)使中继带宽成本成为痛点时表现出色。它在用户想要发送后离线、接收方数小时后才打开链接,或接收方位于阻断 STUN 和 TURN 的企业限制网络时表现较差。对于通用传输工具,纯 P2P 能舒适地覆盖约60%的使用场景。其余40%需要服务器支持的回退——这就是为什么几乎每个"P2P文件传输"产品的架构中都有中继节点的原因。