上一篇我们已经解析出种子的基本结构。下一个问题就是,如何通过种子文件所给的信息,获取文件的下载地址。
上一篇中我们解析种子发现有两个键比较特殊,分别时announce以及announce-list
这两个属性的值便是是tracker服务器的地址。(据我观察,announce里面的地址比announce-list里面的地址访问速度快一点,announce-list里的地址无法访问的概率大一点)
part1.地址格式
首先,我们可以发现,announce和announce-list里面的地址有两种格式的,udp开头的地址,以及http开头的地址。http开头的地址很好理解就是遵循http协议的地址。那udp开头的地址就是遵循udp协议的地址喽。这样理解有对也有不对。准确点讲这个udp地址实际上是遵循udp tracker 协议.(注意:上面两种地址都是无法浏览器直接访问的。)
part2.请求构造解析
1.对于http开头的地址,bt协议对其的介绍在这
http://www.bittorrent.org/beps/bep_0023.html
简单来讲,就是需要利用get的方法,传递给目标tracker网址一些必须的参数,网址接收了这些参数以后会返回数据。
这里有一篇博客详细介绍了这两个地址构造
https://blog.csdn.net/wenxinfly/article/details/1504785
重复的我也没啥好讲了。我就补充一下这篇博客没讲清楚的。
我们可以知道访问http开头的地址的请求构造类似
GET /announce?peer_id=aaaaaaaaaaaaaaaaaaaa&info_hash=
aaaaaaaaaaaaaaaaaaaa&port=6881&left=0&downloaded=100&uploaded=0&compact=1
这里有三点要提醒
1)请求为get请求
2)peer_id是自己随机设定的,只要有20个字节即可
3)这个info_hash,实际上应该是真正的磁力链特征码再经过urlencode得到的。
我们网络上拷贝黏贴下来的磁力链特征码一般是这样的 052ef38011e34ef27e58391da13a327eb88323a3
而这个并不是磁力链特征码的真正样式。只是为了直观显示经过处理后的字符串(因为实际的特征码显示出来会是一串乱码),将上面的字符串,两个字符并成一个就是实际的磁力链了。如,假设我们拷贝的磁力链为”012e“(上面那个太长了,选一个短点的分析),这串编码的二进制流为00000000 00000001 00000010 00001110.那么实际的磁力链二进制编码为00000001 00101110。实际上就是压缩了一下。
更深一步理解为什么要”压缩“一下就要理解以下两点。
1.计算机屏幕上显示的数据和实际数据是不完全等价的。
因为计算机中所有的数据都是二进制流形式,但是我们人却难以理解二进制流的文件。所以就有了编码,将二进制流转化为人能读取的字符流。但是这有一个问题,那就是,实际上二进制01所能表示的”字“数目要多于字符的数目。所以不管哪种编码,那必然会出现有些二进制流乱码,甚至无法编码的问题。所以显示和实际是不完全等价的。
2.加密算法一般都是对于二进制操作。
一般加密算法都是对于01二进制流进行加密的,输入的是01二进制流,输出的也是01二进制流。如上面提到的,二进制流和字符流是不完全等价的。那么就必然有许多字节无法编码成字符。所以加密算法得到的密文一般都是乱码。
磁力链特征码是用sha1加密的->它是一串二进制流->它不能完全编码成任何一种编码(会有乱码)->为了直观显示->将其一个字节拆成两个(这样必然有对应的编码显示)直观显示。
所以对于http开头的tracker请求,我们需要对参数info_hash特殊处理,将其二进制压缩,再编码(这个编码如果不同的话,最后经过urlencode编码得到的数据会不同,我利用utf-8编码是可以实现访问的)得到真正的磁力链字符串,再urlencode。
在php中只要$s= pack(“H*”,info_hash);再$y = urlencode($s);
而java中没有对应的函数,只能自己写了。。。代码放最后。
2.对于udp开头的地址,bt协议对其的介绍在这
http://www.bittorrent.org/beps/bep_0015.html
我一开始以为是udp地址(想起来也是有点蠢。。。)
实际地址是udp://后面那部分,例如地址”udp://open.demonii.com:1337“,实际地址为open.demonii.com,端口为1337.
udp开头的地址请求比较麻烦。有一些情况我还不是特别理解,先写下来吧。
首先,这个请求是建立在udp协议之上的,需要用到udp编程
其次数据交互要如下几步走
1.建立连接
我们先要向tracker发送数据包connection_input,包中包括如下几个部分connection_input,action(0),transcation_id(自己定,随机)。connection_id初始值(即建立连接时的值)为0x41727101980(由于数据包是二进制传输的,所以所有的数据都要转为二进制,这个16进制需要转为2进制)。
2.确认建立
得到返回数据包,判断包的字节数是否为16字节,检查连接状态action是否为0,检查transcation_id是否和你设置的一致,保存返回包中的connection_id。
3.发起请求
这次请求数据包比较长,大概100个字节左右,包括connection_id(我们确认建立连接时保存的),action_id(1,表示start),transacation_id(自己定,随机),peer_id(随机),ip(0表示你希望tracker使用udp上的地址),key(随机,自定义)…
4.接收用户下载信息
和2差不多,检查action==1,检查transcation_id.返回数据包中会包含当前正在下载的用户ip以及port。这样就获得了下载ip。
贴上http访问解析后的结果
访问了好多地址,好不容易有一个有返回值的
贴上udp访问解析后的结果
我这里是设置了,一次获取10个ip,如果没设置numWant参数,一次默认最多50个
udp通讯代码
https://github.com/yyyhah/BtDownload/blob/master/Connection/UDPTrackerTransfor.java
http通讯代码
https://github.com/yyyhah/BtDownload/blob/master/Connection/HTTPTrackerTransfor.java
调用演示代码
进行udp开头的地址通讯
UDPTrackerTransfor udpTf = new UDPTrackerTransfor(10000,"bf917a3e5bc740c316f8bb16129da394cc732bdb");byte[] bytes = udpTf.setUpLink("udp://tracker.openbittorrent.com:80");System.out.println(bytes.length);for(byte b:bytes) {System.out.print(b);System.out.print(" ");}byte[] bytes2 = udpTf.startAnnounceRequest();for(byte b:bytes2) {System.out.print(b);System.out.print(" ");}
进行http开头的地址通讯
HTTPTrackerTransfor httpTf = new HTTPTrackerTransfor();try {String data = httpTf.setUpLink("http://tracker.supertracker.net:1337/announce", hash, port);System.out.println(data);}catch(Exception e) {System.out.println("该链接无法访问!!!");}
java中16进制字符串转2进制流的函数(我写完这段代码,编译器就报优化bug了,不知道是不是在这段的出错了,如果你也遇到这个问题,百度一下就好了)
public static byte[] HexToByte20(String inHex) {byte[] hexBytes=new byte[20];int index = 0;while(inHex.length()<40) {inHex = "0"+inHex;}for(int i=0;i<inHex.length();i=i+2) {int n = Integer.parseInt(inHex.substring(i,i+2), 16);hexBytes[index] = (byte)(n&0xff);index++;}return hexBytes;}
ps:最后再提几点需要注意的地方
1.有些人(比如我)一开始向udp的tracker发出请求只能获得一个(就是自己本地ip)。如果你也遇见这个问题,检查一下hashInfo转化有没有出错。刚开始我就是出错了。我上面那段代码经过抓包检验,应该时没问题了。
2.虽然上面udp tracker给了这么多个ip地址,实际上经过检验,能ping通的没几个,我就因为tcp连接一致连不通,一直以为我弄错了。。。。这里对要进行下一步peer之间的通讯实验的人做个提醒。