PHP如何从magnet链接获取Torrent文件列表及元数据
获取Magnet链接对应的Torrent文件列表与元数据的PHP实现
依赖准备
首先安装维护状态良好的Bencode解析库,用于处理Torrent的元数据格式:
composer require sandfoxme/bencode
核心实现代码
以下代码基于你已解析的Magnet数组,通过Tracker获取Peer列表后,从Peer请求Torrent元数据并提取文件列表:
<?php require 'vendor/autoload.php'; use SandFox\Bencode\Bencode; function getTorrentMetadataFromMagnet(array $magnetData) { // 提取Info Hash并转为二进制格式 $infoHashHex = str_replace('urn:btih:', '', $magnetData['xt']); $infoHash = hex2bin($infoHashHex); // 选取第一个可用Tracker $trackerUrl = $magnetData['tr'][0] ?? null; if (!$trackerUrl) { throw new Exception("无可用Tracker"); } // 解析Tracker地址 $trackerParts = parse_url($trackerUrl); if (!$trackerParts || !isset($trackerParts['scheme'], $trackerParts['host'], $trackerParts['port'])) { throw new Exception("Tracker地址无效"); } // 仅支持UDP协议Tracker,如需HTTP需扩展逻辑 if ($trackerParts['scheme'] !== 'udp') { throw new Exception("示例仅支持UDP类型Tracker"); } // 步骤1:与UDP Tracker建立连接 $socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP); socket_connect($socket, $trackerParts['host'], $trackerParts['port']); // 发送握手请求 $connectionId = pack('N2', 0x4172, 0x7101980); $transactionId = random_bytes(4); $handshake = $connectionId . pack('N', 0) . $transactionId; socket_send($socket, $handshake, strlen($handshake), 0); // 接收握手响应 $response = ''; socket_recv($socket, $response, 16, 0); if (strlen($response) < 16) { throw new Exception("握手响应无效"); } $responseParts = unpack('Naction/NtransactionId/NconnectionId', $response); if ($responseParts['transactionId'] !== unpack('N', $transactionId)[1]) { throw new Exception("事务ID不匹配"); } $connectionId = pack('N2', $responseParts['connectionId'] >> 32, $responseParts['connectionId'] & 0xFFFFFFFF); // 步骤2:发送Announce请求获取Peer列表 $peerId = random_bytes(20); $announceRequest = $connectionId . pack('N', 1) . $transactionId . $infoHash . $peerId . pack('N', 0) . pack('N', 0) . pack('N', 0) . pack('N', 0) . random_bytes(4) . pack('N', -1) . pack('n', 6881); socket_send($socket, $announceRequest, strlen($announceRequest), 0); // 解析Announce响应 $response = ''; socket_recv($socket, $response, 1024, 0); if (strlen($response) < 20) { throw new Exception("Announce响应无效"); } $announceParts = unpack('Naction/NtransactionId/Ninterval/Nleechers/Nseeders', substr($response, 0, 20)); if ($announceParts['transactionId'] !== unpack('N', $transactionId)[1]) { throw new Exception("事务ID不匹配"); } // 提取Peer列表 $peersData = substr($response, 20); $peers = []; for ($i=0; $i<strlen($peersData); $i+=6) { $ip = inet_ntop(substr($peersData, $i, 4)); $port = unpack('n', substr($peersData, $i+4, 2))[1]; $peers[] = "$ip:$port"; } socket_close($socket); // 步骤3:遍历Peer请求Metadata(BEP 9协议) foreach ($peers as $peer) { list($peerIp, $peerPort) = explode(':', $peer); try { $socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 5, 'usec' => 0]); socket_connect($socket, $peerIp, $peerPort); // 发送BitTorrent握手包 $handshake = chr(19) . 'BitTorrent protocol' . str_repeat(chr(0), 8) . $infoHash . $peerId; socket_send($socket, $handshake, strlen($handshake), 0); // 验证Peer握手响应 $response = ''; socket_recv($socket, $response, 68, 0); if (strlen($response) < 68 || substr($response, 0, 20) !== chr(19) . 'BitTorrent protocol') { socket_close($socket); continue; } // 请求Metadata分片 $metadataReq = Bencode::encode(['msg_type' => 0, 'piece' => 0]); $msg = pack('N', strlen($metadataReq)+1) . chr(20) . $metadataReq; socket_send($socket, $msg, strlen($msg), 0); // 拼接完整Metadata $metadata = ''; while (true) { $lengthBuf = ''; socket_recv($socket, $lengthBuf, 4, 0); if (strlen($lengthBuf) < 4) break; $length = unpack('N', $lengthBuf)[1]; if ($length === 0) break; $msgContent = ''; socket_recv($socket, $msgContent, $length, 0); if ($msgContent[0] !== chr(20)) continue; $extMsg = Bencode::decode(substr($msgContent, 1)); if ($extMsg['msg_type'] === 1) { $metadata .= $extMsg['piece']; if (strlen($metadata) === $extMsg['total_size']) { break; } // 请求下一个分片 $nextReq = Bencode::encode(['msg_type' => 0, 'piece' => count(explode(chr(0), $metadata))]); $nextMsg = pack('N', strlen($nextReq)+1) . chr(20) . $nextReq; socket_send($socket, $nextMsg, strlen($nextMsg), 0); } } socket_close($socket); if ($metadata) { return Bencode::decode($metadata); } } catch (Exception $e) { socket_close($socket); continue; } } throw new Exception("无法从任何Peer获取Metadata"); } // 使用示例 $magnetParsed = [ 'xt' => 'urn:btih:12cb31ae6fd4624fe6be485c...', 'dn' => 'Son.of.a.Critch.S0...', 'tr' => [ 'udp://example.com:80/announce', 'udp://example2.com:6969/announce', ] ]; try { $torrentMetadata = getTorrentMetadataFromMagnet($magnetParsed); // 提取文件列表 $files = []; if (isset($torrentMetadata['info']['files'])) { // 多文件Torrent foreach ($torrentMetadata['info']['files'] as $file) { $files[] = implode('/', $file['path']) . ' (' . $file['length'] . ' 字节)'; } } else { // 单文件Torrent $files[] = $torrentMetadata['info']['name'] . ' (' . $torrentMetadata['info']['length'] . ' 字节)'; } echo "文件列表:\n"; foreach ($files as $file) { echo "- $file\n"; } // 可选:输出完整元数据 // print_r($torrentMetadata); } catch (Exception $e) { echo "错误: " . $e->getMessage() . "\n"; } ?>
关键说明
- 代码仅实现UDP Tracker和TCP Peer的基础逻辑,如需支持HTTP Tracker或DHT网络,需扩展对应模块
- 依赖
sandfoxme/bencode处理Torrent特有的Bencode编码格式,该库为当前PHP生态中维护最活跃的同类库 - 部分Peer可能不支持Metadata Exchange(BEP 9)协议,代码会自动跳过此类Peer尝试下一个
内容的提问来源于stack exchange,提问作者Richard
相关产品推荐
相关产品推荐

