5

更新 #4:添加了用于使用 UDP 和发送通知消息的演示 java 片段(记住连接是第一个!)检查下面的自己的响应。

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

更新#3:我设法让它工作,下面提出的方法 doConnect() 是好的,更多信息在我自己的回复下面。

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

我主要对当announce url的协议是UDP时如何下载跟踪器响应感兴趣。

详细信息:所以这些是来自有效种子文件的一些公告网址(第一个是主要的)

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 下载资源?(希望它简单,我看不到数据报的东西)

更新#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 的完整代码应该是第一个通信阶段(接下来是宣布 .. 那里的类似代码)

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) 新问题:是否可以在 elbitz.net 上创建套接字,端口:80,当完整的 url 包含更多信息时(例如:/announce)?

更新#2

上面的代码似乎工作正常..我在谷歌上找到了一个已经实现这个规范的跟踪器列表,瞧,响应发生了(例如:“udp://tracker.openbittorrent.com:80”)

新问题和再次规范在这里: http: //www.bittorrent.org/beps/bep_0015.html - 我似乎不知道如何获得同行列表?.. 在对 torrent 跟踪器的正常请求(通过 http)中,有两种情况:正常响应(编码映射)和压缩响应(二进制形式)。那么现在的同行名单呢?

  • 从规格来看,这是宣布响应:
/*
              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
4

2 回答 2

3

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

  • 如果它以 udp:// 开头,则有效
  • 如果 url 是:udp://elbitz.net:80/announce.php?passkey=362fc69de3402e8ef5794c7ecf7c58d4 您在 udp://elbitz.net 上创建套接字,使用端口 80(创建 UDP 套接字时不使用 /announce 部分)
  • 由于它是 UDP,因此无法保证响应(不会抛出异常,您只需等待),规范中提到了最大 8 轮规则……但这意味着要等待相当长的时间才能实现跟踪器不存在。
  • 在通过 UDP 通信的情况下,您发送 2 种类型的请求(连接和通知,每个作为 1 个 UDP 包)并接收 1 个响应(显然,如果你很幸运,也可以作为 UDP 包).. 提取可用信息,你有解析响应byteBuffer。真正的信息再次在这里: http: //www.bittorrent.org/beps/bep_0015.html
  • 在收到 Connect 响应之前,您不能宣布(您需要一个transactionId, 阅读规范)我doConnect()是好的,我从中更改了很少几行(例如:我正在使用byteBufferby使用的最大大小(以字节为单位DataGramPacket),这意味着 new byte[ 65508] 而不是 [16] - 最小大小。收到响应时,其余部分将填充字节,即 0)。
  • 如您所见,doConnect有一个 while 循环(它显示了最大 8 轮规则);
  • socketTimeOut持续增加(使用规格中的公式,30 秒,90.. 等)。
  • 收到连接响应后,您必须执行announce(),方法内容相似,请求不同(再次参见规范),并且我再次使用缓冲区的最大大小(新字节[65508])但这次更有意义,因为接收到的对等点的列表大小不是硬编码的(它可以是 1/8/10 个对等点...我总是请求 10 个)..
    (最小大小为 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();
}
于 2013-03-20T18:48:17.920 回答
0

在我看来,您在这里混合了网络层。

udp 位于较低层 - http:// 通过 tcp,而其他一些协议可以通过 udp。IP 低于此值,因此您尝试使用的协议类似于“torrent over udp, over IP”。

有关一些 torrent/udp 协议文档,请参见此处

于 2013-03-03T09:49:46.027 回答