1. 引言

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

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


2. BitTorrent 系统架构

2.1 核心角色

BitTorrent 网络由以下几个核心角色协作完成文件传输:

客户端层

数据服务层

内容发布层

文件源

种子生成器

.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 服务器 种子生成器 1. 读取文件,计算分块哈希 2. 构造 Info 字典,生成 .torrent 3. 部署文件到 HTTP 服务器 4. 分发 .torrent 文件 5. 解析 .torrent,获取元数据 6. 发起 HTTP GET 请求 7. 返回文件数据 8. 验证 SHA1 哈希 9. 写入磁盘,完成下载 10. 注册为 Seed(做种者) 11. 发布 announce_peer 12. 获取 .torrent 文件 13. 查询 Peer 列表 14. 返回 ClientA 地址 15. 建立连接,下载数据

3. 种子文件技术规范

3.1 Bencode 编码

Bencode 是 BitTorrent 协议使用的编码格式,设计目标简单、高效、易于解析。支持四种数据类型:

Bencode 数据类型

字符串
{长度}:{内容}

示例: 4:spam

整数
i{数字}e

示例: i42e

列表
l{元素}e

示例: l4:spam4:eggse

字典
d{键值对}e

示例: d3:foo3:bare

3.2 Info 字典结构

Info 字典是种子文件的核心,包含了文件的完整元数据:

Info 字典

name: 文件名

length: 文件大小(字节)

piece length: 每块大小

pieces: 所有块 SHA1 哈希拼接

块 1 哈希
20 字节

块 2 哈希
20 字节

块 N 哈希
20 字节

Info Hash 计算过程:

Info 字典

Bencode 编码

SHA1 哈希

Info Hash
20 字节/40 位十六进制

磁力链接标识

DHT 网络索引键

Tracker 查询标识

3.3 完整种子文件结构

.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 编码器

#!/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 单文件种子生成器

#!/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 多文件(目录)种子生成器

#!/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 使用示例

# 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 客户端 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 头实现分块下载:

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 数据完整性验证

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. 客户端处理流程

存在

不存在

成功

失败

加载 .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 请求支持断点续传
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐