最后更新·
做一个无服务器跨设备剪贴板一整年,最有意思的决定就一个:P2P 栈选谁。这是那个问题的长版回答。
过去一年我一直在做 UniClipboard —— 一个不依赖服务器的跨平台剪贴板同步工具。大部分工作其实都很无聊:操作系统之间剪贴板 API 的差异、加密人体工学的各种坑、Tauri 应用打包到三个平台的痛苦。
但有一个决定确实有意思,而且总是被人问到:为什么 P2P 层选了 iroh, 而不是看起来更显然的 libp2p、WebRTC、syncthing 的 BEP,或者 magic-wormhole?
这篇文章是那个回答的长版本。如果你也在做需要两台终端设备直接对话——跨越互联网、 穿过 NAT、还不想自己跑基础设施——的事情,这里的取舍大概率也适用于你的项目。
一个剪贴板同步工具对网络层有四个要求:
我不需要的:
最后一条悄悄判了 WebRTC 死刑,这个稍后再说。
最显然的选择。libp2p 成熟、有 Rust 实现(rust-libp2p)、 驱动着 IPFS 和加密生态的一大块,而且明确把自己定位成"P2P 工具箱"。
我先用 libp2p 做了原型。能跑。但 libp2p 的形状对一个小应用而言是错的:
// 一个 libp2p hello-world 大致长这样
let local_key = identity::Keypair::generate_ed25519();
let local_peer_id = PeerId::from(local_key.public());
let transport = tcp::tokio::Transport::default()
.upgrade(Version::V1)
.authenticate(noise::Config::new(&local_key)?)
.multiplex(yamux::Config::default())
.boxed();
let behaviour = MyBehaviour {
kademlia: kad::Behaviour::new(local_peer_id, MemoryStore::new(local_peer_id)),
identify: identify::Behaviour::new(...),
ping: ping::Behaviour::new(...),
relay_client: relay::client::Behaviour::new(local_peer_id, relay_client_transport),
dcutr: dcutr::Behaviour::new(local_peer_id),
};
let mut swarm = SwarmBuilder::with_existing_identity(local_key)
.with_tokio()
.with_other_transport(|key| ...)
.with_behaviour(|_| behaviour)?
.build();
// ……到这里,你才终于可以开始写应用代码
这正是 libp2p 设计的样子——让你自由组装一个传输层、一个安全通道、一个多路复用器、 一个 peer 识别协议、一个用于路由的 DHT、一个用于 NAT 穿透的 relay client、一个 hole-punching behaviour。可组合性是它的卖点。
但对一个剪贴板应用而言,我是在发出第一个字节之前先组装并配置一个小型操作系统。 每一块都有自己的版本兼容性、自己的 bug、自己的配置旋钮。上面八个模块里,有三个跟 NAT hole-punching 之间有非显然的相互作用。两周后我有一个大体能跑的 libp2p 原型, 后面跟着一长串我没法自信调试的边角 case。
根本性的错配是:libp2p 是一个用来构建协议的框架。我需要的是一个使用某个协议的 SDK。
WebRTC 是浏览器 P2P 的显然选择,而且也有完全实打实的桌面实现 (webrtc-rs、对 libwebrtc 的绑定)。
排除它的理由:
WebRTC 是"浏览器之间视频聊天"的对的工具,是"两个想要长连接加密通道的桌面应用"的错的工具。
Syncthing 实际上做了我需要的大部分事情——已知设备之间的加密 P2P 同步、通过他们的 relay 网络做 NAT 穿透、成熟的代码库。我真花了一个周末试着把 syncthing 当后端用。
它在一个具体且发人深省的方向上,形状不对。
Syncthing 优化的是文件夹状态的最终一致性。最小循环大概是这样:
这是"让两个音乐库保持同步"的好架构,是"我刚复制了一段字符串,我希望在我切窗口之前
它已经到了另一台笔记本上"的烂架构。即便把 fsScanIntervalS 调到很低,这个 round-trip
还是好几秒。
而且,BEP 是一个文件夹同步协议——没有"对一个 peer 开一条全双工通道"的库版本,只有 "把这个文件夹和那个 peer 同步"。把它改造来传短消息,意味着把每个剪贴板条目塞进一个 临时文件再等同步。当 hackathon 凑合可以,当地基就糟糕了。
magic-wormhole 概念上很美:
人类可发音的码(7-crossover-clockwork)、基于 PAKE 的认证密钥协商、不要账号、
用户也不需要去想任何基础设施。我很喜欢它。
但它是一个一次性协议。你生成一个码,对面输入,内容传完,通道关闭。它没有 "这两台设备有持续关系并希望无限期来回收发消息"的概念。
能不能在 magic-wormhole 之上构建那种东西?能——从一次 wormhole 交换里推导出一个长期共享密钥, 用它在……什么之上 bootstrap 一个 Noise 通道。但到那一步,你已经在重新造 iroh 已经提供的那一层, 只不过造在一个不是为这个用途设计的原语之上。
iroh 是 Number Zero(里面有早期 IPFS 的一些人)做的,目标明确就是一个用于 P2P 的 Rust SDK,而不是框架。光从 API 的形状你就 能看出它的哲学:
use iroh::{Endpoint, SecretKey};
const ALPN: &[u8] = b"uniclipboard/0";
// 绑定一个 P2P endpoint
let endpoint = Endpoint::builder()
.secret_key(secret_key)
.alpns(vec![ALPN.to_vec()])
.discovery_n0() // 可选:用 n0 的发现服务
.bind()
.await?;
// 连一个 peer(跨互联网,NAT 穿透在内部已经处理好)
let conn = endpoint.connect(peer_node_addr, ALPN).await?;
let (mut send, mut recv) = conn.open_bi().await?;
send.write_all(b"hello from device A").await?;
send.finish()?;
let response = recv.read_to_end(1024).await?;
整套搭建就这些。背后:
iroh-net 里,组合了 STUN 类的反射地址发现和一个
DERP 风格的 relay 系统作为 fallback。Connection 对象。对 UniClipboard 而言,这意味着什么:
诚实的回答是:有可能。iroh 比 libp2p 年轻,团队更小,break change 更多。我在 0.x 期间已经 做过两次非平凡的迁移。
我下注的是:我所依赖的 API 表面(Endpoint、Connection、SecretKey、NodeAddr)
是小且稳定的。只要这些原语继续工作,我就能吸收下层的变动。到目前为止这个赌注成立。
万一 iroh 哪天不再被维护的应急方案:底下那些原语(QUIC + DERP 风格的 relay + STUN 风格的 hole-punching)都是标准件。在 raw Quinn 之上重新实现这套 SDK 形状会很不爽,但是有界的—— 大概 2–3 周的工作量。考虑到生产力收益,这是个可接受的风险。
任何 P2P 帖子在 HN 上都会收到的常见回复是:"为什么不直接放到 Tailscale 上?"
Tailscale 很棒,我自己也在用。但是把它做成 UniClipboard 的硬依赖意味着:
对已经在跑 Tailscale 的用户来说,确实——他们大概可以用 tailscale serve + 一个小 daemon
合成出 UniClipboard 80% 的功能。选择不强制要 Tailscale 是有意为之的:卖点是"装到两台机器上,
就能用,不用在任何地方有账号"。
如果你在 2026 年挑选一个 Rust P2P 栈,我会这样想这些选项:
| 库 | 适合 | 不适合 |
|---|---|---|
| iroh | 桌面应用、2-N 设备的小网格、"我想 dial 一个 peer" | 你需要浏览器支持、需要最大化的协议灵活性 |
| libp2p | 自定义协议、大型 peer 网络、IPFS 周边 | 小应用,你不需要 80% 的可配置性 |
| WebRTC | 浏览器 P2P、视频/音频 | 纯桌面、小载荷、不要多媒体 |
| syncthing/BEP | 文件夹同步 | 任何不是文件夹同步的事情 |
| magic-wormhole | 一次性传输、人类可读的码 | 长连接通道 |
| Tailscale + 自研 | 你本来就要求 Tailscale | 你想要零账号 |
具体到 UniClipboard——小载荷、持久的设备配对、不要基础设施、纯桌面——iroh 是干净的契合。 一年下来,我还会做同样的选择。
UniClipboard 是一款端到端加密、P2P 的剪贴板同步工具,支持 macOS、Windows 和 Linux。 开源协议 AGPL-3.0。没有账号,没有服务器(只有 relay fallback,而且只看得到密文)。
如果这篇文章对你有用,整个项目会感激你给个 star,但更有用的是一份 bug report、 一份打包贡献,或者对上面任何一个技术决定的有理有据的反对意见。