You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:33:11