简单做个BitTorrent客户端

功能

  • 多文件torrent下载
  • 支持UDP/HTTP Tracker
  • 支持DHT,PeX,磁力链接

Golang实现

找peers

Torrent文件结构与Bencode编码

一个torrent文件包含一个文件列表和关于所有片段的完整性元数据(integrity metadata),并可选地包含一个很长的tracker列表,并由Bencode编码。

Bencode编码支持4种数据类型:byte string,integer,list和dictionary。

以电影《早餐俱乐部》为例,其torrent文件结构如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
d
  8:announce
    43:udp://tracker.coppersurfer.tk:6969/announce
  13:announce-list
    l
      l43:udp://tracker.coppersurfer.tk:6969/announcee
      l49:udp://tracker.leechers-paradise.org:6969/announcee
      l38:udp://tracker.cyberia.is:6969/announcee
      ...
    e
  7:comment
     80:The.Breakfast.Club.1985.REMASTERED.720p.BluRay.999MB.HQ.x265.10bit-GalaxyRG[TGx]
  13:creation date
     i1600189812e
  4:info
    d
      5:files
        l
          d
            6:length
              i1035352868e
            4:path
              l79:The.Breakfast.Club.1985.REMASTERED.720p.BluRay.999MB.HQ.x265.10bit-GalaxyRG.mkve
          e
          d
            6:length
              i678e
            4:path
              l42:[TGx]Downloaded from torrentgalaxy.to .txte
          e
        e
      4:name
        80:The.Breakfast.Club.1985.REMASTERED.720p.BluRay.999MB.HQ.x265.10bit-GalaxyRG[TGx]
      12:piece length
        i524288e
      6:pieces
        39500:x���vģBɆp6��HH"c�`f|��� ...
    e
e

在这个字典中,所有键和字符串类型值的结构都是length:xxx,整型表示为ixxxe,列表为l[]...[]e,字典为d(key:[])...(key:[])e,其中,[]可以为四种类型的任意一种。

  • announce:tracker的URL
  • announce-list:tracker的URL列表(可选)
  • info:包含文件共享信息的字典
    • files:文件字典列表(仅当共享多个文件时)
      • length:文件的大小,以字节为单位
      • path:与子目录名称对应的字符串列表,其中最后一个是实际的文件名
    • name:建议要保存的文件名(单文件)/ 建议的目录名称(多文件)
    • piece length:文件片的大小,通常为256 KB
    • pieces:每个文件片的二进制编码的SHA-1哈希列表(如果有多文件,则按文件字典中的顺序拼接在一起)

一个torrent文件是由infohash唯一标识的,其值为整个info的SHA-1哈希。

解析torrent文件

解析bencode编码的torrent文件,本质是递归下降。当然,Marshal/Unmarshal的过程需要借助go的反射。最终解析得到一个扁平化的结构体TorrentFile

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
type TorrentFile struct {
	Announce     string
	AnnounceList []string
	InfoSHA      [ShaLen]byte
	FileList     []File
	FileName     string
	FileLen      int
	PieceLen     int
	PieceSHA     [][ShaLen]byte
	HasMulti     bool
}

从tracker获取peers信息

根据Announce中URL的协议类型,可将tracker分为UDP/HTTP tracker。

HTTP

想要得到peers信息,需要给tracker发送一个GET请求。参数列表如下:

  • info_hash:torrent文件中info字典(bencode编码)的SHA-1哈希
  • peer_id:客户端随机生成的20字节长度字符串
  • port:客户端监听的端口
  • uploaded:当前已上传量
  • downloaded:当前已下载量
  • left:当前剩余下载量

tracker收到请求后返回一个bencode编码的字典。

1
2
3
4
5
6
d
  8:interval
    i900e
  5:peers
    252:([ip][port]...)
e

其中,interval表示请求tracker的时间间隔,peers包含了peer的IP地址和端口信息(大端表示),其值为每6字节一组的二进制数据。同样,需要解析得到peer信息切片。

UDP

为了减少HTTP请求给tracker服务器带来的开销、提升性能,tracker也支持UDP。

与HTTP的一次请求响应不同,由于UDP不可靠,客户端至少需要发送两个UDP数据包。为了保证安全性,tracker在收到客户端的第一个包时返回一个connection_id,让客户端在下一个包带上该参数,收到后校验。同时,connection_id一段时间后会失效。

Connect

首先,客户端发送connect包,参数如下:

  • protocol_id:0x41727101980,用于标识协议
  • action:0 表示connect请求
  • transaction_id:随机值,4Bytes

tracker服务器返回的包:

  • action:0 表示connect请求(如果出错则为3)
  • transaction_id:与客户端请求值一致
  • connection_id:由tracker生成,用于标识客户端
Announce

随后,客户端发送announce包,参数如下:

  • connection_id:tracker返回的connection_id
  • action:1 表示announce请求
  • transaction_id:随机值,4Bytes
  • event:none = 0, completed = 1, started = 2, stopped = 3
  • key:随机值,4Bytes
  • num_want:想要tracker返回的最大peers数量,-1为默认值

剩余的info_hashpeer_id这些参数与HTTP请求tracker时相同。 最后,tracker服务器返回包含peers信息的包:

  • action:1 表示announce请求
  • transaction_id:与客户端请求值一致
  • interval:下一次客户端announce的时间间隔
  • leechers:未完成下载的peers数量
  • seeders:已经完成下载且愿意继续上传的peers数量

包的最后是6字节一组的peers信息(IP + Port)。

下载

TCP连接与BitTorrent握手

与peers交换信息

并发控制

参考