第 5 章:Peer 从哪里来 —— 四条发现路径汇入一个候选池
下载前先要回答“和谁下载”。Transmission 同时实现 Tracker、DHT、LPD 与 PEX,但它们不会各自维护连接;所有地址最终都被标准化为
tr_pex,交给 Peer Manager 去重、过滤和调度。
一、统一汇流模型
统一类型让“Peer 从哪里来”成为候选质量的一个字段,而不是四套连接实现。tr_peer_from 会记录来源,统计和连接排序可以使用它。
二、Tracker:按 tier 管理的有状态调度器
Torrent 的 announce list 按 BEP 12 分 tier。tr_torrent_announcer 为每个任务持有多个 tr_tier,每个 tier 又有一个 tracker 列表和当前索引。
2.1 tier 不是简单 URL 数组
每个 tier 记录:
- 当前 tracker、announce/scrape 是否进行中;
announceAt、scrapeAt、最小间隔和正常间隔;- STARTED / COMPLETED / STOPPED / NONE 事件队列;
- 本 tier 的 up/down/corrupt 字节计数;
- 上次结果、错误次数、seeders/leechers/downloaders;
- 手动 announce 的最早允许时间。
事件有优先级。STOPPED 不能被普通 periodic announce 淹没;重复或被后续状态覆盖的事件会被压缩。
2.2 HTTP 与 UDP 只在传输层分叉
Announcer 根据 URL scheme 分发:
http/https → tr_tracker_http_announce / scrape → tr_web/libcurl
udp → tr_announcer_udp::announce / scrape → 事务状态机两条路径最终都回调 tr_announce_response / tr_scrape_response。上层 tier 不关心响应来自 curl 还是 UDP socket。
2.3 失败不是立即全局失败
一次 announce 失败会记录错误、切换到 tier 中下一个 tracker,并按返回值或退避间隔重新安排。只有当前 tracker 失效并不代表 torrent 没有 Peer;其他 tier、DHT、LPD、PEX 仍可继续提供地址。
Tracker 返回 Peer 后,Torrent 的 tracker callback 发布 tr_tracker_event::Peers,再调用 tr_peerMgrAddPex() 汇入 swarm。
三、DHT:没有中心 Tracker 的公网发现
tr_dht_impl 包装第三方 DHT API,通过 Mediator 与 Session 解耦。它持有三类 timer:
- bootstrap:从
dht.bootstrap与默认域名逐步加入节点; - periodic:把收包或空 tick 交给 DHT 引擎,按建议时间再次调用;
- announce:遍历允许 DHT 的 torrents,分别对 IPv4/IPv6 发起 search/announce。
DHT 不会在网络尚未就绪时盲目 announce。内部 SwarmStatus 根据 node 数与 firewalled 状态判断 ready 程度,低于阈值先 bootstrap。
回调拿到 compact peer 数据后转换为 tr_pex,再通过 Mediator 加回对应 info hash 的 swarm。
四、LPD:局域网里的低成本发现
Local Peer Discovery 使用组播发送 BEP 14 风格 announce。消息携带监听端口、cookie 与一个或多个 info hash。
实现里有两类防风暴措施:
- 同一 torrent 的 announce 间隔约 240 秒,并遵守“一分钟最多一次 announce”的原则;
- 接收侧统计一个 upkeep 周期内的消息数,超过上限就丢弃,避免 hostile host 或大局域网拖垮解析。
收到消息后先验证协议格式、端口和 info hash,再通过 LpdMediator::onPeerFound() 找到 torrent 并加入 Peer Manager。自身 announce 通过随机 cookie 识别,避免把自己加入候选池。
五、PEX:已连接 Peer 带来的增量拓扑
PEX 依赖 LTEP 扩展握手协商 ut_pex id。支持后,tr_peerMsgsImpl 周期性比较“上次发送的已连接 Peer 列表”和当前列表:
added = current - previous
dropped = previous - current它分别为 IPv4/IPv6 生成 compact address 与 flags,只发送增量。接收侧限制单条消息的 Peer 数量,解析后同样进入 tr_peerMgrAddPex()。
PEX 特别适合 Tracker/DHT 给出少量入口后迅速扩展邻居,但也最不可信:消息来自普通 Peer,必须限制大小、校验地址并经过 blocklist。
六、Private torrent 的硬边界
tr_torrent 对三种去中心化发现提供统一判断:
allows_pex() = is_public() && session->allows_pex();
allows_dht() = is_public() && session->allowsDHT();
allows_lpd() = is_public() && session->allowsLPD();也就是说,Session 全局开启并不够;private torrent 会关闭 DHT、LPD 与 PEX,只使用 Tracker 明确给出的 Peer。这是协议隐私语义,不是可选优化。
收到 private torrent 上的 ut_pex 时,消息层会拒绝,而不是“接收后不使用”。边界越靠近输入越安全。
七、Peer Manager 如何吸收地址
tr_peerMgrAddPex() 对每个候选执行:
- compact/address 结构是否合法;
- 地址是否被 blocklist 命中;
- 该来源下地址是否可作为公网/局域网 Peer;
- swarm 是否已有同 socket address 的
tr_peer_info; - 合并 flags、来源和可连接性,而不是重复插入。
候选记录不等于连接。Peer Manager 的 reconnect pulse 会综合全局/单 torrent Peer 上限、最近失败、是否被 ban、是否已有连接/握手、µTP 失败历史、是否 upload-only 等条件选择新连接。
八、为什么发现和连接必须解耦
如果 Tracker 回来 200 个地址就立刻连 200 个,会同时击穿 fd、NAT、带宽和远端服务。将发现结果先放入池中有四个收益:
- 去重和来源合并;
- blocklist 与地址合法性统一执行;
- 连接失败可以积累退避/fruitless 历史;
- 全局连接预算能跨 torrent 公平分配。
这是一种普适模式:发现产生候选,调度器才拥有资源分配权。
九、排障时看哪一层
| 现象 | 优先检查 |
|---|---|
| Tracker 显示成功但没有连接 | Peer Manager 过滤、Peer 上限、端口可达性 |
| Private torrent Peer 很少 | Tracker tier;DHT/LPD/PEX 被设计性关闭 |
| 同局域网机器互相看不到 | LPD 开关、组播接口、防火墙、监听端口 |
| DHT 一直未就绪 | bootstrap 文件/DNS、UDP socket、firewalled 状态 |
| Peer 数暴涨但速度不涨 | 候选数不等于 active/unchoked Peer;转看请求和带宽章节 |
源码锚点
libtransmission/announcer.cc:tier、事件队列与失败切换libtransmission/announcer-http.cc、announcer-udp.cc:两种 Tracker 传输libtransmission/tr-dht.cc:bootstrap/periodic/announcelibtransmission/tr-lpd.cc:组播与防洪libtransmission/peer-msgs.cc:ut_pex增量交换libtransmission/peer-mgr.cc:tr_peerMgrAddPex与连接候选