Bencode是BitTorrent协议定义的轻量序列化格式,仅含string、integer、list、dictionary四种类型,字符串带长度前缀(如4:spam),无空白容忍,不可用JSON库解析;手写解析需跳过空白、识别首字符、递归下降,关键在正确提取info原始字节以计算info_hash。

什么是Bencode,为什么不能直接用JSON库解析
Bencode是BitTorrent协议定义的轻量序列化格式,不是JSON、XML或YAML。它只有四种类型:string、integer、list、dictionary,且无空格容忍、无类型后缀、字符串带长度前缀(如 4:spam 表示字符串 "spam")。用jsoncpp或nlohmann/json强行读会直接崩溃或返回空——因为开头就是数字或字母,根本不是合法JSON。
手写解析器的关键三步:跳过空白、识别首字符、递归下降
标准Bencode无空白要求,但实际种子文件常含换行/空格(尤其comment字段),必须跳过;首字符决定类型:d→字典,l→列表,i→整数,数字→字符串。解析函数必须返回解析后对象 + 新的读取位置(避免拷贝子串)。
-
parse_string:读到冒号后,按前面数字提取对应长度字节,不校验UTF-8 -
parse_int:跳过i,读到e为止,支持负数(如i-42e) -
parse_dict:循环调用parse_string(key)和任意类型解析(value),直到遇到e
示例片段(简化):
std::pair<BValue, size_t> parse(const std::string& s, size_t pos) {
while (pos < s.size() && std::isspace(s[pos])) ++pos;
if (pos >= s.size()) throw std::runtime_error("unexpected EOF");
switch (s[pos]) {
case 'i': return parse_int(s, pos);
case 'l': return parse_list(s, pos);
case 'd': return parse_dict(s, pos);
default: return parse_string(s, pos); // 数字开头即string
}
}
解析种子文件时必须处理的特殊字段和陷阱
Torrent文件是Bencode字典,但关键字段有隐含规则:比如info字典必须先序列化为Bencode再计算SHA-1(不是对原始JSON或结构体哈希),而pieces是二进制字符串(每个piece 20字节,拼起来是固定长度的hash串,不是base32/base64)。常见坑:
立即学习“C++免费学习笔记(深入)”;
- 误把
pieces当普通字符串解析后直接.c_str()——它含\0,std::string构造正确,但打印会截断 -
encoding字段存在时,需用其指定编码(如UTF-8)解码name等字段,但多数种子没设该字段,默认UTF-8 -
creation date是Unix时间戳(整数),不是ISO字符串;private是整数0/1,不是布尔值
推荐用现成库还是自己写
如果只解析种子文件(非完整BT客户端),用libtorrent的entry类最省事:entry e = bdecode(data),然后e["info"]["name"].string()即可。但若嵌入资源受限环境(如嵌入式)、或需深度定制(如跳过info只读announce),手写轻量解析器更可控——核心逻辑200行内可覆盖99%种子文件。注意:不要尝试兼容所有Bencode变种(如带浮点扩展的非标格式),标准BitTorrent种子严格遵循原始spec。
真正麻烦的是info字典的原始Bencode字节提取——必须从解析器中暴露未解析的原始子串,否则无法正确计算info_hash。这点几乎所有简易解析器都会忽略。


















