Skip to content

第 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 是否进行中;
  • announceAtscrapeAt、最小间隔和正常间隔;
  • STARTED / COMPLETED / STOPPED / NONE 事件队列;
  • 本 tier 的 up/down/corrupt 字节计数;
  • 上次结果、错误次数、seeders/leechers/downloaders;
  • 手动 announce 的最早允许时间。

事件有优先级。STOPPED 不能被普通 periodic announce 淹没;重复或被后续状态覆盖的事件会被压缩。

2.2 HTTP 与 UDP 只在传输层分叉

Announcer 根据 URL scheme 分发:

text
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。

实现里有两类防风暴措施:

  1. 同一 torrent 的 announce 间隔约 240 秒,并遵守“一分钟最多一次 announce”的原则;
  2. 接收侧统计一个 upkeep 周期内的消息数,超过上限就丢弃,避免 hostile host 或大局域网拖垮解析。

收到消息后先验证协议格式、端口和 info hash,再通过 LpdMediator::onPeerFound() 找到 torrent 并加入 Peer Manager。自身 announce 通过随机 cookie 识别,避免把自己加入候选池。

五、PEX:已连接 Peer 带来的增量拓扑

PEX 依赖 LTEP 扩展握手协商 ut_pex id。支持后,tr_peerMsgsImpl 周期性比较“上次发送的已连接 Peer 列表”和当前列表:

text
added   = current - previous
dropped = previous - current

它分别为 IPv4/IPv6 生成 compact address 与 flags,只发送增量。接收侧限制单条消息的 Peer 数量,解析后同样进入 tr_peerMgrAddPex()

PEX 特别适合 Tracker/DHT 给出少量入口后迅速扩展邻居,但也最不可信:消息来自普通 Peer,必须限制大小、校验地址并经过 blocklist。

六、Private torrent 的硬边界

tr_torrent 对三种去中心化发现提供统一判断:

cpp
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() 对每个候选执行:

  1. compact/address 结构是否合法;
  2. 地址是否被 blocklist 命中;
  3. 该来源下地址是否可作为公网/局域网 Peer;
  4. swarm 是否已有同 socket address 的 tr_peer_info
  5. 合并 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;转看请求和带宽章节

源码锚点

文档采用 CC BY-SA 4.0;源码片段保留 Transmission 上游许可。