Taipei-Torrent源码分析 - 蘭陵N散記

JerryXia 发表于 , 阅读 (0)
WEB服务器文件元信息(metainfo)BitTorrent Tracker原始资源发布者目的端用户浏览器目的端用户下载者

其中原始资源发布者与目的端下载者都称为Peer,而Tracker主要用于获取不同的Peer信息信息,BitTorrent把要下载文件虚拟分成大小相等的块,并把每块的索引信息与Hash验证码等元数据信息写到一个.torrent文件中,即种子文件。种子文件采用B编码格式,它本质是一个文本。Peer与Tacker或DHT节点通讯也采用B编码格式。根据获取Peer信息的途径不同,又分为两种。

  • 有的Tracker结构
        [WebServer]          |          | torrent file          |     [  Peer ] ---Get Peers --- [TrackerServer]         \          \          Download&Upload(TCP)           \            \           [ OtherPeer ]
  • Trackerless的DHT结构
        [WebServer]          |          | torrent file          |     [  Peer ] ---Get Peers --- [DHT Nodes]         \          \          Download&Upload(TCP)           \            \           [ OtherPeer ]

Trackerless的DHT结构解决了Trakcer中心故障的问题,是一个更去中化的结构。DHT结构中,每个peer都可能是一个tracker。DHT是基于Kademila协议的,并且在UDP协议基础上实现。

每个节点都有一个全局唯一的标识符,称为节点ID。距离度量用来比较两个节点或者节点与infohash之间的远近程度。节点必须维护一个含有少量其他节点联系信息的路由表。ID越靠近自身ID时,路由表越详细。节点知道很多离它很近的节点,只知道少量离它很远地节点。

在Kademlia中,距离度量采用异或计算,结果解释成一个无符号整数。 distance (A,B)=(A ~| B),值越小,距离越近。每个节点维护一个路由表,由它所知道的好节点组成。路由表中的节点被用作在DHT中发送请求的起点。当其他节点查询时,就返回路由表中的节点。

开源实现

C++语言

实现BitTorrent协议最有名要算两个C++的实现:

  • rakshase 版本:他来源于Mozilla NSS的项目,LICENSE是GPL,使用它的客户端亦rtorrent等,基于Posix接口开发,可以在兼容Posix系统中编译使用,也支持HDT。这个项目已有12年了,目前还在发展,可以是非常的稳定与成熟,据说是速度之王。
  • rasterbar(arvidn/libtorrent)版本,GitHub上有两个地址,另一个是libtorrent/libtorrent。前者是还是发展,后者已停止开发了。rasterbar版本是基于boost asio编写的,跨平台不存问题。默认有Python与Ruby的绑定接口。并且具有良好扩展性,例如有uTP,DHT安全扩展

Go语言

由于个人爱好的原因,一真想找是否有Go语言实现版本,于是在GitHub上寻寻觅觅,也找到两个不错的实现:

  • Taipei Torrent:它一个较轻量的,基于命令行接口的Torrent客户端,主要功能有支持多Torrent文件,Magnet链接,DHT,UPnP/NAT-PMP打洞,也提供简单的tracker服务。
  • anacrolix/torrent: 它实现了BitTorrent协议相关功能包,以及提供较丰富的命令行工具集。支持加密协议,DHT,PEX,uTP以及多种扩展。从代码结构来说,anacrolix/torrent比Taipei Torrent更容易做二次开发。

Taipei Torrent

首先它的名字比较有意思,项目开始于作者在台北的旅游,所以取名为Taipei Torrent。它的代码量比较不多,像Bencode,DHT,NAT-PMP,网络工具包都采用第三方库。

代码结构

Taipei-Torrent git:(master) tree├── main.go├── queryTracker.bash├── resolveBindIP.go├── resolveBindIP_test.go├── test.bash├── testData│   ├── a.torrent│   └── testFile├── testdht.bash├── testswarm.bash├── testtracker.bash├── torrent│   ├── accumulator.go│   ├── accumulator_test.go│   ├── bitset.go│   ├── cache.go│   ├── cache_test.go.......│   ├── upnp.go│   ├── uri.go│   └── uri_test.go└── tracker    ├── tracker.go    └── tracker_test.go

源码分析

花了一个下午走读它的代码,代码简洁易懂,主要功能都在torrent目录下。每个Peer对一个torrent文件会产生一个会话,在我的笔记本记,使用Docker搭建了6个节点,发布下载77M的go的安装包,是秒级速度。如下所示:

2016/01/17 12:13:13 Starting.2016/01/17 12:13:13 Listening for peers on port: 77772016/01/17 12:13:13 [ go1.5.3.linux-amd64.tar.gz ] Tracker: [], Comment: , InfoHash: 556871e1ada306c2da5033e8fe0d4f077edbe6f7, Encoding: , Private: 02016/01/17 12:13:13 [ go1.5.3.linux-amd64.tar.gz ] Computed missing pieces (0.35 seconds)2016/01/17 12:13:13 [ go1.5.3.linux-amd64.tar.gz ] Good pieces: 0 Bad pieces: 1223 Bytes left: 801472692016/01/17 12:13:13 Created torrent session for go1.5.3.linux-amd64.tar.gz2016/01/17 12:13:13 Starting torrent session for go1.5.3.linux-amd64.tar.gz2016/01/17 12:13:14 [ go1.5.3.linux-amd64.tar.gz ] Peers: 0 downloaded: 0 (0.00 B/s) uploaded: 0 ratio: 0.000000 pieces: 0/1223

作为Peer Client,主要代码逻辑在torrent\torrentLoop.go的RunTorrents方法中,它充分利用了Go的channel机制。实现步骤如下:

  1. 根据命令参数,开启Peer连接端口(TCP)。若不指定端口,则采用随机端口。目前是绑定在所的IP上,如果是内网,可以做NAT转换。
  2. 根据MaxActive参数,开启Session(对象为TorrentSession)数,如果同时下载torrent多余MaxActive则排队处理
  3. 如果设置参数useDHT,或torrent文件中没有Tracker服务,则会开启DHT,而DHT是采用UDP,端口与第1步的相同。
  4. 如果设置useLPD(Use Local Peer Discovery),则又会通过组播在同一个网段内相互发现,组播地址为239.192.152.143:6771
  5. 当完成上述初始化之后,在mainLoop主要根据事件来处理:
    1. 如果是Session创建成功,则异步执行TorrentSession.DoTorrent方法开始启动下载
    2. 如果是有Session下载结束,则从排队中取出未处理的torrent文件加入到Session处理。
    3. 如果是收到退出信号,则等下载结束退出。
    4. 如果是收到其它Peer的连接请求,根据Infohash来判断是否存在相应的Session,如果存在,则提供给其它的Peer下载数据。
    5. 如果是收到DHT Peer的请求结果,则处理其它的Peer地址,根据地址从其它Peer下载数据。
    6. 如果是收到其它Peer的组播请求,则处理其它的Peer地址,根据地址从其它Peer下载数据。

另一个核心代码逻辑是在torrent\torren.go的DoTorrent方法中,实现步骤如下:

  1. 如果设置了内存缓存或硬盘缓存数,则根据torrent文件中元信息(块个数,整个大小)初始化缓存。
  2. 开启几个定时器,每隔1秒心跳检查,每隔60秒连接KeepAlive,如果采用Tracker服务,每隔20秒与Tracker服务列表请求,直到有Tracker服务有咱应。
  3. 如果是DHT,则从DHT Peer获取其它的Peer地址。
  4. 处理各种Channel的消息。

还一个重要的torrent\torren.go的DoMessage方法,它用于是产生协议的消息,与一个peer建立TCP连接后,首先向peer发送握手消息,peer收到握手消息后回应一个握手消息。握手消息是一个长度固定为68字节的消息。消息的格式如下:

[pstrlen][pstr][reserved][info_hash][peer_id]