近几年的版权大战,影视资源基本集中到了爱奇艺,腾讯和优酷三家手中,其他的盗版资源大站基本都被干了。21世纪前10年的”互联网是免费“的黄金时代,随着快播等的倒下,基本上已经结束了。
我并不抵制视频网站的付费模式,作为商业公司,它们花费高昂的版权和带宽费用,为我们提供高清的视频,通过广告和付费会员的模式来盈利,是正常不过的商业行为。
但存在的一些问题,让我在观看影视作品时,会选择bt下载。
- 资源分散,三家各自购买了一些独家的版权资源,这样用户需要开三家的付费会员。
- 资源缺失,例如,很多美剧,由于尺度问题,无法引进国内。
- 资源被删减,广电剪刀手的鬼斧神工,把电影剪的支零破碎。当年在电影院看《金刚狼3》,最后森林里的大决战,狼叔刚打完一瓶药,准备大开杀戒,然后下个镜头气喘吁吁地在跑步,看的我和我的小伙伴一脸懵逼。
- 好吧,还有vvvv...vvvip。
BT下载的原理
bt是一种用于分发文件的协议。相比传统的http下载,bt下载每个客户端在下载文件同时,也将文件上传给其他的客户端。
可以看出传统的http下载方式,瓶颈在中心的服务器,越多的客户端向服务器发起下载请求,服务器就需要更多的带宽和cpu等服务器资源。
先解释下图里的几个术语。
Torrent File
- bt种子文件(下面的章节,会一起看下bt文件里面的内容)Web Server
- 保存bt种子的网站服务器Tracker
- bt服务器,帮助bt客户端获取其他正在下载相同文件的bt客户端信息Peer
- bt客户端,下载或者上传文件
Bt下载的过程
- 从资源网站,如人人影视等下载bt种子。
- 本地启动
bt客户端
(uTorrent
等),解析bt种子,得到tracker服务器<ip,port>
, 向tracker
发起请求。 tracker
响应请求,将正在下载相同资源的其他bt客户端信息<ip, port>
,返回给我们。- 发起对
其他bt客户端
的连接,从其他bt客户端
下载文件,同时将已下载好的文件,上传给其他的bt客户端
。
可以看出作为中心服务器的tracker
, 不需要保存文件,也不提供文件的下载,只保存正在下载文件的bt客户端的信息。相比http下载的服务器,一台tracker服务器可以同时支持更多的客户端。
Bt种子
详细来看看bt种子文件里的内容
bt种子文件,使用了bt定义的bencoding来编码文件内容,是一个bencoing编码的字典。里面的键值对有:
announce
tracker服务器的url地址
info
name
建议的文件名
piece length
整个文件分割成多个相同大小的文件块后,文件块的大小(单位:byte)
pieces
每个文件块的sha1值(长度20的字符串),拼接在一起的字符串。
下面的length和files不会同时出现在文件里,只出现length时,表示我们下载的是单独的一个文件,这时候上面的name的值是文件名。只出现files时,表示我们下载的是保存在目录里的一组文件,上面的name的值是目录名。
length
文件的大小(单位:byte)
files
目录里的文件列表
length
文件大小(单位:byte)
path
子目录的名字
单文件的情况
{
"announce":"xxxx",
"info":{
"name":"xxxx",
"piece length":"xxxx",
"pieces":"xxxx",
"length":"xxxx"
}
}
多个文件的情况
{
"announce":"xxxx",
"info":{
"name":"xxxx",
"piece length":"xxxx",
"pieces":"xxxx",
"files":[
{
"length":"xxxx",
"path":"xxxx"
},
{
"length":"xxxx",
"path":"xxxx"
},
{
"length":"xxxx",
"path":"xxxx"
}
]
}
}
请求Tracker
解析bt种子后,我们可以从announce
键得到tracker
的地址,向tracker
发起一个get
请求来获取,正在下载同一资源的其他bt客户端
的信息。
Get请求有以下的参数
参数 | 含义 |
---|---|
info_hash | 这个参数标识了资源,每个资源都有唯一的info_hash,info_hash是对种子文件里info键对应的值 使用sha1哈希来生成。 |
peer_id | bt客户端启动时,会随机生成一个长度为20的字符串,作为客户端id |
ip | bt客户端的ip地址 |
port | bt客户端正在监听的端口 |
uploaded | bt客户端已经上传的文件大小 |
downloaded | bt客户端已经下载的文件大小 |
left | bt客户端还要下载的文件大小 |
event | 事件,start -bt客户端开始下载,completed -bt客户端完成下载,stopped -bt客户端终止下载,empty -bt客户端常规的轮询请求,用来告诉tracker,客户端仍然在线 |
Get请求的正常响应包含两个字段
字段 | 含义 |
---|---|
interval | bt客户端常规轮询请求tracker的时间间隔。bt客户端可以不遵守这个时间间隔,在收到的其他bt客户端数量不够时,可以自行决定是否马上重新请求tracker |
peers | 其他bt客户端信息的列表 |
DHT-分布式哈希
因为bt往往是用来下载盗版资源,所以国家也在大力打击bt下载。从上面的内容,你应该知道怎样去摧毁一个bt下载了吧。
tracker作为整个bt下载的中心,只要让关停tracker所在的服务器,我们的bt客户端就无法得到其他客户端的信息了。
bt社区也很早意识到这个问题,在bt客户端了加入DHT(分布式哈希表),来实现无中心化,将原来由tracker存储的信息,分散存储到整个bt网络的每一台客户端里,这样避免了tracker的单一故障带来的整个网络瘫痪。bt下载网络从第一代包含tracker服务器的网络,转换成了第二代的无tracker的dht网络。
实现DHT的算法和技术有很多,bt和bt的门徒们(eMule等)使用的都是基于Kademlia
算法,我之前写过一篇关于kademlia
算法的博客,可以先看看。
简单地讲下整个查找过程。
- 每一个bt客户端都有一个随机的160 bit的node_id。每个资源都有一个唯一的info_hash。
- 每一个bt客户端都维护个一个路由表,里面保存着其他bt客户端的信息
<ip, port, node_id>
。- 本地bt客户端,在自己的路由表里选出8个离资源info_hash
最近
的bt的客户端,发送get_peers
请求,询问这8个bt客户端上,是否保存有正在下载这个资源的其他客户端信息。- 如果有,那就响应返回给本地bt客户端,如果没有,那就从路由表选出8个离资源info_hash
最近
的bt的客户端信息,返回给本地bt客户端。- 如果返回的是正在下载这个资源的其他客户端信息,本地bt客户端就可以开始连接下载。
- 本地bt客户端收到8个离资源info_hash
最近
的bt的客户端信息,向这8个客户端再发起get_peers
请求。- 这个过程,一直递归持续下去,直到无法找到离资源info_hash
更近
的客户端。整个过程下来,最终本地bt客户端会得到8个离资源最近
的客户端信息。- 本地bt客户端向这8个离资源最近的客户端,发送
announce_peer
,告诉它们,我正在下载这个资源,以后可以连接我来下载资源。
类比下生活中例子。
川普手里应该有美国51区的机密档案,我想通过电话联系上川普,拿到机密档案。但我没有川普的电话(⊙︿⊙)。我先在通讯录里找到最有可能认识川普的8个朋友(这里面有美国友人,政府官员,这些人应该比普通人,更有可能有川普的电话),分别打电话询问他们。
他们应该是没有机密档案,他将他通讯录里最有可能认识川普的8个朋友的电话给我。我要继续打电话给8个人,询问他们有没有川普电话。
将这个过程不断地持续下去,根据六度分割理论,最后我肯定能拿到川普的电话,川普答应将机密文件发给我。
整个过程下来,我拿到很多人的电话号码,拿到机密档案后,我会告诉这里面和川普"熟悉度"的8个人,我拿到机密档案了。以后找我也可以拿到机密档案。
当然,我们利用上面的流程,除了51区的机密文件,当然也可以用来得到其他的文件。
种子爬虫的原理
种子爬虫正是利用了上面的过程。
- ”机密档案“ - 资源info_hash(这里的”机密文件“可以理解为只是一个代号,而不是机密文件里的内容,用来表明我要拿的东西是机密文件。就好像info_hash是资源的标识,而不是资源的具体内容)
- 机密档案的内容 - 其他正在下载这个资源的客户端信息 (这里可能有点类别不太恰当,bt dht拿到是正在下载这个资源的其他客户端信息,具体的文件内容,需要本地客户端连接上其他客户端去下载)
- 通讯录 - 路由表
- 电话号码 - 路由表里的其他bt客户端信息
因为资源的info_hash是通过sha1算法计算种子文件里的内容得到的,我们得到info_hash后,后面有办法通过info_hash得到种子文件。所有我们的种子爬虫收集的是资源的info_hash。
为了得到更多的info_hash,我们需要将自己加入更多人的路由表(通讯录),同时声明自己离资源更近
(认识川普),这样有更大的概率,会被挑选出来,返回给请求者,请求者会带着资源info_hash的参数询问我们,你有没有这个资源相关的信息。这样我们就得到了info_hash了。
种子爬虫具体的实现-KRPC协议
krpc协议在bep5已经详细讲解,可以直接看文档。
种子爬虫主要用到是find_node
和get_peer
。
我们带着自己的node_id
,不断地向其他的bt客户端发起find_node
请求,其他客户端收到请求,会将收到的node_id
插入到路由表中(就好像将自己的电话号码加入到他的通讯录),同时我们要不断伪造不同的node_id
, 插入到其他bt客户端的路由表,这样后面其他bt客户端在自己路由表,挑选哪些node_id
对应的bt客户端上,离资源更近
时,更有可能将我们的node_id
挑选出来,返回给请求者。(就好像我要装得我和川普很熟悉,我比其他人,更可能有川普的电话)。
加入到其他客户端的路由表后,我们就可以等待接受get_peers
请求,get_peers
请求里会带有参数info_hash
,这样就可以收集资源了。
我们的种子爬虫还有几个需要注意的地方:
- 客户端刚启动的时候,路由表里什么信息都没有,需要向
常驻网络的节点
(如dht.transmissionbt.com:6881),发起对本客户端node_id
的find_node
请求,在这个过程里,将收到的其他客户端信息填充到自己路由表。 - 要响应
ping
,find_node
,get_peer
和announce_peer
的请求,路由表是有大小的,如果不响应,路由表会将你删除。就好像打你电话,你总是不接,那就把你从通讯录里删掉了。 - 因为NAT的原因,种子爬虫要部署到有公网ip的机器上。
具体代码实现
今天就写到这里,有点累了,具体代码实现,下一篇文章,我会详细讲解下。
参考资料
BEP3
BEP5
Kademlia: A Peer-to-peer Information System Based on the XOR Metric
Bit torrent techtalks_dht
Youtube - BitTorrent Tech Talks: DHT