与Torrent相关:UDP协议下的 Tracker 响应(更新#3-可用)

5
更新 #4:添加了演示 Java 代码片段,用于处理 UDP 并发送通告消息(记得首先进行连接!)请查看下面的自己的响应。

====================================================

更新#3:我成功使其工作,下面介绍的doConnect()方法是正确的,在我的回复中有更多信息。

====================================================

我主要关心的是当announce url的协议为UDP时,如何下载跟踪器响应。
细节: 以下是一些有效种子文件的announce url(第一个是主要的)。
http://tracker.torrentbox.com:2710/announce
udp://elbitz.net:80/announce.php?passkey=362fc69de3402e8ef5794c7ecf7c58d4
udp://tracker.podtropolis.com:2711/announce

如果协议是HTTP,一切都很顺利,这就是我的工作方式:
String fullUrl = announceURL + "?info_hash=" + this.m_TorrentInfoHashAsURL + .. // i add the params
URL url = new URL(fullUrl);
URLConnection connection = url.openConnection();
InputStream is = connection.getInputStream();
.. //reading the stream

如果协议是UDP,URL构造函数会抛出“java.net.MalformedURLException: unknown protocol: udp”错误。因此,我猜问题可以归结为以下内容:如何从UDP协议的URL下载资源?(希望简单明了,看不到数据报stuff)。更新#1:我在网上进行了更多调查,并得出了下面粘贴的结构(应该有效...但实际上没有,我的意思是本地可以,但与真正的跟踪器不兼容)。规格链接:http://www.bittorrent.org/beps/bep_0015.html。例如:这是我创建套接字的方式,但在有效的跟踪器上,我从未收到任何响应,因此某些内容不起作用。
if full url: udp://elbitz.net:80/announce.php?passkey=362fc69de3402e8ef5794c7ecf7c58d4
this.m_TrackerHost: elbitz.net 
this.m_TrackerPort: 80

private DatagramSocket m_WorkingSocket;
    private DatagramSocket getWorkingSocket() {
        Logger.d(TAG, "getWorkingSocket()");

        if(this.m_WorkingSocket==null){
            Random rnd = new Random();
            for (int i = 0; i < 100; i++) {
                try {
                    int port = 15000 + rnd.nextInt(15000); // [15000-30000)
                    DatagramSocket result = new DatagramSocket(port);
                    InetAddress trackerAddress = InetAddress.getByName(this.m_TrackerHost);
                    result.connect(trackerAddress, this.m_TrackerPort);
                    this.m_WorkingSocket = result;
                } catch (SocketException se) {
                    Logger.w(TAG, "getWorkingSocket() - port is taken");
                } catch (SecurityException se) {
                    Logger.w(TAG, "getWorkingSocket() - port is blocked?");
                } catch (UnknownHostException e) {
                    Logger.w(TAG, "getWorkingSocket() - unkwnown host?");
                }
            }
        }

        return this.m_WorkingSocket;
    }

& 现在是doConnect的全部代码,这应该是第一个通信阶段(下一步是announce .. 那里有类似的代码)

private boolean doConnect() throws IOException{
    Logger.d(TAG, "doConnect()");

    DatagramSocket workingSocket = this.getWorkingSocket();
    DatagramPacket sendPacket = null, receivePacket = null;

    byte[] sendData = null;
    byte[] receiveData = null;
    int round = 0;

    Logger.d(TAG, "doConnect(): first round, timeout: " + this.getTimeoutInMillis(ACTION_ID_CONNECT, round));
    while(true) {
        if(round==8){
            Logger.w(TAG, "doConnect() - failed to connect with tracker, consumed in vain all 8 rounds..");
            return false;
        }

        workingSocket.setSoTimeout(this.getTimeoutInMillis(ACTION_ID_CONNECT, round));

        if(receivePacket==null){
            /*
            Offset  Size            Name            Value
            0       32-bit integer  action          0 // connect
            4       32-bit integer  transaction_id
            8       64-bit integer  connection_id
            16  */
            receiveData = new byte[16]; //CONNECT: at least 16 bytes
            receivePacket = new DatagramPacket(receiveData, receiveData.length);

            sendData = this.getConnectRequest();//return byte[] with everything..just like in specs
            sendPacket = new DatagramPacket(sendData, sendData.length); 
        }

        try {
            Logger.d(TAG, "doConnect() - sending connect data: " + (Arrays.toString(sendData)));
            workingSocket.send(sendPacket);
            workingSocket.receive(receivePacket);
            break;
        } catch (SocketTimeoutException ste) {
            round ++;
            Logger.w(TAG, "doConnect() connect - new round: " + (round+1) + "|timeout: " + this.getTimeoutInMillis(ACTION_ID_CONNECT, round));
            continue;
        }
    }

    byte[] connectResponse = receivePacket.getData();
    int actionIdFromResponse = Utils.byteArrayToInt(Utils.subArray(connectResponse, 0, 4));
    int transactionIdFromResponse = Utils.byteArrayToInt(Utils.subArray(connectResponse, 4, 4));
    long connectionIdFromResponse = Utils.byteArrayToLong(Utils.subArray(connectResponse, 8, 8));

    if(transactionIdFromResponse!=this.m_TransactionId){
        Logger.w(TAG, "doConnect() - received different transactionId");
        return false;
    }

    if(actionIdFromResponse!=ACTION_ID_CONNECT){
        Logger.w(TAG, "doConnect() - didnt received ACTION_ID_CONNECT");
        return false;
    }

    //store connectionId
    this.m_ConnectionId = connectionIdFromResponse;
    return true;
}

问题仍然存在...我从追踪器那里没有收到任何回应(尝试了其他网址) 新问题:当完整的url包含更多信息(例如/announce)时,在elbitz.net,端口80上创建套接字是否可以?
更新#2 上面的代码似乎工作正常...我在谷歌上找到了一个实现此规范的追踪器列表,哇哦,回应发生了(例如:“udp://tracker.openbittorrent.com:80”)
新问题,规范在这里:http://www.bittorrent.org/beps/bep_0015.html - 我似乎看不到如何获取对等方列表? 在向种子追踪器的正常请求中(通过http),有两种情况:正常响应(bencoded映射)和压缩响应(以二进制形式)。 那么现在对等方列表在哪里?
从规范中可以看出,这是发布响应:
/*
              Offset      Size            Name            Value
              0           32-bit integer  action          1 // announce
              4           32-bit integer  transaction_id
              8           32-bit integer  interval
              12          32-bit integer  leechers
              16          32-bit integer  seeders
              20 + 6 * n  32-bit integer  IP address
              24 + 6 * n  16-bit integer  TCP port
              20 + 6 * N  */
从我的测试中,我总是收到相同的字段值:IP地址和TCP端口... 再加上每个请求只有一个响应..所以这肯定不是它!我需要一个对等方列表!
请帮忙!我还没有实现的响应消息类型是扫描和错误...但是没有一个包含我感兴趣的信息(对等方信息:IP和端口) 例如:扫描
Offset      Size            Name            Value
0           32-bit integer  action          2 // scrape
4           32-bit integer  transaction_id
8 + 12 * n  32-bit integer  seeders
12 + 12 * n 32-bit integer  completed
16 + 12 * n 32-bit integer  leechers
8 + 12 * N
2个回答

3

我回答自己的问题...好吧,谢谢我自己!

  • 如果以udp://开头,则为有效地址。
  • 如果url是:udp://elbitz.net:80/announce.php?passkey=362fc69de3402e8ef5794c7ecf7c58d4,则在udp://elbitz.net上创建套接字,使用端口80(/announce部分在创建UDP套接字时不使用)。
  • 由于它是UDP,因此不能保证响应(不会抛出异常,您只需等待),规格提到了最大8轮规则...但这意味着要等待相当长的时间才能意识到跟踪器已停止运行。
  • 在使用UDP进行通信的情况下,您发送两种类型的请求(Connect和Announce,每个请求都作为1个UDP数据包),并收到1个响应(显然仅当您很幸运时,并且也作为UDP数据包)。 )..为了提取可用信息,您必须解析响应byteBuffer。再次强调,真正的信息在这里:http://www.bittorrent.org/beps/bep_0015.html
  • 在获得连接响应之前,您无法进行公告(您需要transactionId,请阅读规格),我的doConnect()是正确的,我从中更改了很少的行(例如:我使用用于DataGramPacket使用的byteBuffer的最大字节大小,即新的byte[65508]而不是[16]-最小大小。当接收响应时,其余部分将是填充字节,意思是0)。
  • 正如您在doConnect中看到的那样,有一个while循环(它显示最大8轮规则);
  • socketTimeOut不断增加(使用规格中的公式,30秒,90..等)。
  • 在接收连接响应后,您必须执行announce(),方法内容类似,请求不同(再次参见规格),并且这次我对缓冲区使用最大大小(new byte [65508]),但这次它有更多的意义,因为接收到的对等方列表大小不是硬编码的(它可以是1/8/10个对等方...我总是请求10个)..
    (当未添加msg中的对等方信息时,最小大小为20字节)。再次强调,此响应仅为一个数据包(在这些最大65508字节中有空间...我认为我获得了70%的填充)
  • 由于阻塞方法(receive()),一切都很慢,我将其放在自己的线程上(通常那些响应的跟踪器不需要消耗超过2轮...这很好)。
  • 在上述代码中看不到的规则是1分钟规则(在1分钟后再次进行连接)。
  • 我的意见:这是丑陋的代码!使用UDP和线程,因为一切都很慢且阻塞...

更新***

用于处理UDP的代码片段

DatagramSocket workingSocket = ?;//
DatagramPacket sendPacket = null, receivePacket = null;
byte[] sendData = null;
byte[] receiveData = null;

receiveData = new byte[65508]; //NOTE: at least 16 bytes | 65508 is max size, unused bytes are 0
receivePacket = new DatagramPacket(receiveData, receiveData.length);

sendData = this.getRequestTypeAnnounce(announceUDPWrapper.a_TransactionId);
sendPacket = new DatagramPacket(sendData, sendData.length); 
workingSocket.send(sendPacket);
workingSocket.receive(receivePacket);
byte[] fullResponse = receivePacket.getData();

用于组合公告消息的代码片段

private byte[] getRequestTypeAnnounce(AnnounceUDPWrapper announceUDPWrapper) {
    //long connectionId, int transactionId, int tcpPort
    ByteBuffer bBuffer = ByteBuffer.allocate(98);
    bBuffer.putLong(announceUDPWrapper.a_ConnectionId);
    bBuffer.putInt(ACTION_ID_ANNOUNCE);
    bBuffer.putInt(announceUDPWrapper.a_TransactionId);
    bBuffer.put(announceUDPWrapper.a_InfoHash);//<<<< what you asked.. adding the infoHash which is byte[]
    bBuffer.put(this.m_MyId);

    bBuffer.putLong(announceUDPWrapper.a_Downloaded);
    bBuffer.putLong(announceUDPWrapper.a_Uploaded);
    bBuffer.putLong(announceUDPWrapper.a_Left);
    bBuffer.putInt(announceUDPWrapper.a_EventType.ordinal());

    bBuffer.put(Utils.intToByteArray(0));// ip, 0 = default
    bBuffer.putInt(0);//key
    bBuffer.putInt(10);//num_want

    byte[] portAsBytes = Utils.intToByteArray(announceUDPWrapper.a_ListeningTCPPort);
    bBuffer.put(Utils.subArray(portAsBytes, 2, 2)); //port
    return bBuffer.array();
}

嗨,我正在做一个类似的项目,但我无法弄清楚如何正确发送info_hash。我有一个原始字符串哈希值,你能发布一下你是如何编码并发送你的announce请求吗? - Ethan
1
@Ethan 这里有一个很好的例子:https://github.com/erindru/m2t/blob/58c34f97a5ae613e98cf63e562a4de63e936a071/m2t/scraper.py - Saher Ahwal
1
@Ethan:附上了两个代码片段,见上方的回复。希望能有所帮助,这是从工作项目中复制粘贴的。 - pulancheck1988
这是一份更好的手册 https://libtorrent.org/udp_tracker_protocol.html - Master

0

看起来你在混淆网络层。

UDP 在较低的层次上 - HTTP 使用 TCP,而其他一些协议可以使用 UDP。IP 位于其下面,因此您尝试使用的协议类似于“通过 UDP,在 IP 上进行 torrent 下载”。

请参阅此处以获取一些 torrent/udp 协议文档。


UDP跟踪器标准似乎没有任何意义。唯一不发生的事情是确认和可能的重传。对于那些由种子客户端如此不经常检查的东西来说,这并不值得。 - Chris Dennett
1
我已经阅读了规范,其中有一点:尽量减少追踪器的带宽使用(最多50%),但客户端必须处理数据包发送逻辑。 - pulancheck1988

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接