Snapdrop WebRTC 深潜:信令、ICE 候选与 DataChannel 如何让 P2P 直连跑起来
【免费下载链接】snapdropA Progressive Web App for local file sharing项目地址: https://gitcode.com/gh_mirrors/sn/snapdrop
Snapdrop 是一个用于本地文件共享的渐进式 Web 应用(PWA),它借助 WebRTC 建立 P2P 直连,让同一局域网内的手机和电脑互传文件——服务器只做"传话",文件的每一个字节都不经过第三方机器。本文带你在 5 分钟里看懂这条直连链路是如何跑起来的:信令(Signaling)、ICE 候选与 DataChannel 各干了什么、文件又是如何可靠送达的。
1️⃣ 全景图:P2P 直连的三个角色
在深入细节前,先建立整体认知。Snapdrop 的一次 WebRTC 连接里,有三类"参与者":
| 角色 | 职责 | 是否接触文件内容 |
|---|---|---|
| 🔔 信令服务器(WebSocket) | 设备发现 + 中转握手消息 | ❌ 完全不碰 |
| 🧊 ICE(含 STUN) | 在 NAT 之后为双方找出可达路径 | ❌ 只有地址/端口信息 |
| 📦 DataChannel | 真正承载文件字节的加密隧道 | ✅ 端到端传输 |
一句话总结:信令负责"介绍认识",ICE 负责"找到门牌号",DataChannel 负责"搬货"。而服务器从头到尾只是个牵线人,这正是 Snapdrop 隐私设计的核心。
2️⃣ 第一步:WebSocket 信令——服务器如何知道"谁在线"
打开 Snapdrop 页面后,浏览器第一步就通过 WebSocket 连上信令服务器。在 client/scripts/network.js 的_endpoint()方法中可以看到:浏览器会根据自己是否支持 WebRTC,分别连接/webrtc或/fallback端点——这一步决定了后续走 P2P 直连还是降级链路。
服务端逻辑在 server/index.js 里非常直观:
- 按 IP 分房间:
_joinRoom()以客户端 IP 为 key 创建房间,只有同一局域网的设备互相可见,天然实现了"本地文件共享"的边界; - 互相打招呼:新设备加入时,服务器给在场设备广播
peer-joined,同时把在线名单peers推给新设备; - 牵线搭桥:当客户端发出一条带
to字段的消息时,服务器执行"删掉收件人、加上发件人"的中转操作(见 server/index.js),把消息原样递给对方。
信令消息里只有 SDP 描述、ICE 候选这类握手信息——文件内容从头到尾不会出现在这条链路上。
3️⃣ 第二步:SDP 协商——连接前的"谈条件"
当一方决定向某台设备发消息时,会创建RTCPeerConnection并发起Offer/Answer 协商,核心代码集中在 client/scripts/network.js 的RTCPeer类:
- 主叫方调用
createDataChannel('data-channel', { ordered: true, reliable: true }),随后createOffer()生成 SDP(一段描述媒体能力、编码偏好、连接策略的文本),通过信令发给对方; - 被叫方收到后
setRemoteDescription()写入远端描述,再createAnswer()回应; - 双方把对方的 SDP 写入本地,协商完成。
可以把 SDP 理解成通话前的"确认支持哪些格式"——双方都声明:我要开一条有序、可靠的数据通道。
4️⃣ 第三步:ICE 候选——穿越 NAT 的路径探测 🧊
协商解决了"谈什么",还没解决"怎么找到对方":两台设备往往藏在路由器(NAT)后面,彼此的真实内网地址对方都够不着。ICE(Interactive Connectivity Establishment)就是来干这件事的。
- 收集候选(Candidate Gathering):每个浏览器会收集多组"地址 + 端口"组合——本机网卡地址(host 候选)、经 STUN 服务器"照镜子"得到的公网映射地址(srflx 候选)等;
- 逐条上报(Trickle ICE):Snapdrop 采用"边收集边发送"的策略,
onicecandidate每产生一条候选就立刻通过信令转发给对方(见 client/scripts/network.js),对方用addIceCandidate()收下; - 打洞与优选:双方依次尝试这些候选组合,谁先连通就用谁。
关于 STUN 服务器的配置在文件末尾一目了然:client/scripts/network.js 中RTCPeer.config指定了stun:stun.l.google.com:19302这一公开 STUN 服务,且采用unified-plan语义。💡 小贴士:同一 Wi-Fi 下 host 候选通常直接命中,连接几乎是瞬时的;跨子网时则依赖 STUN 映射地址——这也是 Snapdrop 定位为"本地共享"而非全球 P2P 的原因(未配置 TURN 中继)。
5️⃣ 第四步:DataChannel——文件真正流过的加密隧道 📦
候选配对成功,DataChannel 打开。此后所有文件传输都在这条通道里进行,且由 WebRTC 自带的 DTLS 加密保护,即使信令服务器能看到通道存在,也读不懂里面的内容。
Snapdrop 在 client/scripts/network.js 用两个精巧的类保证大文件的可靠传输:
FileChunker(发送端):文件被切成64 KB小块依次发送,每凑满1 MB形成一个"分区"(partition),然后发一条partition消息;只有收到对端partition-received确认后才发下一分区——轻量级的"分段 ACK"协议,避免整包重传;FileDigester(接收端):把收到的分块拼回完整 Blob,全程在浏览器内存中完成并实时计算进度,拼齐后触发file-received事件,最终由浏览器发起下载。
配合progress消息,两端界面(client/scripts/ui.js 中的PeerUI)就能同步画出进度圆环。🎉
6️⃣ 兜底与容错:直连断了怎么办
- 降级方案:浏览器不支持 WebRTC 时,
_onPeers()会改用 WSPeer——消息改由 WebSocket 经服务器中转,功能不变(只是不再 P2P 直连),这就是端点选择/fallback的意义; - 断线重连:
ServerConnection掉线后每 5 秒自动重连;DataChannel 关闭或connectionState变为failed时,refresh()会重建连接与通道(client/scripts/network.js)。
7️⃣ 本地一键部署:亲手体验 P2P 文件传输 🐳
想亲手验证这套链路,用 Docker 最快:
git clone https://gitcode.com/gh_mirrors/sn/snapdrop cd snapdrop docker-compose up -d随后用浏览器打开http://localhost:8080,同一 Wi-Fi 下的多台设备互开页面即可互传文件。部署细节(含 PWA 证书配置)见 docs/local-dev.md,容器编排见 docker-compose.yml,nginx 反代示例见 docker/nginx/default.conf。
8️⃣ 常见疑问快答 📮
- 文件会经过服务器吗?不会。P2P 模式下服务器只中转信令,文件走 DataChannel 直达;官方 FAQ 明确"文件永远不会发到任何服务器,Snapdrop 甚至没有数据库",详见 docs/faq.md。
- 传输过程安全吗?DataChannel 由 WebRTC 在传输中加密,服务器无法窥探内容。
- 为什么两台设备必须同网段?信令房间按 IP 隔离 + 未配置 TURN,保证的是"本地共享"场景,而非任意两台设备互通。
小结:Snapdrop 用不到 500 行服务端代码和一份清晰的客户端 network.js,演示了 WebRTC 落地的最小完整形态——信令建立房间、SDP 谈妥条件、ICE 找到路径、DataChannel 加密搬货。读懂它,你就掌握了 P2P 直连的全套骨架。
【免费下载链接】snapdropA Progressive Web App for local file sharing项目地址: https://gitcode.com/gh_mirrors/sn/snapdrop
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考