点对点传输

浏览器之间直接传输文件、文件夹与文字

正在加载工具…

关于 浏览器点对点文件传输

点对点传输工具在两个浏览器之间建立一条直连的加密数据通道,把文件、整棵文件夹和文本消息通过它流式送达。载荷字节从一个浏览器直接走到另一个浏览器;本站最多只可能经手那条很短的会话建立消息,而在手动码模式下连这条都不经手。

每一次 WebRTC 会话都由两个互不相干的部分组成。会话建立本身是个先有鸡还是先有蛋的问题——两个素未谋面的浏览器必须先交换 SDP offer、answer 和一串 ICE 候选——而规范刻意没有规定该怎么交换,于是每个产品都自己发明一套信令。描述就位之后,两端就直接对话:UDP 之上是 DTLS,DTLS 之内是 SCTP,DTLS 密钥在两端之间协商,证书指纹则写在 SDP 里。本工具创建通道时设置为有序投递且不限制重传次数,因此它是一条完全可靠的有序流,语义与 TCP 一致;RTCDataChannel 的无序与部分可靠模式是给实时遥测用的,不适合必须完整抵达的字节。文件按 16 KB 分块发送,缓冲区积压到 2 MB 时开始施加背压、回落到 256 KB 时放行,避免发送端把自己的内存撑爆。

如何使用这个开发者工具

  1. 在两台设备上分别打开工具。房间码模式下,在第一台设备创建房间,把 8 位房间码、邀请链接或二维码交给第二台设备,由它输入加入。
  2. 如果完全不希望有服务器介入,优先用手动码:生成邀请码,通过你本来就信任的渠道交给对方,再把应答码粘回来,整个握手不向本站发出任何一次请求。
  3. 两台设备不在同一网络时,添加你自己的 STUN 或 TURN 服务。若不希望交换任何本地地址和反射地址,可以打开「仅中继」模式。
  4. 传输大文件前,先在接收端选好接收文件夹。没有选择时,收到的数据会缓存在浏览器内存里,上限 512 MB。
  5. 把文件或整个文件夹拖进来,或者输入一段不超过 32 KB 的文字。文件夹结构会以相对路径在接收端还原。
  6. 传输过程中留意链路读数:它会显示胜出的候选类型、协议、往返时延,以及这条路径最终是否经过了中继。

实例:一次房间码握手的完整请求序列

设备 A 创建房间,设备 B 用房间码加入,两端交换 offer、answer 和逐条下发的候选。下面就是与服务端的全部往来,其中每个 data 值都是不透明的压缩载荷,此处做了截断。

信令往来
A -> POST /api/signal   {"action":"create"}
A <- 200                {"mode":"room","room":"7KQ3M8ZP","peer":"a41f0c9e73b25d68","token":"c7f1...","peers":1,"expiresIn":600000}

B -> POST /api/signal   {"action":"join","room":"7KQ3M8ZP"}
B <- 200                {"mode":"room","room":"7KQ3M8ZP","peer":"1d77be04a9c3f512","token":"5b90...","peers":2,"expiresIn":600000}

A -> POST /api/signal   {"action":"send","room":"7KQ3M8ZP","peer":"a41f...","token":"c7f1...","data":"DFX1.eJyVVMtu2zAQ_BV..."}
A <- 200                {"delivered":1,"peers":2}

B -> POST /api/signal   {"action":"poll","room":"7KQ3M8ZP","peer":"1d77...","token":"5b90..."}
B <- 200                {"messages":[{"seq":1,"data":"DFX1.eJyVVMtu2zAQ_BV..."}],"peers":2}
载荷里究竟是什么,两端最终得到什么
解开第一条 DFX1 载荷(offer):

  {"kind":"offer","type":"offer","sdp":"v=0 ... a=fingerprint:sha-256 8C:1F:2B:...:A0
                                           a=setup:actpass
                                           a=mid:0
                                           a=sctp-port:5000
                                           a=max-message-size:262144"}

解开一条逐个下发的候选(每条消息一个):

  {"kind":"candidate","candidate":{"candidate":"candidate:842163049 1 udp 1686052863 203.0.113.44 54123 typ srflx raddr 0.0.0.0 rport 0","sdpMid":"0","sdpMLineIndex":0}}

两端最终得到:

  链路       srflx -> srflx,走 udp,往返 38 ms,是否中继:否
  数据通道   devtoys-transfer,有序、可靠、已打开
  首个文件   report-2026-q1.pdf,41.2 MB,按 16 KB 分块流式传输

信箱自始至终只经手这些 DFX1 字符串:按序转发、从不解析、两端离开即遗忘。但这些字符串里确实含有 203.0.113.44——这正是手动码模式存在的理由,留给那些连候选都不愿意经过任何服务器的人。

NAT 行为与直连能否成立

真正决定成败的是路由器如何分配外部端口。前三种行为对所有目的地共用同一个映射,所以 STUN 报告的那个地址正是对端真正可用的地址;最后一种不是,这时中继就不再是可选项了。

NAT 行为外部端口映射谁可以发起入站能否直连
完全锥形(端点无关映射)每个内部套接字一个外部端口,对所有目的地复用映射存在后任何主机都可以可以,而且是最轻松的一种情况
地址限制锥形对所有目的地使用同一外部端口只有你已经发过包的主机可以——两端的 ICE 连通性检查会互相打开许可
端口限制锥形对所有目的地使用同一外部端口只有你发过包的那个精确地址和端口可以,前提是两端都主动探测。这是家用路由器最常见的情形
对称型(地址与端口相关映射)每换一个目的地就分配一个新的外部端口只有你发过包的那个精确地址和端口不行。STUN 看到的端口不是对端将看到的端口,srflx 候选因此毫无用处,必须上 TURN
运营商级 NAT 或双层 NAT外层通常是对称型,而且不在你的掌控之中基本上没有人通常不行。移动数据网络和部分运营商光纤属于这一类,请直接按需要中继来规划

常见问题排查

大文件传输开头很快,接收端标签页随后卡住,最后报内存上限。

成因 没有选择接收文件夹时,收到的分块会堆积在浏览器内存里。会话把上限设在 512 MB,到达后主动中止该文件,而不是任由操作系统杀掉标签页、把已传的部分一起丢掉。

解决办法 开始之前先选好接收文件夹。文件系统访问的目录选择器让工具把每个分块直接写入磁盘,上限于是从内存变成可用磁盘空间。目前只有 Chromium 系浏览器提供这个选择器;在 Firefox 或 Safari 上,超大文件请先切分,或改用桌面工具。

两端都进了房间,通道却始终打不开,诊断在 12 秒后提示已停滞。

成因 典型原因是两端都在对称 NAT 后面,或者网络丢弃 UDP:只配了 STUN 时,没有任何候选对能通过连通性检查。浏览器可能在 checking 状态里停留很久才肯承认失败,所以工具在 12 秒时就判定为停滞,而不是让转圈无限继续。

解决办法 在两台设备上都添加带凭据的 TURN 服务——本站不运行中继,请自建 coturn 或使用商业服务——并先在 WebRTC 检测页确认它确实能发放 relay 候选,再回来重试。

对方刚创建的房间码,这边却提示房间不存在。

成因 两种截然不同的情况表现完全一样。信箱是进程内存状态,前端部署多个实例时,两位成员可能落到不同的进程上;另一种情况是房间已经空闲超过 10 分钟被释放了。

解决办法 部署侧需要为 /api/signal 配置会话粘滞(按客户端 IP 或 Cookie 哈希),并让反向代理允许该路径的长轮询挂起约 25 秒、同时关闭响应缓冲。作为使用者,可以重新创建一个房间,或者直接切到手动码模式,那条路径根本不碰服务器。

在公司网络里怎么都连不上,连配了 TURN 也不行。

成因 UDP 被整体丢弃,对 turn:host:3478 的分配永远完不成,host 与反射路径同样走不通。

解决办法 改用 443 端口的 turns: 地址并加上 ?transport=tcp。在多数中间设备看来,443 上的 TURN over TLS 与普通 HTTPS 没有区别。但要有心理准备:吞吐会明显下降,因为每个字节都要被中继、封帧,外面还套着 DTLS 加密。

手动邀请码被判为无效,或者长得根本贴不进任何地方。

成因 手动模式会等待 ICE 采集结束,把完整 SDP 打包进一个码里,所以网卡多、又带 relay 候选的机器会产出很长的字符串。聊天软件会折行、缩略甚至截断它,而被截断的码在解压那一步就会失败。

解决办法 用能逐字节保留内容的方式传递:文件、剪贴板同步、扫二维码都可以。工具只在大约 1800 字符以内才提供二维码,这本身就是一个信号:超过这个长度,这个码已经不适合走聊天软件了。

确认是直连,速率却远低于链路带宽。

成因 数据按 16 KB 分块发送,SCTP 在缓冲 2 MB 时施加背压,发送端要逐片从磁盘读取,每个分块还要经过 DTLS 加密。如果走的是中继路径,每个字节还要多绕 TURN 服务器一程。

解决办法 先看链路读数:显示经过中继时,上限取决于你的 TURN 服务器带宽,而不是本地链路。同一局域网内,可以把公共 STUN 条目去掉,让 host 候选尽快胜出,把路径留在本地。

适合用这个工具的场景

  • 在同一网络下的两台机器之间搬运大文件,不用 U 盘,也不必先上传到任何地方。
  • 给正在对话的同事发一整棵目录树,前提是两边此刻都在线。
  • 从一台能开浏览器、却装不了软件也挂不了外部存储的受管控机器上把文件取出来。
  • 用实测看清自己的 NAT 处境:链路读数会直白地告诉你最终走的是直连还是中继。

应该换用别的工具的场景

  • 异步投递。两个浏览器必须同时在线,如果对方要等到明天才来取,请改用托管的文件服务,或用对象存储加签名链接。
  • 受监管或需要留痕的传输。这里没有回执、没有留存、没有病毒扫描、也没有访问日志;需要证明「发了什么」时,请用托管型安全传输产品。
  • 把一个文件分发给多个接收者。一个房间只容纳两名成员,多人分发请用文件服务,或者对每个对端各传一次。

隐私与数据处理

房间码模式下,两个浏览器通过同源的 POST /api/signal 信箱交换会话描述。这个信箱只把房间保存在 SSR 进程内存中:每个房间最多两名成员,单条消息最多 96 KB,双方离开或空闲 10 分钟后释放;每个访问 IP 每天最多创建 100 个房间,每分钟最多 600 次信令请求。它不落盘,也从不解析载荷——但这句话的边界需要说准。请求会像任何 HTTP 请求一样携带你的 IP,房间码对服务端可见,而它转发的那串不透明数据是压缩后的 SDP,里面含有你的 ICE 候选(也就是 IP 地址)和 DTLS 指纹。文件与消息的字节从不进入其中。手动码模式则完全不联系服务器:邀请码和应答码是经过 deflate 压缩、Base64url 编码的 SDP,由你自己带过去。ICE 服务地址与凭据只留在这个浏览器里。这是一个诊断级别的点对点工具,不是托管型安全传输产品:DTLS 保护通道,但这里没有人替你保存回执、审计日志或任何副本。

相关开发者工具

常见问题

我的文件会经过你们的服务器吗?

不会。文件、文件夹和文本都走两个浏览器之间的 WebRTC 数据通道直传,从不抵达本站。房间码模式下,服务端只中转那几条很短的会话建立字符串,转发时不解析内容,且最多在内存里保留 10 分钟空闲时间。手动码模式则根本不会联系服务器。

有文件大小限制吗?

协议层面没有限制,因为传输是按 16 KB 分块流式进行的,不需要把整个文件读进内存。真正的边界在接收端:选了接收文件夹,上限就是可用磁盘空间;跳过这一步,数据会缓存在浏览器内存中,硬上限为 512 MB。文本消息单条限制为 32 KB。

什么情况下才需要 TURN?

STUN 只能告诉两端各自的公网映射,只要有一端能被对方穿透 NAT 触达就够用了。当两端都在对称 NAT 或运营商级 NAT 后面,或者网络丢弃 UDP 时,没有任何直连候选对能通过,会话就必须由 TURN 中继承载。本站不托管中继,所以请配置自己的服务,中继链路始终由你掌控。

手机和笔记本之间能用吗?

可以,二维码正是为此准备的:用手机扫一下就能进入房间,不必手敲房间码。在同一个 Wi-Fi 下通常是直连,速度也快。走移动数据时要预料到运营商级 NAT,那种情况下往往必须配置 TURN 才能建立连接。

没有外网、只有局域网时能用吗?

部分可以。两台设备首先都要把页面加载出来,这一步需要访问本站,所以如果外网即将断开,请让标签页保持打开。此后手动码可以在不经过任何服务器的情况下完成握手,mDNS 主机候选也能在本地子网内通过多播 DNS 解析,传输本身完全跑在局域网内。房间码模式依赖本站的信令接口,离线状态下无法使用。

这真的算端到端加密吗?

数据通道由 DTLS 保护,密钥在两个浏览器之间直接协商,证书指纹写在 SDP 里交换。但房间码模式下有一点要说清楚:指纹是由信箱转发的,一个会篡改 offer 的信令服务器在理论上可以把自己插进中间。用你本来就信任的渠道传手动码,可以彻底排除这种可能。

房间码能存活多久?

房间保存在服务端进程内存中,空闲 10 分钟后过期,两名成员都离开则立即释放。连接期间它会一直保留,以便任一方在停滞后用同一个房间码触发重连。房间码是 8 位,取自一套 32 个字符的字母表,其中剔除了 0、O、1、I 这类易混淆字符。