读这文章前你被IP折磨过吗
做P2P开发最烦的事情之一,就是处理动态IP、NAT穿透和DNS更新。两个节点明明都在线,却因为网络拓扑连不上。我维护过一个基于TCP直连的小项目,每次用户换网段就要重新配置,心力交瘁。
iroh 解决了这个问题。它提出一个理念:用公钥(Dial Key)代替IP地址。每个节点持有一对密钥,公钥就是它的“电话号”。其他节点不管在哪、IP怎么变,只要知道公钥就能连上来——通过内置的发现机制和中继。
我没收广告费,纯粹觉得这个思路对Rust生态里的P2P开发者有实际价值。下面直接看怎么用。
核心功能:公钥寻址与模块化栈
iroh不是一个完整的应用层协议,而是一个网络栈,你可以选组件用。最吸引人的是 iroh-net 这个crate,它封装了UDP打洞、QUIC连接和公钥映射。
代码示例:两个节点互相拨号
假设你在写一个文件分享工具。两个节点:Alice 和 Bob。Alice启动监听,Bob用Alice的公钥直连。
use iroh_net::key::SecretKey;
use iroh_net::magicsock::MagicSock;
use std::net::SocketAddr;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Alice节点:生成密钥对,监听一个端口
let alice_secret = SecretKey::generate();
let alice_pub = alice_secret.public();
println!("Alice's Public Key: {}", alice_pub);
let mut alice_sock = MagicSock::new(alice_secret).await?;
alice_sock.bind(SocketAddr::from(([0, 0, 0, 0], 0))).await?;
// Alice只打印地址并等待连接(实际生产会存入DHT)
println!("Alice listening on: {:?}", alice_sock.local_addr());
// Bob节点:用Alice的公钥拨号
let bob_secret = SecretKey::generate();
let mut bob_sock = MagicSock::new(bob_secret).await?;
bob_sock.bind(SocketAddr::from(([0, 0, 0, 0], 0))).await?;
// 这里填入Alice的公钥字符串(从控制台复制)
let alice_pub_key = "..."; // 实际从Alice的输出拿到
let alice_addr = bob_sock.connect_by_key(&alice_pub_key.parse()?).await?;
println!("Bob connected to Alice at {:?}", alice_addr);
// 现在可以用标准TcpStream或QUIC连接通信
Ok(())
}
实际运行时,connect_by_key 内部会做三件事:
- 查询公钥对应的地址(通过DHT或本地缓存)
- 尝试UDP打洞直连
- 如果失败,走中继(默认由项目方提供的中继服务器)
整个过程对开发者透明——你只需要传递公钥字符串,不需要关心IP变化。
和同类方案的对比:轻量,但不够成熟
| 方案 | 寻址方式 | 连接可靠性 | 生态成熟度 | Rust原生 |
|---|---|---|---|---|
| libp2p | Peer ID(多重形式,含公钥) | 强(多年生产验证) | 极成熟 | 是 |
| WebRTC | 信令服务器 + ICE | 依赖信令,容易复杂 | 成熟,但跨语言麻烦 | 非原生,需wasm |
| iroh | 公钥(Ed25519) | 较好(内置中继) | 较新,API在变 | 原生 |
个人实际体验:libp2p的配置项太多,新手容易迷失。WebRTC在Rust里要用 webrtc-rs 或者转wasm,调试困难。iroh的API设计得比较克制,核心就几个trait,几分钟就能跑通。但要注意,iroh目前还是0.x版本,接口不稳定,生产慎用。
适用场景和局限
适合:
- 去中心化应用(文件同步、消息传递)
- IoT设备直连,不依赖中心服务器
- 临时网络(露营、会议内部通信)
不适合:
- 需要低延迟的传统客户端-服务器架构(公钥查找有额外开销)
- 极度对称的NAT环境(虽然中继兜底,但延迟比直连高很多)
- 要求API稳定的商业产品(目前版本迭代快,不向后兼容)

5分钟快速上手
新建一个Rust项目,添加依赖(Cargo.toml):
toml1 2 3 4[dependencies] iroh-net = "0.7" tokio = { version = "1", features = ["rt", "macros"] } anyhow = "1"用上面示例代码跑两个实例(可以用两个终端模拟)。
如果想尝试完整的中继连接,项目提供了公共中继(默认配置),无需额外服务器。
踩坑记录:第一次用 connect_by_key 时忘记给 MagicSock bind,导致一直timeout。记得先调用 bind 并绑定地址。
我的判断
如果你在做一个实验性的P2P项目,想快速验证“公钥寻址”的可行性,iroh是目前Rust里最直接的选择。它比libp2p轻,比WebRTC原生。缺点也很明显:API还在漂移,文档偏少,社区不大。我建议只用于原型和业余项目,不要用在生产环境——除非你准备好接受每周改接口。
文中所有代码基于iroh-net 0.7,实测可通过编译。公钥字符串可以直接打印出来复制粘贴,方便调试。