BitTorrent 种子文件生成与 Web Seed 技术详解:从理论到实践

1. 引言

BitTorrent 是互联网上最流行的 P2P 文件共享协议之一。其核心设计理念是将大文件分割成多个小块,让用户之间相互传输数据,从而减轻单一服务器的带宽压力。种子文件(.torrent)作为 BitTorrent 网络的"地图",承载着文件的元数据信息,是整个下载流程的起点。

本文将从纯技术角度深入剖析种子文件的生成原理、Bencode 编码规范、Info Hash 计算机制,以及 Web Seed 等扩展特性的实现方式。


2. BitTorrent 系统架构

2.1 核心角色

BitTorrent 网络由以下几个核心角色协作完成文件传输:
#mermaid-svg-2vSg1cljRpwOvjuH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2vSg1cljRpwOvjuH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2vSg1cljRpwOvjuH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2vSg1cljRpwOvjuH .error-icon{fill:#552222;}#mermaid-svg-2vSg1cljRpwOvjuH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2vSg1cljRpwOvjuH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2vSg1cljRpwOvjuH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2vSg1cljRpwOvjuH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2vSg1cljRpwOvjuH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2vSg1cljRpwOvjuH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2vSg1cljRpwOvjuH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2vSg1cljRpwOvjuH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2vSg1cljRpwOvjuH .marker.cross{stroke:#333333;}#mermaid-svg-2vSg1cljRpwOvjuH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2vSg1cljRpwOvjuH p{margin:0;}#mermaid-svg-2vSg1cljRpwOvjuH .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-2vSg1cljRpwOvjuH .cluster-label text{fill:#333;}#mermaid-svg-2vSg1cljRpwOvjuH .cluster-label span{color:#333;}#mermaid-svg-2vSg1cljRpwOvjuH .cluster-label span p{background-color:transparent;}#mermaid-svg-2vSg1cljRpwOvjuH .label text,#mermaid-svg-2vSg1cljRpwOvjuH span{fill:#333;color:#333;}#mermaid-svg-2vSg1cljRpwOvjuH .node rect,#mermaid-svg-2vSg1cljRpwOvjuH .node circle,#mermaid-svg-2vSg1cljRpwOvjuH .node ellipse,#mermaid-svg-2vSg1cljRpwOvjuH .node polygon,#mermaid-svg-2vSg1cljRpwOvjuH .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-2vSg1cljRpwOvjuH .rough-node .label text,#mermaid-svg-2vSg1cljRpwOvjuH .node .label text,#mermaid-svg-2vSg1cljRpwOvjuH .image-shape .label,#mermaid-svg-2vSg1cljRpwOvjuH .icon-shape .label{text-anchor:middle;}#mermaid-svg-2vSg1cljRpwOvjuH .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-2vSg1cljRpwOvjuH .rough-node .label,#mermaid-svg-2vSg1cljRpwOvjuH .node .label,#mermaid-svg-2vSg1cljRpwOvjuH .image-shape .label,#mermaid-svg-2vSg1cljRpwOvjuH .icon-shape .label{text-align:center;}#mermaid-svg-2vSg1cljRpwOvjuH .node.clickable{cursor:pointer;}#mermaid-svg-2vSg1cljRpwOvjuH .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-2vSg1cljRpwOvjuH .arrowheadPath{fill:#333333;}#mermaid-svg-2vSg1cljRpwOvjuH .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-2vSg1cljRpwOvjuH .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-2vSg1cljRpwOvjuH .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2vSg1cljRpwOvjuH .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-2vSg1cljRpwOvjuH .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2vSg1cljRpwOvjuH .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-2vSg1cljRpwOvjuH .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-2vSg1cljRpwOvjuH .cluster text{fill:#333;}#mermaid-svg-2vSg1cljRpwOvjuH .cluster span{color:#333;}#mermaid-svg-2vSg1cljRpwOvjuH div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-2vSg1cljRpwOvjuH .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-2vSg1cljRpwOvjuH rect.text{fill:none;stroke-width:0;}#mermaid-svg-2vSg1cljRpwOvjuH .icon-shape,#mermaid-svg-2vSg1cljRpwOvjuH .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-2vSg1cljRpwOvjuH .icon-shape p,#mermaid-svg-2vSg1cljRpwOvjuH .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-2vSg1cljRpwOvjuH .icon-shape .label rect,#mermaid-svg-2vSg1cljRpwOvjuH .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-2vSg1cljRpwOvjuH .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-2vSg1cljRpwOvjuH .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-2vSg1cljRpwOvjuH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端层
数据服务层
内容发布层
文件源
种子生成器
.torrent 文件
Web Seed 服务器
Tracker 服务器
DHT 网络
Peer A

下载/上传
Peer B

下载/上传

各角色职责说明:

角色 职责 技术实现
种子生成器 扫描文件内容,计算分块哈希,生成 .torrent 元数据 自定义工具、mktorrent、Transmission
Tracker 服务器 维护 Peer 列表,协助 Peer 相互发现 HTTP/HTTPS/UDP 协议,返回 Bencode 编码的 Peer 列表
Web Seed 服务器 通过 HTTP/HTTPS 协议直接提供文件数据 标准 Web 服务器,支持 Range 请求
DHT 网络 分布式 Peer 发现,无需中心化服务器 Kademlia 协议,分布式哈希表
客户端(Peer) 下载和上传文件数据块 libtorrent、Deluge、qBittorrent

2.2 数据流转路径

DHT 客户端 B 客户端 A Tracker 服务器 Web Seed 服务器 种子生成器 DHT 客户端 B 客户端 A Tracker 服务器 Web Seed 服务器 种子生成器 #mermaid-svg-RZD78NSszzjbCrAE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-RZD78NSszzjbCrAE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-RZD78NSszzjbCrAE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-RZD78NSszzjbCrAE .error-icon{fill:#552222;}#mermaid-svg-RZD78NSszzjbCrAE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-RZD78NSszzjbCrAE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-RZD78NSszzjbCrAE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-RZD78NSszzjbCrAE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-RZD78NSszzjbCrAE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-RZD78NSszzjbCrAE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-RZD78NSszzjbCrAE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-RZD78NSszzjbCrAE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-RZD78NSszzjbCrAE .marker.cross{stroke:#333333;}#mermaid-svg-RZD78NSszzjbCrAE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-RZD78NSszzjbCrAE p{margin:0;}#mermaid-svg-RZD78NSszzjbCrAE .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RZD78NSszzjbCrAE text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-RZD78NSszzjbCrAE .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-RZD78NSszzjbCrAE .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-RZD78NSszzjbCrAE .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-RZD78NSszzjbCrAE .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-RZD78NSszzjbCrAE #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-RZD78NSszzjbCrAE .sequenceNumber{fill:white;}#mermaid-svg-RZD78NSszzjbCrAE #sequencenumber{fill:#333;}#mermaid-svg-RZD78NSszzjbCrAE #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-RZD78NSszzjbCrAE .messageText{fill:#333;stroke:none;}#mermaid-svg-RZD78NSszzjbCrAE .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RZD78NSszzjbCrAE .labelText,#mermaid-svg-RZD78NSszzjbCrAE .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-RZD78NSszzjbCrAE .loopText,#mermaid-svg-RZD78NSszzjbCrAE .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-RZD78NSszzjbCrAE .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-RZD78NSszzjbCrAE .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-RZD78NSszzjbCrAE .noteText,#mermaid-svg-RZD78NSszzjbCrAE .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-RZD78NSszzjbCrAE .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RZD78NSszzjbCrAE .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RZD78NSszzjbCrAE .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-RZD78NSszzjbCrAE .actorPopupMenu{position:absolute;}#mermaid-svg-RZD78NSszzjbCrAE .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-RZD78NSszzjbCrAE .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-RZD78NSszzjbCrAE .actor-man circle,#mermaid-svg-RZD78NSszzjbCrAE line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-RZD78NSszzjbCrAE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 读取文件,计算分块哈希2. 构造 Info 字典,生成 .torrent3. 部署文件到 HTTP 服务器4. 分发 .torrent 文件5. 解析 .torrent,获取元数据6. 发起 HTTP GET 请求7. 返回文件数据8. 验证 SHA1 哈希9. 写入磁盘,完成下载10. 注册为 Seed(做种者)11. 发布 announce_peer12. 获取 .torrent 文件13. 查询 Peer 列表14. 返回 ClientA 地址15. 建立连接,下载数据


3. 种子文件技术规范

3.1 Bencode 编码

Bencode 是 BitTorrent 协议使用的编码格式,设计目标简单、高效、易于解析。支持四种数据类型:
#mermaid-svg-w8t5WmDIGHB66LUf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w8t5WmDIGHB66LUf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w8t5WmDIGHB66LUf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w8t5WmDIGHB66LUf .error-icon{fill:#552222;}#mermaid-svg-w8t5WmDIGHB66LUf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w8t5WmDIGHB66LUf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w8t5WmDIGHB66LUf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w8t5WmDIGHB66LUf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w8t5WmDIGHB66LUf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w8t5WmDIGHB66LUf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w8t5WmDIGHB66LUf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w8t5WmDIGHB66LUf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w8t5WmDIGHB66LUf .marker.cross{stroke:#333333;}#mermaid-svg-w8t5WmDIGHB66LUf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w8t5WmDIGHB66LUf p{margin:0;}#mermaid-svg-w8t5WmDIGHB66LUf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w8t5WmDIGHB66LUf .cluster-label text{fill:#333;}#mermaid-svg-w8t5WmDIGHB66LUf .cluster-label span{color:#333;}#mermaid-svg-w8t5WmDIGHB66LUf .cluster-label span p{background-color:transparent;}#mermaid-svg-w8t5WmDIGHB66LUf .label text,#mermaid-svg-w8t5WmDIGHB66LUf span{fill:#333;color:#333;}#mermaid-svg-w8t5WmDIGHB66LUf .node rect,#mermaid-svg-w8t5WmDIGHB66LUf .node circle,#mermaid-svg-w8t5WmDIGHB66LUf .node ellipse,#mermaid-svg-w8t5WmDIGHB66LUf .node polygon,#mermaid-svg-w8t5WmDIGHB66LUf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w8t5WmDIGHB66LUf .rough-node .label text,#mermaid-svg-w8t5WmDIGHB66LUf .node .label text,#mermaid-svg-w8t5WmDIGHB66LUf .image-shape .label,#mermaid-svg-w8t5WmDIGHB66LUf .icon-shape .label{text-anchor:middle;}#mermaid-svg-w8t5WmDIGHB66LUf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w8t5WmDIGHB66LUf .rough-node .label,#mermaid-svg-w8t5WmDIGHB66LUf .node .label,#mermaid-svg-w8t5WmDIGHB66LUf .image-shape .label,#mermaid-svg-w8t5WmDIGHB66LUf .icon-shape .label{text-align:center;}#mermaid-svg-w8t5WmDIGHB66LUf .node.clickable{cursor:pointer;}#mermaid-svg-w8t5WmDIGHB66LUf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w8t5WmDIGHB66LUf .arrowheadPath{fill:#333333;}#mermaid-svg-w8t5WmDIGHB66LUf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w8t5WmDIGHB66LUf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w8t5WmDIGHB66LUf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w8t5WmDIGHB66LUf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w8t5WmDIGHB66LUf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w8t5WmDIGHB66LUf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w8t5WmDIGHB66LUf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w8t5WmDIGHB66LUf .cluster text{fill:#333;}#mermaid-svg-w8t5WmDIGHB66LUf .cluster span{color:#333;}#mermaid-svg-w8t5WmDIGHB66LUf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w8t5WmDIGHB66LUf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w8t5WmDIGHB66LUf rect.text{fill:none;stroke-width:0;}#mermaid-svg-w8t5WmDIGHB66LUf .icon-shape,#mermaid-svg-w8t5WmDIGHB66LUf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w8t5WmDIGHB66LUf .icon-shape p,#mermaid-svg-w8t5WmDIGHB66LUf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w8t5WmDIGHB66LUf .icon-shape .label rect,#mermaid-svg-w8t5WmDIGHB66LUf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w8t5WmDIGHB66LUf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w8t5WmDIGHB66LUf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w8t5WmDIGHB66LUf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Bencode 数据类型
字符串

{长度}:{内容}
示例: 4:spam
整数

i{数字}e
示例: i42e
列表

l{元素}e
示例: l4:spam4:eggse
字典

d{键值对}e
示例: d3:foo3:bare

3.2 Info 字典结构

Info 字典是种子文件的核心,包含了文件的完整元数据:
#mermaid-svg-9snqn6wmHqrOaAhl{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9snqn6wmHqrOaAhl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9snqn6wmHqrOaAhl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9snqn6wmHqrOaAhl .error-icon{fill:#552222;}#mermaid-svg-9snqn6wmHqrOaAhl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9snqn6wmHqrOaAhl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9snqn6wmHqrOaAhl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9snqn6wmHqrOaAhl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9snqn6wmHqrOaAhl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9snqn6wmHqrOaAhl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9snqn6wmHqrOaAhl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9snqn6wmHqrOaAhl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9snqn6wmHqrOaAhl .marker.cross{stroke:#333333;}#mermaid-svg-9snqn6wmHqrOaAhl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9snqn6wmHqrOaAhl p{margin:0;}#mermaid-svg-9snqn6wmHqrOaAhl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-9snqn6wmHqrOaAhl .cluster-label text{fill:#333;}#mermaid-svg-9snqn6wmHqrOaAhl .cluster-label span{color:#333;}#mermaid-svg-9snqn6wmHqrOaAhl .cluster-label span p{background-color:transparent;}#mermaid-svg-9snqn6wmHqrOaAhl .label text,#mermaid-svg-9snqn6wmHqrOaAhl span{fill:#333;color:#333;}#mermaid-svg-9snqn6wmHqrOaAhl .node rect,#mermaid-svg-9snqn6wmHqrOaAhl .node circle,#mermaid-svg-9snqn6wmHqrOaAhl .node ellipse,#mermaid-svg-9snqn6wmHqrOaAhl .node polygon,#mermaid-svg-9snqn6wmHqrOaAhl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-9snqn6wmHqrOaAhl .rough-node .label text,#mermaid-svg-9snqn6wmHqrOaAhl .node .label text,#mermaid-svg-9snqn6wmHqrOaAhl .image-shape .label,#mermaid-svg-9snqn6wmHqrOaAhl .icon-shape .label{text-anchor:middle;}#mermaid-svg-9snqn6wmHqrOaAhl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-9snqn6wmHqrOaAhl .rough-node .label,#mermaid-svg-9snqn6wmHqrOaAhl .node .label,#mermaid-svg-9snqn6wmHqrOaAhl .image-shape .label,#mermaid-svg-9snqn6wmHqrOaAhl .icon-shape .label{text-align:center;}#mermaid-svg-9snqn6wmHqrOaAhl .node.clickable{cursor:pointer;}#mermaid-svg-9snqn6wmHqrOaAhl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-9snqn6wmHqrOaAhl .arrowheadPath{fill:#333333;}#mermaid-svg-9snqn6wmHqrOaAhl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-9snqn6wmHqrOaAhl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-9snqn6wmHqrOaAhl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9snqn6wmHqrOaAhl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-9snqn6wmHqrOaAhl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9snqn6wmHqrOaAhl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-9snqn6wmHqrOaAhl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-9snqn6wmHqrOaAhl .cluster text{fill:#333;}#mermaid-svg-9snqn6wmHqrOaAhl .cluster span{color:#333;}#mermaid-svg-9snqn6wmHqrOaAhl div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-9snqn6wmHqrOaAhl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-9snqn6wmHqrOaAhl rect.text{fill:none;stroke-width:0;}#mermaid-svg-9snqn6wmHqrOaAhl .icon-shape,#mermaid-svg-9snqn6wmHqrOaAhl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-9snqn6wmHqrOaAhl .icon-shape p,#mermaid-svg-9snqn6wmHqrOaAhl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-9snqn6wmHqrOaAhl .icon-shape .label rect,#mermaid-svg-9snqn6wmHqrOaAhl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-9snqn6wmHqrOaAhl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-9snqn6wmHqrOaAhl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-9snqn6wmHqrOaAhl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Info 字典
name: 文件名
length: 文件大小(字节)
piece length: 每块大小
pieces: 所有块 SHA1 哈希拼接
块 1 哈希

20 字节
块 2 哈希

20 字节
块 N 哈希

20 字节

Info Hash 计算过程:
#mermaid-svg-U1sSb7XpQN1KzIHw{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-U1sSb7XpQN1KzIHw .error-icon{fill:#552222;}#mermaid-svg-U1sSb7XpQN1KzIHw .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-U1sSb7XpQN1KzIHw .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-U1sSb7XpQN1KzIHw .marker{fill:#333333;stroke:#333333;}#mermaid-svg-U1sSb7XpQN1KzIHw .marker.cross{stroke:#333333;}#mermaid-svg-U1sSb7XpQN1KzIHw svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-U1sSb7XpQN1KzIHw p{margin:0;}#mermaid-svg-U1sSb7XpQN1KzIHw .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-U1sSb7XpQN1KzIHw .cluster-label text{fill:#333;}#mermaid-svg-U1sSb7XpQN1KzIHw .cluster-label span{color:#333;}#mermaid-svg-U1sSb7XpQN1KzIHw .cluster-label span p{background-color:transparent;}#mermaid-svg-U1sSb7XpQN1KzIHw .label text,#mermaid-svg-U1sSb7XpQN1KzIHw span{fill:#333;color:#333;}#mermaid-svg-U1sSb7XpQN1KzIHw .node rect,#mermaid-svg-U1sSb7XpQN1KzIHw .node circle,#mermaid-svg-U1sSb7XpQN1KzIHw .node ellipse,#mermaid-svg-U1sSb7XpQN1KzIHw .node polygon,#mermaid-svg-U1sSb7XpQN1KzIHw .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-U1sSb7XpQN1KzIHw .rough-node .label text,#mermaid-svg-U1sSb7XpQN1KzIHw .node .label text,#mermaid-svg-U1sSb7XpQN1KzIHw .image-shape .label,#mermaid-svg-U1sSb7XpQN1KzIHw .icon-shape .label{text-anchor:middle;}#mermaid-svg-U1sSb7XpQN1KzIHw .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-U1sSb7XpQN1KzIHw .rough-node .label,#mermaid-svg-U1sSb7XpQN1KzIHw .node .label,#mermaid-svg-U1sSb7XpQN1KzIHw .image-shape .label,#mermaid-svg-U1sSb7XpQN1KzIHw .icon-shape .label{text-align:center;}#mermaid-svg-U1sSb7XpQN1KzIHw .node.clickable{cursor:pointer;}#mermaid-svg-U1sSb7XpQN1KzIHw .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-U1sSb7XpQN1KzIHw .arrowheadPath{fill:#333333;}#mermaid-svg-U1sSb7XpQN1KzIHw .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-U1sSb7XpQN1KzIHw .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-U1sSb7XpQN1KzIHw .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U1sSb7XpQN1KzIHw .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-U1sSb7XpQN1KzIHw .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U1sSb7XpQN1KzIHw .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-U1sSb7XpQN1KzIHw .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-U1sSb7XpQN1KzIHw .cluster text{fill:#333;}#mermaid-svg-U1sSb7XpQN1KzIHw .cluster span{color:#333;}#mermaid-svg-U1sSb7XpQN1KzIHw div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-U1sSb7XpQN1KzIHw .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-U1sSb7XpQN1KzIHw rect.text{fill:none;stroke-width:0;}#mermaid-svg-U1sSb7XpQN1KzIHw .icon-shape,#mermaid-svg-U1sSb7XpQN1KzIHw .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-U1sSb7XpQN1KzIHw .icon-shape p,#mermaid-svg-U1sSb7XpQN1KzIHw .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-U1sSb7XpQN1KzIHw .icon-shape .label rect,#mermaid-svg-U1sSb7XpQN1KzIHw .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-U1sSb7XpQN1KzIHw .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-U1sSb7XpQN1KzIHw .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-U1sSb7XpQN1KzIHw :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Info 字典
Bencode 编码
SHA1 哈希
Info Hash

20 字节/40 位十六进制
磁力链接标识
DHT 网络索引键
Tracker 查询标识

3.3 完整种子文件结构

#mermaid-svg-8ex3jwkO81SpniJo{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8ex3jwkO81SpniJo .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8ex3jwkO81SpniJo .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8ex3jwkO81SpniJo .error-icon{fill:#552222;}#mermaid-svg-8ex3jwkO81SpniJo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8ex3jwkO81SpniJo .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8ex3jwkO81SpniJo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8ex3jwkO81SpniJo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8ex3jwkO81SpniJo .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8ex3jwkO81SpniJo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8ex3jwkO81SpniJo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8ex3jwkO81SpniJo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8ex3jwkO81SpniJo .marker.cross{stroke:#333333;}#mermaid-svg-8ex3jwkO81SpniJo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8ex3jwkO81SpniJo p{margin:0;}#mermaid-svg-8ex3jwkO81SpniJo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8ex3jwkO81SpniJo .cluster-label text{fill:#333;}#mermaid-svg-8ex3jwkO81SpniJo .cluster-label span{color:#333;}#mermaid-svg-8ex3jwkO81SpniJo .cluster-label span p{background-color:transparent;}#mermaid-svg-8ex3jwkO81SpniJo .label text,#mermaid-svg-8ex3jwkO81SpniJo span{fill:#333;color:#333;}#mermaid-svg-8ex3jwkO81SpniJo .node rect,#mermaid-svg-8ex3jwkO81SpniJo .node circle,#mermaid-svg-8ex3jwkO81SpniJo .node ellipse,#mermaid-svg-8ex3jwkO81SpniJo .node polygon,#mermaid-svg-8ex3jwkO81SpniJo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8ex3jwkO81SpniJo .rough-node .label text,#mermaid-svg-8ex3jwkO81SpniJo .node .label text,#mermaid-svg-8ex3jwkO81SpniJo .image-shape .label,#mermaid-svg-8ex3jwkO81SpniJo .icon-shape .label{text-anchor:middle;}#mermaid-svg-8ex3jwkO81SpniJo .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8ex3jwkO81SpniJo .rough-node .label,#mermaid-svg-8ex3jwkO81SpniJo .node .label,#mermaid-svg-8ex3jwkO81SpniJo .image-shape .label,#mermaid-svg-8ex3jwkO81SpniJo .icon-shape .label{text-align:center;}#mermaid-svg-8ex3jwkO81SpniJo .node.clickable{cursor:pointer;}#mermaid-svg-8ex3jwkO81SpniJo .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8ex3jwkO81SpniJo .arrowheadPath{fill:#333333;}#mermaid-svg-8ex3jwkO81SpniJo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8ex3jwkO81SpniJo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8ex3jwkO81SpniJo .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8ex3jwkO81SpniJo .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8ex3jwkO81SpniJo .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8ex3jwkO81SpniJo .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8ex3jwkO81SpniJo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8ex3jwkO81SpniJo .cluster text{fill:#333;}#mermaid-svg-8ex3jwkO81SpniJo .cluster span{color:#333;}#mermaid-svg-8ex3jwkO81SpniJo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8ex3jwkO81SpniJo .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8ex3jwkO81SpniJo rect.text{fill:none;stroke-width:0;}#mermaid-svg-8ex3jwkO81SpniJo .icon-shape,#mermaid-svg-8ex3jwkO81SpniJo .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8ex3jwkO81SpniJo .icon-shape p,#mermaid-svg-8ex3jwkO81SpniJo .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8ex3jwkO81SpniJo .icon-shape .label rect,#mermaid-svg-8ex3jwkO81SpniJo .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8ex3jwkO81SpniJo .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8ex3jwkO81SpniJo .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8ex3jwkO81SpniJo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} .torrent 文件
info: Info 字典
announce: 主要 Tracker URL
announce-list: 备用 Tracker 列表
url-list: Web Seed URL 列表
creation date: 创建时间戳
comment: 注释
created by: 生成工具标识
name
length
piece length
pieces
Web Seed 1
Web Seed 2


4. 种子生成代码实现

4.1 Bencode 编码器

python 复制代码
#!/usr/bin/env python3
"""
纯 Python Bencode 编码实现
遵循 BitTorrent 规范:字典键按字典序排序
"""

def bencode(obj):
    """
    将 Python 对象编码为 Bencode 格式
    
    参数:
        obj: Python 对象(str/bytes/int/list/dict)
    
    返回:
        bytes: Bencode 编码后的字节序列
    """
    if isinstance(obj, str):
        obj = obj.encode('utf-8')
    
    if isinstance(obj, bytes):
        return str(len(obj)).encode('ascii') + b':' + obj
    
    elif isinstance(obj, int):
        return b'i' + str(obj).encode('ascii') + b'e'
    
    elif isinstance(obj, list):
        result = b'l'
        for item in obj:
            result += bencode(item)
        result += b'e'
        return result
    
    elif isinstance(obj, dict):
        result = b'd'
        # 键必须按字典序排序(BitTorrent 规范强制要求)
        for key in sorted(obj.keys()):
            if not isinstance(key, str):
                raise TypeError(f"字典键必须是字符串: {type(key)}")
            result += bencode(key)
            result += bencode(obj[key])
        result += b'e'
        return result
    
    else:
        raise TypeError(f"不支持的类型: {type(obj)}")

4.2 单文件种子生成器

python 复制代码
#!/usr/bin/env python3
"""
单文件 BitTorrent 种子生成器
支持 Web Seed 和 Tracker 配置
"""

import os
import sys
import hashlib
import time
from pathlib import Path

def calculate_piece_hashes(data, piece_length):
    """
    计算文件分块 SHA1 哈希
    
    参数:
        data: 文件二进制数据
        piece_length: 每块大小(字节)
    
    返回:
        bytes: 所有块哈希值的拼接(每块 20 字节)
    """
    pieces = []
    offset = 0
    
    while offset < len(data):
        chunk = data[offset:offset + piece_length]
        pieces.append(hashlib.sha1(chunk).digest())
        offset += piece_length
    
    return b''.join(pieces)

def generate_torrent(file_path, output_path, trackers=None, web_seeds=None, 
                     piece_length=16384, comment=None):
    """
    生成带 Web Seed 支持的种子文件
    
    参数:
        file_path: 要打包的文件路径
        output_path: 输出 .torrent 文件路径
        trackers: Tracker URL 列表
        web_seeds: Web Seed URL 列表
        piece_length: 分块大小(默认 16KB)
        comment: 注释信息
    
    返回:
        str: Info Hash(四十位十六进制字符串)
    """
    # 1. 读取文件内容
    with open(file_path, 'rb') as f:
        data = f.read()
    
    # 2. 计算分块哈希
    piece_hashes = calculate_piece_hashes(data, piece_length)
    
    # 3. 构造 Info 字典
    info = {
        'name': os.path.basename(file_path),
        'length': len(data),
        'piece length': piece_length,
        'pieces': piece_hashes
    }
    
    # 4. 构造完整的 torrent 元数据
    torrent = {
        'info': info,
        'creation date': int(time.time()),
        'created by': 'PythonTorrentGenerator/1.0'
    }
    
    # 添加 Tracker 配置
    if trackers:
        torrent['announce'] = trackers[0]
        if len(trackers) > 1:
            torrent['announce-list'] = [[t] for t in trackers]
    else:
        torrent['announce'] = 'http://127.0.0.1:6881/announce'
    
    # 添加 Web Seed 配置
    if web_seeds:
        torrent['url-list'] = web_seeds
    
    # 添加注释
    if comment:
        torrent['comment'] = comment
    
    # 5. Bencode 编码并写入文件
    with open(output_path, 'wb') as f:
        f.write(bencode(torrent))
    
    # 6. 计算 Info Hash
    info_encoded = bencode(info)
    info_hash = hashlib.sha1(info_encoded).hexdigest()
    
    return info_hash

def main():
    """命令行入口"""
    import argparse
    
    parser = argparse.ArgumentParser(description='BitTorrent 种子文件生成器')
    parser.add_argument('file', help='要打包的文件路径')
    parser.add_argument('-o', '--output', default='output.torrent', 
                        help='输出种子文件路径')
    parser.add_argument('-p', '--piece-size', type=int, default=16384,
                        help='分块大小(字节),默认 16384')
    parser.add_argument('-t', '--tracker', action='append',
                        help='Tracker URL(可多次使用)')
    parser.add_argument('-w', '--webseed', action='append',
                        help='Web Seed URL(可多次使用)')
    parser.add_argument('-c', '--comment', help='种子注释')
    
    args = parser.parse_args()
    
    if not os.path.exists(args.file):
        print(f"错误:文件不存在 - {args.file}")
        sys.exit(1)
    
    info_hash = generate_torrent(
        file_path=args.file,
        output_path=args.output,
        trackers=args.tracker,
        web_seeds=args.webseed,
        piece_length=args.piece_size,
        comment=args.comment
    )
    
    print(f"[✓] 种子生成成功")
    print(f"    输出: {args.output}")
    print(f"    Info Hash: {info_hash}")
    print(f"    分块大小: {args.piece_size} 字节")
    print(f"    文件大小: {os.path.getsize(args.file)} 字节")
    if args.tracker:
        print(f"    Tracker: {args.tracker}")
    if args.webseed:
        print(f"    Web Seed: {args.webseed}")

if __name__ == '__main__':
    main()

4.3 多文件(目录)种子生成器

python 复制代码
#!/usr/bin/env python3
"""
多文件 BitTorrent 种子生成器
支持整个目录打包,保持目录结构
"""

import os
import sys
import hashlib
import time
from pathlib import Path

def calculate_piece_hashes_multifile(dir_path, files, piece_length):
    """
    计算多文件目录的分块哈希
    所有文件按顺序拼接,统一分块
    """
    pieces = []
    current_piece = b''
    
    for file_info in files:
        file_path = dir_path / '/'.join(file_info['path'])
        
        with open(file_path, 'rb') as f:
            while True:
                chunk = f.read(piece_length - len(current_piece))
                if not chunk:
                    break
                
                current_piece += chunk
                
                if len(current_piece) == piece_length:
                    pieces.append(hashlib.sha1(current_piece).digest())
                    current_piece = b''
    
    # 处理最后一个不完整的块
    if current_piece:
        pieces.append(hashlib.sha1(current_piece).digest())
    
    return b''.join(pieces)

def generate_multi_file_torrent(dir_path, output_path, trackers=None, 
                                web_seeds=None, piece_length=16384, 
                                comment=None):
    """
    生成多文件种子
    """
    dir_path = Path(dir_path)
    
    if not dir_path.is_dir():
        raise ValueError(f"路径不是目录: {dir_path}")
    
    # 1. 收集所有文件
    files = []
    total_size = 0
    
    for file in sorted(dir_path.rglob('*')):
        if file.is_file():
            rel_path = str(file.relative_to(dir_path)).split(os.sep)
            file_size = file.stat().st_size
            files.append({
                'length': file_size,
                'path': rel_path
            })
            total_size += file_size
    
    if not files:
        raise ValueError(f"目录为空: {dir_path}")
    
    # 2. 计算分块哈希
    piece_hashes = calculate_piece_hashes_multifile(dir_path, files, piece_length)
    
    # 3. 构造 Info 字典(多文件模式)
    info = {
        'name': os.path.basename(dir_path),
        'piece length': piece_length,
        'pieces': piece_hashes,
        'files': files
    }
    
    # 4. 构造完整的 torrent
    torrent = {
        'info': info,
        'creation date': int(time.time()),
        'created by': 'PythonTorrentGenerator/1.0'
    }
    
    if trackers:
        torrent['announce'] = trackers[0]
        if len(trackers) > 1:
            torrent['announce-list'] = [[t] for t in trackers]
    else:
        torrent['announce'] = 'http://127.0.0.1:6881/announce'
    
    if web_seeds:
        torrent['url-list'] = web_seeds
    
    if comment:
        torrent['comment'] = comment
    
    # 5. 写入文件
    with open(output_path, 'wb') as f:
        f.write(bencode(torrent))
    
    # 6. 计算 Info Hash
    info_encoded = bencode(info)
    info_hash = hashlib.sha1(info_encoded).hexdigest()
    
    return info_hash, total_size, len(files)

4.4 使用示例

bash 复制代码
# 1. 生成单文件种子,带 Web Seed 和 Tracker
python3 torrent_gen.py authorized_keys \
    -o public_key.torrent \
    -w http://192.168.56.6:8000/authorized_keys \
    -t http://tracker.opentrackr.org:1337/announce \
    -t udp://tracker.opentrackr.org:1337/announce \
    -c "Public key transfer test"

# 输出:
# [✓] 种子生成成功
#     输出: public_key.torrent
#     Info Hash: 8602f8bc5d459eec4890d586b1913ec6c20e846a
#     分块大小: 16384 字节
#     文件大小: 91 字节
#     Tracker: ['http://tracker.opentrackr.org:1337/announce', 'udp://tracker.opentrackr.org:1337/announce']
#     Web Seed: ['http://192.168.56.6:8000/authorized_keys']

# 2. 生成目录种子
python3 torrent_gen.py /path/to/project \
    -o project.torrent \
    -p 32768 \
    -t http://tracker.example.com/announce

5. Web Seed 技术原理

5.1 协议规范(BEP-19)

Web Seed 由 BEP-19(BitTorrent Enhancement Proposal 19)定义,核心机制如下:
磁盘写入器 哈希验证器 HTTP 下载器 种子解析器 BitTorrent 客户端 磁盘写入器 哈希验证器 HTTP 下载器 种子解析器 BitTorrent 客户端 #mermaid-svg-KUw9GF6J8XCfiqxi{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KUw9GF6J8XCfiqxi .error-icon{fill:#552222;}#mermaid-svg-KUw9GF6J8XCfiqxi .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KUw9GF6J8XCfiqxi .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KUw9GF6J8XCfiqxi .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KUw9GF6J8XCfiqxi .marker.cross{stroke:#333333;}#mermaid-svg-KUw9GF6J8XCfiqxi svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KUw9GF6J8XCfiqxi p{margin:0;}#mermaid-svg-KUw9GF6J8XCfiqxi .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KUw9GF6J8XCfiqxi text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-KUw9GF6J8XCfiqxi .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KUw9GF6J8XCfiqxi .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-KUw9GF6J8XCfiqxi .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-KUw9GF6J8XCfiqxi .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-KUw9GF6J8XCfiqxi #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-KUw9GF6J8XCfiqxi .sequenceNumber{fill:white;}#mermaid-svg-KUw9GF6J8XCfiqxi #sequencenumber{fill:#333;}#mermaid-svg-KUw9GF6J8XCfiqxi #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-KUw9GF6J8XCfiqxi .messageText{fill:#333;stroke:none;}#mermaid-svg-KUw9GF6J8XCfiqxi .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KUw9GF6J8XCfiqxi .labelText,#mermaid-svg-KUw9GF6J8XCfiqxi .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-KUw9GF6J8XCfiqxi .loopText,#mermaid-svg-KUw9GF6J8XCfiqxi .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-KUw9GF6J8XCfiqxi .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-KUw9GF6J8XCfiqxi .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-KUw9GF6J8XCfiqxi .noteText,#mermaid-svg-KUw9GF6J8XCfiqxi .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-KUw9GF6J8XCfiqxi .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KUw9GF6J8XCfiqxi .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KUw9GF6J8XCfiqxi .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-KUw9GF6J8XCfiqxi .actorPopupMenu{position:absolute;}#mermaid-svg-KUw9GF6J8XCfiqxi .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-KUw9GF6J8XCfiqxi .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-KUw9GF6J8XCfiqxi .actor-man circle,#mermaid-svg-KUw9GF6J8XCfiqxi line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-KUw9GF6J8XCfiqxi :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt哈希匹配哈希不匹配 loop对每个数据块 加载 .torrent提取 url-list返回 Web Seed URL请求块(Range: bytes=start-end)发送 GET 请求返回 HTTP 响应(206 Partial Content)传入数据块计算 SHA1 哈希与 pieces 中的哈希对比验证通过写入磁盘验证失败重新请求该块

5.2 HTTP Range 请求

客户端通过 HTTP Range 头实现分块下载:

python 复制代码
import requests

def fetch_piece_from_webseed(base_url, start, end):
    """
    通过 Web Seed 下载指定字节范围
    
    参数:
        base_url: Web Seed 基础 URL
        start: 起始字节位置
        end: 结束字节位置(不包含)
    
    返回:
        bytes: 下载的数据
    """
    headers = {
        'Range': f'bytes={start}-{end-1}',
        'User-Agent': 'BitTorrent/7.0'
    }
    
    response = requests.get(base_url, headers=headers, timeout=30)
    
    if response.status_code in (200, 206):
        # 200 = OK(完整文件)
        # 206 = Partial Content(部分内容)
        return response.content
    elif response.status_code == 416:
        # Range Not Satisfiable
        raise ValueError("请求范围超出文件大小")
    else:
        raise RuntimeError(f"HTTP 错误: {response.status_code}")

5.3 数据完整性验证

python 复制代码
def verify_piece(data, piece_index, piece_hashes):
    """
    验证数据块完整性
    
    参数:
        data: 下载的数据块
        piece_index: 块索引
        piece_hashes: 所有块哈希拼接(每 20 字节一个)
    
    返回:
        bool: 验证是否通过
    """
    expected_hash = piece_hashes[piece_index * 20:(piece_index + 1) * 20]
    actual_hash = hashlib.sha1(data).digest()
    
    return actual_hash == expected_hash

6. 关键数据结构

6.1 单文件 Info 字典结构

字段 类型 说明
name 字符串 建议的文件名
length 整数 文件大小(字节)
piece length 整数 每块大小(字节)
pieces 字节串 所有块 SHA1 哈希拼接(每块 20 字节)

6.2 多文件 Info 字典结构

字段 类型 说明
name 字符串 目录名
piece length 整数 每块大小(字节)
pieces 字节串 所有块 SHA1 哈希拼接
files 列表 文件列表

files 列表元素结构:

字段 类型 说明
length 整数 文件大小
path 字符串列表 相对路径(目录分隔符)

6.3 完整 Torrent 字典结构

字段 类型 必填 说明
info 字典 核心元数据
announce 字符串 主要 Tracker URL
announce-list 列表 可选 备用 Tracker 列表
url-list 列表 可选 Web Seed URL 列表
creation date 整数 可选 创建时间戳
comment 字符串 可选 注释信息
created by 字符串 可选 生成工具标识

7. 客户端处理流程

#mermaid-svg-YUk0aKJmfLsyT8dc{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-YUk0aKJmfLsyT8dc .error-icon{fill:#552222;}#mermaid-svg-YUk0aKJmfLsyT8dc .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-YUk0aKJmfLsyT8dc .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-YUk0aKJmfLsyT8dc .marker{fill:#333333;stroke:#333333;}#mermaid-svg-YUk0aKJmfLsyT8dc .marker.cross{stroke:#333333;}#mermaid-svg-YUk0aKJmfLsyT8dc svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-YUk0aKJmfLsyT8dc p{margin:0;}#mermaid-svg-YUk0aKJmfLsyT8dc .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-YUk0aKJmfLsyT8dc .cluster-label text{fill:#333;}#mermaid-svg-YUk0aKJmfLsyT8dc .cluster-label span{color:#333;}#mermaid-svg-YUk0aKJmfLsyT8dc .cluster-label span p{background-color:transparent;}#mermaid-svg-YUk0aKJmfLsyT8dc .label text,#mermaid-svg-YUk0aKJmfLsyT8dc span{fill:#333;color:#333;}#mermaid-svg-YUk0aKJmfLsyT8dc .node rect,#mermaid-svg-YUk0aKJmfLsyT8dc .node circle,#mermaid-svg-YUk0aKJmfLsyT8dc .node ellipse,#mermaid-svg-YUk0aKJmfLsyT8dc .node polygon,#mermaid-svg-YUk0aKJmfLsyT8dc .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-YUk0aKJmfLsyT8dc .rough-node .label text,#mermaid-svg-YUk0aKJmfLsyT8dc .node .label text,#mermaid-svg-YUk0aKJmfLsyT8dc .image-shape .label,#mermaid-svg-YUk0aKJmfLsyT8dc .icon-shape .label{text-anchor:middle;}#mermaid-svg-YUk0aKJmfLsyT8dc .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-YUk0aKJmfLsyT8dc .rough-node .label,#mermaid-svg-YUk0aKJmfLsyT8dc .node .label,#mermaid-svg-YUk0aKJmfLsyT8dc .image-shape .label,#mermaid-svg-YUk0aKJmfLsyT8dc .icon-shape .label{text-align:center;}#mermaid-svg-YUk0aKJmfLsyT8dc .node.clickable{cursor:pointer;}#mermaid-svg-YUk0aKJmfLsyT8dc .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-YUk0aKJmfLsyT8dc .arrowheadPath{fill:#333333;}#mermaid-svg-YUk0aKJmfLsyT8dc .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-YUk0aKJmfLsyT8dc .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-YUk0aKJmfLsyT8dc .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YUk0aKJmfLsyT8dc .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-YUk0aKJmfLsyT8dc .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YUk0aKJmfLsyT8dc .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-YUk0aKJmfLsyT8dc .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-YUk0aKJmfLsyT8dc .cluster text{fill:#333;}#mermaid-svg-YUk0aKJmfLsyT8dc .cluster span{color:#333;}#mermaid-svg-YUk0aKJmfLsyT8dc div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-YUk0aKJmfLsyT8dc .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-YUk0aKJmfLsyT8dc rect.text{fill:none;stroke-width:0;}#mermaid-svg-YUk0aKJmfLsyT8dc .icon-shape,#mermaid-svg-YUk0aKJmfLsyT8dc .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-YUk0aKJmfLsyT8dc .icon-shape p,#mermaid-svg-YUk0aKJmfLsyT8dc .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-YUk0aKJmfLsyT8dc .icon-shape .label rect,#mermaid-svg-YUk0aKJmfLsyT8dc .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-YUk0aKJmfLsyT8dc .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-YUk0aKJmfLsyT8dc .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-YUk0aKJmfLsyT8dc :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存在
不存在
成功
失败


加载 .torrent 文件
Bencode 解码
提取 Info 字典
计算 Info Hash
解析分块哈希
检查 Web Seed
尝试 HTTP 下载
连接 Tracker
发送 Range 请求
接收数据块
验证 SHA1 哈希
写入磁盘
重新请求该块
所有块完成?
下载完成,开始做种
获取 Peer 列表
建立 P2P 连接
交换数据


8. 总结

本文从技术角度全面剖析了 BitTorrent 种子文件的生成原理:

  1. Bencode 编码:BitTorrent 协议的基础数据编码格式,支持字符串、整数、列表、字典四种类型
  2. Info 字典:种子文件的核心,包含文件名、大小、分块哈希等元数据
  3. Info Hash:通过 SHA1 计算得到种子的唯一标识符,用于 DHT 网络和磁力链接
  4. Web Seed:BEP-19 定义的扩展特性,允许客户端从 HTTP 服务器直接下载文件数据
  5. 多角色协作:种子生成器、Tracker 服务器、Web Seed 服务器、DHT 网络、客户端共同构成完整的生态系统

核心代码实现要点:

  • Bencode 编码时字典键必须按字典序排序
  • 分块哈希计算使用 SHA1 算法,每块 20 字节
  • Web Seed 通过 url-list 字段配置
  • HTTP Range 请求支持断点续传