实现细节
浏览器里的点对点文件传输是怎么做的
每个浏览器里都已经装着一套能穿透 NAT、带拥塞控制、自带加密的传输层:WebRTC 数据通道。缺的只是让两个陌生人找到彼此的办法 —— 这就是这里的服务器唯一的工作,干完之后它就彻底退出数据路径。
一句话回答
浏览器之间的点对点文件传输是怎么实现的?
信令服务器先让两个浏览器互相认识,交换连接描述和候选地址;双方随后穿透各自的 NAT,建立一条直连的 WebRTC 数据通道。文件被切块、压缩、加密后从这条通道推过去 —— 服务器一个字节都看不到。
自己跑一遍看看
将连接码提供给对方,对方输入后即可建立连接
想看协商过程的话,传输时把控制台打开。
怎么用
- 1
会合
一端申请 6 位连接码,房间以 15 分钟 TTL 存在 Redis 里。另一端提交连接码后,创建者会收到一条带 4 位标识的明确申请 —— 光有连接码永远开不了连接。
- 2
协商
双方通过信令通道交换 SDP offer / answer 与 ICE 候选,直到找到一条可用路径:同一局域网下的 host 候选,或者穿透两端 NAT 得到的 server-reflexive 候选。
- 3
交接
数据通道打开后,在控制通道上跑一次 ECDH P-256 交换,两端各自用 HKDF 推导出会话密钥。从这一刻起,信令和 Redis 都不再参与。
一条控制通道,四条数据通道
单条数据通道就是单条 SCTP 流,队头丢一个包,后面排队的全都卡住。所以传输跑在 4 条 unordered 但 reliable 的批量通道上:每个块自带索引,乱序到达没有意义,重传只会卡住它自己那一块。
第 5 条是有序的控制通道,走文件清单、拉取请求、暂停与取消。把控制流量从批量通道上摘出来,意味着一条清单不会排在 8 MB 待发数据后面干等。
- 块大小跟着协商出来的 sctp.maxMessageSize 自适应,从 64 KiB 起,上限 256 KiB,尽量让一次 send 填满一条消息而不是被拆片。
- 背压由 bufferedAmount 驱动:8 MB 高水位、2 MB 低水位,另外限制最多 24 个块在途。
- 每个块在加密前单独 deflate,而且只在开头几个块采样证明划算时才做 —— 视频和压缩包这类本来就压不动的数据会整体跳过。
- 然后是 AES-256-GCM,每块独立 nonce 和认证标签。正是「块自包含」这一点,才让并行通道和断点续传成为可能。
ICE,以及故意不要 TURN
ICE 会按优先级逐对尝试候选。两台设备在同一局域网时 host 候选胜出,文件根本不出这个网络;否则由 STUN 告诉各自的公网映射,两端同时打洞,得到一对直接跨越公网的 server-reflexive 候选。
标准 WebRTC 部署里还有第三条路:TURN —— 打洞失败时由中继转发流量。这里是故意不提供的。下发给客户端的 ICE 列表在服务端被过滤为只允许 stun: 开头的地址,所以中继根本配不进来,失败就是失败。
什么时候、传了什么
- 加入文件时只广播一条清单记录 —— 只有文件名和大小,文件既不打开也不读取。
- 接收端发出拉取请求,里面带上它已经持有的字节区间(来自上一次尝试、记录在 IndexedDB 里)。
- 到这一步发送端才开始按块读盘,每块在 Worker 里压缩、加密,然后才上线。
- 收到的每个块先验证认证标签,再写入 IndexedDB;它填补的区间在确认下一块之前就被记录下来。
- 区间集合完整后,组装成 blob 交给浏览器下载。认证失败会中止传输,而不是把可疑数据写进去。
数据通道本身已经有 DTLS 加密,所以再叠一层 AES-256-GCM,对于被动的网络窃听者来说是冗余的。但对信令服务器不是:正因为密钥在浏览器里推导,一个被攻破或恶意的信令路径才没法把自己插进来冒充对端。两边屏幕上那组安全校验词,就是同一把密钥在人这一侧的可验证端点。
常见问题
点对点传输为什么还需要服务器?
因为两个在 NAT 后面的浏览器没法自己发现对方,总得有人替它们转交 offer、answer 和候选地址。这里的服务器只做这件事:保存带短 TTL 的房间状态,从不接触文件数据。
为什么用 WebRTC,而不是 WebSocket 或 WebTransport?
因为 WebRTC 数据通道是浏览器里唯一能建立直连、且自带 NAT 穿透的 API。WebSocket 和 WebTransport 都终结在服务器上,那等于又把你的文件放回了别人的机器。
为什么数据通道要用 unordered?
如果要求有序,4 条并行流上又会出现队头阻塞。既然每个块都带索引、各自独立认证,线上的顺序就不携带任何信息 —— 接收端按索引重组即可。
打洞失败会怎样?
会明确报告失败和原因,并给出真正有用的办法:连到同一个网络、开手机热点、关掉 VPN。没有任何中继,所以不存在「悄悄变慢」这种退化。
断线之后能续传吗?
可以。已收到的区间存在接收端的 IndexedDB 里,下一次拉取请求会声明它们,发送端只重新读取缺口部分。
文件发送前会压缩吗?
每个块独立 deflate,但只在采样开头几个块证明划算时才压。已经压缩过的格式会跳过,不会让你白花 CPU 去换一个零头。
实际跑一次
有意思的部分大概只有两秒钟 —— 之后就只是字节在两台机器之间移动了。
开始传输