首页 / 资讯中心 / 文章详情

PHP实现BT种子转磁力链接:bencode解码与info哈希计算详解

PHP实现BT种子转磁力链接:bencode解码与info哈希计算详解 ★ FEATURED ARTICLE
简介这份资源面向有一定PHP基础的开发者与文件分享系统搭建者聚焦BT种子与磁力链接之间的格式转换问题。包内共5个文件以4个PHP源码文件与1份Markdown说明文档为主压缩包约3KB体量轻巧便于直接嵌入现有项目或作为独立工具脚本使用。源码围绕种子文件解析、Info Hash计算、磁力链接拼接与Tracker地址处理等环节展开配套说明文档可辅助理解各模块职责与调用方式适合用来学习DHT与BitTorrent元数据的处理思路。已有4101人学习下载说明其在PHP磁力链接转换这一细分场景中具备一定参考价值。读者可从中获得一套可运行的转换脚本、种子B编码解析的实现参考以及构建magnet链接时参数组织的具体做法便于快速验证与二次开发。1. 一个种子文件丢进去磁力链接吐出来手里攒了一堆.torrent文件想分享给别人的时候才发现发文件不如发一串磁力链接省事。种子文件几十 KB 到几 MB 不等传起来麻烦还容易被中间环节改坏磁力链接就是一行文本复制粘贴就能用接收方拿到后由客户端自己去 DHT 网络里找资源。这个项目干的就是这件事——用 PHP 把 BT 种子文件解析成磁力链接。它适合两类人一是手上有一批种子想批量转成磁力链接做归档的开发者二是想搞清楚 BT 种子二进制结构、拿 PHP 练手做解析器的人。整个项目不依赖数据库、不依赖扩展核心就是 bencode 解码加 SHA1 计算丢到任意支持 PHP 的环境里就能跑属于那种“看着小、拆开全是细节”的类型。2. 项目定位与技术选型为什么是 PHP 加纯手写解析2.1 这个项目到底解决什么问题BT 种子文件.torrent本质是一个 bencode 编码的字典里面装着 tracker 地址、文件分块信息、文件名、分块哈希等元数据。磁力链接Magnet URI则是把这些关键信息压缩进一行magnet:?xturn:btih:...的文本里其中最核心的是btih参数也就是种子信息的 SHA1 摘要。转换过程听起来简单读出种子里的info字典对它做一次 bencode 编码算 SHA1拼成链接。但真正动手写就会撞上一堆问题——bencode 是二进制安全的字符串长度按字节算PHP 里稍不注意就会被多字节字符或者trim搞坏info字典的边界必须精确截取多一个字节少一个字节算出来的哈希就完全对不上。这个项目的价值就在于把这些细节封装好了。它不追求做成一个 Web 服务或者命令行工具而是一个可以直接嵌入其他 PHP 项目的转换库/脚本传入种子文件路径或二进制内容返回磁力链接字符串。对于需要做种子归档、资源索引、链接分享的开发者来说这就是一块能直接搬走的砖。2.2 技术栈与运行环境项目是纯 PHP 实现没有 Composer 依赖也没有要求任何 PHP 扩展sha1是 PHP 内置函数bencode 解析全部手写。这意味着它的部署门槛极低PHP 5.6 以上基本都能跑PHP 7/8 更没问题。常见做法是把项目解压到 Web 根目录通过浏览器访问入口文件或者直接在命令行用php执行转换脚本。项目要素说明语言PHP纯原生无框架核心依赖无第三方库仅用内置sha1、字符串函数输入.torrent文件路径或二进制内容输出magnet:?xturn:btih:40位SHA1形式的链接运行方式Web 访问或 CLI 执行数据存储无纯内存计算选 PHP 做这件事优势不在性能而在“随手可用”。很多资源站、采集脚本、后台管理系统本身就是 PHP 写的转换逻辑直接内嵌进去不用再起一个 Python 服务或者调外部命令。代价是 PHP 处理二进制字符串时要格外小心这也是后面代码讲解里反复强调的点。2.3 目录结构与文件职责解压后项目结构很扁平典型形态如下bt2magnet/ ├── index.php # Web 入口处理上传/表单提交 ├── Bencode.php # bencode 解码器核心解析类 ├── Torrent.php # 种子解析与磁力链接生成 └── README.md # 使用说明Bencode.php负责把二进制字符串递归解析成 PHP 数组/字符串/整数Torrent.php在解码结果基础上定位info字典、重新编码、计算哈希index.php是薄薄一层交互壳。这种分层的好处是解析逻辑和界面完全解耦你想做成 CLI、API 还是批量脚本只改入口不动核心。提示不同下载包里文件名可能略有差异比如合并成单个functions.php但职责划分基本一致读代码时先找“解码”和“算哈希”两个函数即可。3. 核心模块拆解bencode 解码与 info 哈希的完整链路3.1 bencode 编码规则回顾与 PHP 实现要点bencode 只有四种数据类型字符串长度:内容、整数i数字e、列表l...e、字典d...e。字符串长度是字节数不是字符数所以 PHP 里必须用strlen而不是mb_strlen。解析器通常用一个“游标”在字符串上滑动每读一个类型就推进游标直到整个结构读完。下面是一个精简但完整的解码实现逻辑和项目里的核心类一致?php class Bencode { private $data; private $pos 0; public function decode($data) { $this-data $data; $this-pos 0; return $this-parseValue(); } private function parseValue() { $char $this-data[$this-pos]; if ($char i) { return $this-parseInt(); } elseif ($char l) { return $this-parseList(); } elseif ($char d) { return $this-parseDict(); } else { return $this-parseString(); } } private function parseInt() { $this-pos; // 跳过 i $end strpos($this-data, e, $this-pos); $num substr($this-data, $this-pos, $end - $this-pos); $this-pos $end 1; return (int)$num; } private function parseString() { $colon strpos($this-data, :, $this-pos); $len (int)substr($this-data, $this-pos, $colon - $this-pos); $this-pos $colon 1; $str substr($this-data, $this-pos, $len); $this-pos $len; return $str; } private function parseList() { $this-pos; // 跳过 l $list []; while ($this-data[$this-pos] ! e) { $list[] $this-parseValue(); } $this-pos; // 跳过 e return $list; } private function parseDict() { $this-pos; // 跳过 d $dict []; while ($this-data[$this-pos] ! e) { $key $this-parseString(); $dict[$key] $this-parseValue(); } $this-pos; // 跳过 e return $dict; } }这段代码的关键在$this-pos这个游标所有解析函数共享它读多少推进多少保证二进制流按顺序消费。parseString里substr的第三个参数是字节长度正好符合 bencode 规范。parseDict先读 key一定是字符串再读 value递归下去。参数说明decode($data)接收完整种子二进制内容返回嵌套数组$pos是类内状态每次decode前重置避免多次调用串位。3.2 定位 info 字典并重新编码拿到解码后的数组磁力链接需要的是info这个子字典的 SHA1。注意不能对解码后的 PHP 数组做serialize或json_encode再算哈希必须把info字典按 bencode 规则原样重新编码字节序列和种子里完全一致哈希才对得上。所以项目里除了解码器还需要一个编码器或者更稳妥的做法——在解码时记录info字典在原始二进制中的起止偏移直接截取原始字节。常见做法是后者因为重新编码容易在字典 key 排序、整数格式上出偏差。实现思路解析到顶层字典的infokey 时记下 value 开始的位置解析完这个 value 后记下结束位置substr截出来就是原始字节。?php // 在 parseDict 中针对顶层 info 做偏移记录简化示意 private function parseDictWithInfoOffset() { $this-pos; // d $dict []; while ($this-data[$this-pos] ! e) { $key $this-parseString(); if ($key info) { $start $this-pos; // info value 起始偏移 $dict[$key] $this-parseValue(); $end $this-pos; // info value 结束偏移 $this-infoRaw substr($this-data, $start, $end - $start); } else { $dict[$key] $this-parseValue(); } } $this-pos; return $dict; }$start和$end夹住的正是info字典的原始 bencode 字节$this-infoRaw保存下来后直接sha1($this-infoRaw)即可。参数说明$start在读完 key 之后、解析 value 之前取$end在 value 解析完、游标停在下一位时取两者之差就是字节长度。这一步是整个项目最容易翻车的地方偏移差一个字节哈希就全错而且不会报错只会生成一个“看起来正常但没人能下载”的链接。3.3 生成磁力链接与附加参数有了 40 位 SHA1拼链接就是字符串操作?php function buildMagnet($infoHash, $name , $trackers []) { $magnet magnet:?xturn:btih: . $infoHash; if ($name ! ) { $magnet . dn . rawurlencode($name); } foreach ($trackers as $tr) { $magnet . tr . rawurlencode($tr); } return $magnet; }dn是显示名称取自种子里的name字段tr是 tracker 列表取自announce和announce-list。rawurlencode不能省tracker 地址里的://和不编码会破坏链接结构。参数说明$infoHash是 40 位十六进制字符串小写即可客户端不区分大小写$name建议原样传入编码交给函数$trackers传数组循环拼接。实际项目里还会判断announce-list是否存在存在就展开成多个tr不存在就退回单个announce。3.4 入口层如何串起整个流程index.php的职责很薄接收上传文件或表单里的种子内容调用解码器取infoRaw算哈希拼链接输出。Web 场景下注意upload_max_filesize和post_max_size要大于种子文件大小一般几 MB 足够CLI 场景直接file_get_contents($argv[1])。批量转换时把核心逻辑抽成函数循环调用即可不要在循环里反复 new 解码器导致状态残留——每次decode前重置$pos就是这个原因。4. 跑起来与批量改造从单文件转换到脚本化处理4.1 环境准备与最小可运行步骤先把压缩包解压到本地目录确认 PHP 可用php -v # 期望输出 PHP 7.x 或 8.x如果走 Web 方式把目录放到 Web 服务器根目录下浏览器访问index.php上传种子文件即可看到磁力链接。如果走 CLI写一个最小脚本调用核心类php -r require Bencode.php; require Torrent.php; $t new Torrent(); echo $t-toMagnet(demo.torrent), PHP_EOL; 这里demo.torrent换成你手上的种子文件名。Torrent类内部会读文件、解码、截取info、算哈希、拼链接。跑通的标准很简单输出的链接以magnet:?xturn:btih:开头后面跟 40 位十六进制复制到任意 BT 客户端里能识别出文件名和大小就说明哈希算对了。注意如果客户端显示“无法识别”或文件名乱码优先怀疑info偏移截取错了而不是编码问题。拿一个已知正确的种子做对照用sha1sum对info原始字节单独验证一次能快速定位。4.2 批量转换脚本怎么写单文件转换跑通后批量处理是自然需求。核心是把“读文件 → 转换 → 输出”包成循环加上错误处理避免一个坏种子中断整批?php require Bencode.php; require Torrent.php; $dir __DIR__ . /torrents; $out []; foreach (glob($dir . /*.torrent) as $file) { try { $t new Torrent(); $magnet $t-toMagnet($file); $out[basename($file)] $magnet; echo basename($file), , $magnet, PHP_EOL; } catch (Exception $e) { // 单个文件失败不影响后续 fwrite(STDERR, basename($file) . 转换失败: . $e-getMessage() . PHP_EOL); } } file_put_contents(__DIR__ . /magnets.txt, implode(PHP_EOL, $out));glob收集目录下所有种子try/catch隔离单文件异常结果同时打印和落盘。参数说明$dir改成你的种子目录magnets.txt是汇总输出一行一个链接方便后续导入其他系统。批量场景下建议给每个文件单独 new 一个Torrent实例避免解码器内部游标状态互相污染——这是血泪经验早期图省事复用实例结果第二个文件开始哈希全错。4.3 输出格式与下游对接转换结果最常见的下游是资源索引站、采集脚本或数据库。磁力链接本身是纯文本落库时字段长度给 255 足够带多个 tracker 可能到几百字符给 512 更稳。如果要做去重直接对btih部分建唯一索引比整条链接去重更可靠因为dn和tr参数可能不同但指向同一资源。对接 API 时把链接包成 JSON 返回注意JSON_UNESCAPED_SLASHES让链接里的/不被转义可读性更好。5. 避坑与排查哈希对不上、乱码、大文件失败的现场记录5.1 生成的链接客户端不认哈希对不上现象链接格式完全正确40 位十六进制也有但客户端解析后找不到资源或者显示的文件名和实际不符。原因几乎都是info字典截取偏移错了——多截了结尾的e或者少截了开头。解决用已知正确的种子做基准单独把infoRaw写进文件用系统sha1sum算一遍和 PHP 的sha1结果对比再检查$start是否在读完 key 后取、$end是否在 value 解析完取。这个排查顺序能省掉大量瞎猜。5.2 文件名或 tracker 出现乱码现象磁力链接里的dn参数解码后是乱码。原因种子里的name字段是 UTF-8 字节但部分老种子用 GBK 编码直接rawurlencode后客户端按 UTF-8 解就花了。解决先检测编码常见做法是用mb_detect_encoding判断非 UTF-8 的用mb_convert_encoding转一次再编码。注意转换只影响dn显示不影响btih哈希所以可以放心处理。5.3 大种子文件上传失败或内存爆掉现象Web 上传几十 MB 的种子直接报错或者 PHP 进程内存超限。原因upload_max_filesize、post_max_size、memory_limit三个配置任一不够都会挂另外解码器把整个文件读进内存再递归解析超大种子含大量文件列表会放大内存占用。解决调高这三个配置批量场景改用 CLI 绕开上传限制极端情况下可以流式解析但一般种子几 MB 以内调配置足够。5.4 批量转换到一半结果全错现象第一个文件转换正确后面全部哈希错误。原因解码器实例被复用$pos游标没重置第二次decode从上次结束位置开始读。解决每次转换 new 一个新实例或者在decode入口强制$this-pos 0。这个坑很隐蔽因为不报错只是结果错建议在类里加一行重置作为防御。5.5 带announce-list的种子只生成了一个 tracker现象磁力链接里只有一个tr但种子里明明有多个 tracker。原因代码只读了announce字段没处理announce-list这个二维数组。解决判断announce-list是否存在存在就双层循环展开每个 tracker 拼一个tr不存在再退回announce。注意announce-list的结构是[[url1], [url2, url3]]内层可能多个别只取第一层。6. 二次开发把转换器改成可复用的服务组件拿到这个项目后我一般不会停在“能转就行”而是把它往两个方向改一是把核心逻辑抽成无状态函数或服务类方便嵌入现有系统二是加上校验和缓存避免重复计算。具体做法是新建一个MagnetService构造函数只接收配置是否带 tracker、是否转编码convert($fileOrContent)方法内部完成解码、截取、哈希、拼接返回结构化数组而不是裸字符串?php class MagnetService { public function convert(string $torrentPath): array { $raw file_get_contents($torrentPath); $decoder new Bencode(); $meta $decoder-decode($raw); $infoHash sha1($decoder-infoRaw); return [ hash $infoHash, name $meta[info][name] ?? , magnet buildMagnet($infoHash, $meta[info][name] ?? ), ]; } }返回数组的好处是下游可以只取hash做去重、取name做展示、取magnet做分享各取所需。验证方法也简单拿同一批种子分别走原脚本和新服务逐条比对hash是否一致一致就说明重构没改变行为。再进一步可以加一层文件级缓存以种子文件的sha1为 key 存转换结果批量重复处理时能省掉重复解码。改造时有个习惯我一直保持任何涉及二进制偏移的改动改完必须拿至少三个不同来源的种子回归一遍其中一个带多文件列表、一个带announce-list、一个文件名含中文。因为这类 bug 不会抛异常只会安静地生成错误链接等用户反馈回来再查成本太高。从那以后我每次动解析层都强制走一遍这个三件套回归希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站