bencode是bittorrent专用序列化格式,以d/l/i/数字:e编码字典、列表、整数和字符串,无json兼容性;需手写递归下降解析器或使用tiny-bencode等专用库,关键在于保留原始字节(如info子串算info_hash)。

什么是Bencode,为什么不能直接用JSON库解析
Bencode是BitTorrent协议专用的序列化格式,不是JSON、XML或YAML。它用d表示字典、l表示列表、i表示整数、数字:e表示字符串(如5:hello)。没有引号、没有布尔值、没有浮点数,且字典键必须是字符串且按字节序排序。用jsoncpp或nlohmann/json强行解析会直接报错或静默失败——因为开头就是d,根本不是合法JSON。
- Bencode不校验UTF-8,字符串是纯字节序列,
std::string可直接承载 - 整数范围是任意精度有符号整数,但实际种子文件中基本都在
int64_t范围内 - 字典键重复时以后出现者为准(规范未明确定义,主流实现如libtorrent覆盖前值)
手写递归下降解析器的关键三步
核心思路:用一个const char*指针单向扫描,每层函数负责消费一段合法Bencode并返回解析结果,同时更新指针位置。
- 遇到
d:循环读键值对,每次先解析键(必须是string),再解析对应值;遇到e结束 - 遇到
l:循环解析元素直到e;元素类型由下一个字符决定(d/l/i/数字) - 遇到
i:跳过i,读直到e,用std::strtoll转整数(注意检查endptr和溢出) - 遇到数字(如
12:abc...):先读冒号前的十进制长度,再截取对应字节数作为std::string(不null终止,不假设可打印)
示例片段(简化版整数解析):
int64_t parse_int(const char*& p) {
if (*p != 'i') throw std::runtime_error("expected 'i'");
++p;
char* end;
int64_t val = std::strtoll(p, &end, 10);
if (end == p || *end != 'e') throw std::runtime_error("invalid int format");
p = end + 1; // skip 'e'
return val;
}
解析种子文件时必须处理的特殊字段
.torrent文件本质是Bencode字典,但结构固定。只关注关键路径即可,不用全量建模:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
-
info字典:所有元数据核心,其序列化二进制(非解析后内容)的SHA-1哈希即info_hash -
info内name:文件或目录名,原始字节,可能含空格/非ASCII -
info内files(多文件)或length(单文件):决定下载内容大小 -
announce:Tracker URL,需保留原始编码(不要擅自UTF-8 decode) -
encoding字段极少出现,可忽略;无此字段时,name等字符串按字节流处理
常见坑:
- 把
info字典整个反序列化成C++对象再算hash → 错!必须用原始Bencode字节(从info开始到对应e结束的子串) - 用
std::stoi解析大整数(如length超2GB)→ 溢出,改用std::strtoll或boost::multiprecision - 忽略
pieces字段是二进制字符串(每20字节一个piece hash)→ 它是20:xxx...xxx形式,长度必被20整除
推荐轻量级方案:用现成parser避免重复造轮子
自己写完整parser容易漏边界(如嵌套过深、超长字符串、无效字符)。更稳妥的做法是集成已验证的C++库:
-
libtorrent(lt::bdecode()):最成熟,但头文件依赖多,链接重 -
tiny-bencode(header-only):仅几百行,支持std::variant返回值,无外部依赖 - 手动提取+正则?不行。Bencode是递归结构,正则无法正确匹配嵌套
d/e
使用tiny-bencode示例:
#include "bencode.h"
auto data = bencode::decode(std::ifstream("test.torrent").rdbuf());
auto info = data.get_dict().at("info"); // throws if missing
std::string info_bin = bencode::encode(info); // 原始字节,用于算info_hash
真正麻烦的从来不是“怎么解析”,而是后续怎么用——比如pieces要拆成vectorsha1_hash,files要按path数组拼出完整相对路径。这些逻辑不在Bencode本身,但没它们,解析出来也没法下种。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










