☰
用 WebRTC DataChannel 做个浏览器直传工具:SixDrop 的 6 位取件码与打洞实践
2026/10/24 4:14:18 网站建设 项目流程

先说结论

SixDrop 是一个打开网页就能用的传文件工具:两边各开一个网页,一边把 6 位取件码报给对方,对方输码,两个浏览器建立 WebRTC 直连,文件从一台设备直接流到另一台。

  • 不装客户端、不注册、不加好友
  • 直连时文件字节不经过服务器,服务器只交换 SDP/ICE 信令
  • 没有会员、没有排队、没有「先上传再下载」
  • 打不通会明确告诉你为什么,默认不会偷偷把文件传到云上(中转是可选项)

项目名是 SixDrop,名字和站点同名,前后端代码在一个仓库里,文末写了怎么自己部署一份;在线版入口放在最后一段。


为什么做这个

起因很具体:给同事传一段 2.3 GB 的录屏。

  • 微信:超过 1 GB 直接拒绝
  • 网盘:先上传 40 分钟,对方下载还要解压/会员
  • 分享链接:对方打开先弹注册框
  • 最后拿移动硬盘拷的

中间这些环节,本质都是「把文件先搬到某个中间服务器,再让对端去取」。可如果两个浏览器能直接对话,这些环节全都能省掉——WebRTC 的 DataChannel 就是干这个的。


怎么用

两种入口,按场景选:

场景操作
两台设备在同一个 Wi-Fi大厅里会直接列出对方的设备,点「申请加入」→ 对方点「同意」→ 进同一个房间
不在同一个网络一方复制链接或报 6 位取件码,另一方输码进房间
双 WAN / 多出口的网络自动发现可能对不上(出口 IP 每条连接都在变),直接用取件码最稳

进房间之后:

  1. 选文件(可多选)
  2. 对方确认,选保存目录
  3. 传完房间还在:可以再发一批,也可以切到「消息」发文字(走同一条连接)

技术栈

层选型作用
接入Cloudflare Workers路由、取件码格式校验、静态资源分发
信令Durable Objects(带 SQLite 存储 + 休眠)每个房间一个 DO,转发 SDP/ICE;空闲自动休眠,不烧额度
同网发现另一个 Durable Object(按出口 IP 分组)大厅列表、一次性邀请 token
传输WebRTC DataChannel真正的数据通道,点对点
NAT 穿透STUN(4 个公共服务器)拿 srflx 候选;不配置 TURN
兜底中转R2(可选,用户主动点)打不通时把文件放 R2,给对端下载链接
校验DTLS 指纹 + SHA-256两端显示同一个 8 位校验码,防中间人
前端原生 JS,无框架一个app.js,没有构建步骤

成本:Workers Free + Durable Objects Free 额度内,0 元。R2 只有主动用中转时才产生存储/流量。


三个关键设计

1)为什么是 6 位码,不是长链接

链接有三个实际问题:微信里点陌生链接会弹风险提示;太长,口头说不清;群里发出去谁都能点。

6 位码用的是去掉易混字符的 32 字符表(2-9 A-H J-N P-Z,没有0/1/I/O),口头报、手输都不容易错。码只在房间存活期内有效,房间用完可以换新码。

2)不装 TURN:打不通就直说

TURN 会让「连通率」好看,但代价是:所有流量都过你的服务器,免费额度撑不住,而且用户以为自己在直连。

所以这里的选择是:只做 STUN 打洞,不做 TURN。打不通就明确失败,把原因写清楚;需要兜底时,用户自己点「改用中转」,文件才会上 R2——这是一个显式动作,不是默认行为。

3)失败必须可见

失败页不是一句「网络错误」,而是给出:

  • 候选统计:host 2 / srflx 4 / relay 0,以及其中几个是 IPv6
  • 失败类型:超时、打洞失败、传输中断
  • 对端通知:两边同时知道,不会一边转圈一边干等
  • 三条后续:换网络重试 / 同码重连 / 改用中转

做了这套之后,“为什么连不上” 从玄学变成了可以对着排查的东西。


传输实现:分块、背压、写盘

数据面没什么魔法,核心就是几个常量和一条发送循环:

// 分块默认 256 KiB(?chunk=64 可调小做对比),真正发送时还会按平台 SCTP 上限收窄constCHUNK_LIMIT=256*1024;// 背压水位:缓冲超过 8 MB 就停,降到 4 MB 再继续constHIGH_WATER=8*1024*1024;constLOW_WATER=4*1024*1024;

发送循环(简化):

for(constbufofchunks){awaitwaitForDrain(dc);// bufferedAmount >= HIGH_WATER 就等 bufferedamountlowawaitsendChunk(dc,buf);// 队列满(send queue is full)时等对方接住,最长 30s}

这两个地方都踩过坑:

  • 只等背压不够:dc.send()仍可能抛send queue is full(手机、5G、双 WAN 抖动时很常见)。早期实现 2 秒就放弃,直接判整个传输失败;现在改成等bufferedamountlow、最长 30 秒,期间界面提示「网络卡了一下,正在等对方接住」。
  • 失败要分清:已经传过数据再断,标题是「传输中断」而不是「打洞失败」,诊断行也会说明「直连本来是通的」。

接收端写盘分两种:

  • Chrome / Edge:File System Access API,边收边写,内存占用恒定
  • Firefox / Safari:没有流式写盘,先收进内存再落盘,所以建议单文件 < 1 GB

校验码:两端对一下就知道有没有中间人

连接建立后,两端各自把 DTLS 指纹排序后做 SHA-256,取前 4 字节转成 8 位十六进制:

constbytes=newTextEncoder().encode([localFingerprint,remoteFingerprint].sort().join('|'));constdigest=newUint8Array(awaitcrypto.subtle.digest('SHA-256',bytes));constcode=[...digest.slice(0,4)].map((b)=>b.toString(16).padStart(2,'0')).join('').toUpperCase();

两端显示的是同一个码,说明双方看到的是同一对证书,没有中间人。这个码不是文件哈希,是连接的指纹——文件完整性由 DTLS 自己保证。


实测与测试

速度这件事我不想吹:同一个 Wi-Fi 的局域网直连、跨网络打洞、经 R2 中转,这三档完全不是一个量级,而且跨网络能不能打通还取决于两边 NAT。

能说的是工程上的验证:

  • 本地 e2e(Playwright + 本地 Edge):3 个文件共 4.4 MB,端到端 1.8–2.5 s,逐文件哈希一致
  • 房间模型 e2e:同房间连续三批、接收中发消息不抢焦点、拒绝/返回、离开换码
  • 同网发现 e2e:互相发现、申请/同意、拒绝、可见性开关
  • 单测 5 组:房间状态机、大厅、信令协议、前端结构、R2

其中专门注入过一次「发送队列满」:让发送端前 3 秒所有send都抛queue is full,验证传输能挺过去——这在真实手机上是会发生的。


局限(重要,别踩坑)

限制说明
对称 NAT / 公司网 / 校园网打洞大概率失败,没有 TURN 就是打不通;可以改用中转
双 WAN、CGNAT、VPN出口 IP 每条连接都可能变,自动发现会时有时无,用取件码
Firefox / Safari 大文件没有流式写盘,建议单文件 < 1 GB
没有断点续传中断要重传;对端在线才能传(不是离线分享)
不做账号没有历史记录,关掉页面就散
不适合同一场合给很多人发一个房间是 1 对 1,多人分发请用网盘/CDN

本地跑起来

# 先把仓库克隆到本地并进入目录,然后:nodetools/dev-server.mjs# 本地垫片,默认监听 127.0.0.1:8787nodetest/room.test.js# 单测nodetools/e2e.mjs# 端到端(需要本地装 Edge/Chromium)

部署(需要自己的 Cloudflare 账号):

npminstallnpx wrangler deploy

wrangler.jsonc里已经配好ROOMS、DROPS、LOBBY三个 Durable Object 绑定和 R2 bucket,迁移标签是 v1/v2/v3。

node tools/dev-server.mjs默认监听127.0.0.1:8787(可用PORT覆盖);它是个本地垫片,不依赖 Cloudflare 账号就能调前端和信令。


代码结构

路径内容
src/worker.js路由、房间创建、大厅/邀请、R2 中转
src/room.js房间 DO:WebSocket 信令、成员状态、knock/admit/reject
src/lobby.js同网发现 DO:条目 TTL、一次性 invite token
src/drop.jsR2 中转与分享页
public/app.js前端全部逻辑(约 2500 行,无框架)
public/index.html/style.css大厅 + 房间两个视图、两个标签页
tools/*-e2e.mjs4 套 Playwright 验收脚本

适合谁 / 不适合谁

适合:临时给同事/家人传个文件、手机照片挪电脑、跨网络发个安装包、不想注册任何账号的场景。

不适合:需要长期保存和分享链接、需要多人同时下载、需要断点续传、或者网络环境本身打不通 P2P 的场景。


最后

这个项目最初只是想解决「传个文件为什么这么麻烦」,做完之后最大的收获其实是:把「连不上」的原因讲清楚,比把速度吹快更难,也更有用。

在线版就是一个网页:sixdrop.dev,两边打开就能用,不装、不注册。

如果你在自己的网络里也遇到过打洞失败,欢迎在评论区贴一下失败页的候选统计(host / srflx / relay 各几个、有没有 IPv6)——不同的 NAT 组合,处理方式确实不一样。


需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询