在 2000 年左右开始接触互联网的同学都应该记得用 BT 种子下载电影和小电影那段的时光。之前只是大概知道 BT 的工作原理,但并没有仔细研究过,所以一直很好奇。
随便在网上搜索下,可以知道 BT 大概是这样工作的:
BitTorrent 协议把提供下载的文件虚拟分成大小相等的块,块大小必须为 2k 的整数次方,并把每个块的索引信息和 Hash 验证码 写入 .torrent 文件(即种子文件,也简称为“种子”)中,作为被下载文件的“索引”。 下载者要下载文件内容,需要先得到相应的 .torrent 文件,然后使用 BT 客户端软件进行下载。
下载时,BT 客户端首先解析 .torrent 文件得到 Tracker 地址,然后连接 Tracker 服务器。Tracker 服务器回应下载者的请求,提供下载者其他下载者(包括发布者)的 IP。或者,BT客户端也可解析 .torrent 文件得到 nodes 路由表,然后连接路由表中的有效节点,由网络节点提供下载者其他下载者的 IP。
torrent 文件包含了什么
根据 bittorrent.org官方文档,种子文件也被称为metainfo files
, 主要包含以下信息:
announce
, The URL of the tracker.info
, This maps to a dictionary.
所以种子文件就是告诉你,去 announce 这个地址找文件,具体文件信息包含在 info 里面。
Info 结构体有以下基本内容:
name
key maps to a UTF-8 encoded string.piece length
maps to the number of bytes in each piece the file is split into.pieces
maps to a string whose length is a multiple of 20. 其实就是文件被切成很多片,这个变量保存了所有片的 SHA1 值。length
- The length of the file, in bytes.
以上4个是最基本的结构体信息,只支持下载单个文件,如果是表示多个文件或文件夹,还需要增加一些额外信息,具体见官方文档。
根据以上信息就可以写出一个种子文件的 Golang 结构体,
type TorrentFile struct {
Announce string
InfoHash [20]byte
PieceHashes [][20]byte
PieceLength int
Length int
Name string
}
此外,BT torrent 文件是使用 bencoding
编码方式编码的,所以如果用 text editor 打开种子文件会看到是一堆读不懂的符号。
好在已经有人帮我们写好了解析 bencoding 的库了,比如 github.com/jackpal/bencode-go,直接拿来使用就可以。
与 Tracker 的请求和回复
当知道了 tracker 服务器的地址后,client 就可以向服务器发起 GET 请求,那么请求要包含哪些信息呢?
info_hash
对种子文件 info 字段进行 hash 后的一个值,用来唯一定位一个文件。peer_id
client 自己的给自己的 IDip
client 自己的 IP。Generally used for the origin if it’s on the same machine as the tracker.port
The port number this peer is listening on.uploaded
The total amount uploaded so far, encoded in base ten ascii.downloaded
The total amount downloaded so far, encoded in base ten ascii.left
剩余需要下载的字节数。Note that this can’t be computed from downloaded and the file length since it might be a resume.event
This is an optional key which maps to started, completed, or stopped (or empty, which is the same as not being present). If not present, this is one of the announcement
之后 Tracker 会回复一个 bencoding 编码的 key value, 包含以下信息:
- 如果失败,返回
failure
包含失败的原因。 - 如果成功,返回两个值,一个是
interval
,告诉 client 多久请求一次一遍更新信息,单位是秒s。第二个是peers
, 包含一串 peer id, ip, and port。
与 Peer 的交互
终于,经过上面两步,我们拿到了 Peer 的信息,接下来就可以去 Peer 那里下载文件的分片了。
Peer Protocol
官方文档上关于 protocol 有一大段的描述,我的理解如下:
每当 client 下载完一个piece 并且验证完 hash 之后,它需要跟其他 peers announce 这个 piece。
连接双方都时刻维护着两个状态:choked or not
, interested or not
, Choking is a notification that no data will be sent until unchoking happens.
当一方是 interested
并且另一方是 not choked
时,才可以传数据。连接初始时是 choked and not interested
。
Handshake
开始握手时,发起方会发一个 handshake message 给 peers,它由以下信息组成:
- 用 19
BitTorrent protocol
作为开始,这个是协议的 identifier 和它的长度,之后每条消息也是把长度放在前面,后面接上信息本身,长度被 encode 成 big endian 4 bytes。 - 接着第二部分是 8 字节全为的 0 ,主要是保留作为扩展,但是现在没有使用,所以全都是 0。
- 接下来是 20 字节的 BT 种子 metainfo 字段的 SHA1 值。
- 接下来是 20 字节的我们自己的 peer id。
对方 peer 收到这条握手信息后,会发回一个一样格式的消息,我们需要 parse,然后检查里面的信息与我们期待的一致,比如 metainfo 的 SHA1 值。
到此,握手结束。在网上找了一张截图放在这里,
接下来就是真正交互式的传数据,消息都是由长度作为 prefix 加上 msg 本身。 如果长度是0,则是 keepalive 消息。
Peer Message
正如前面说的,handshake 完成后我们还不能请求数据,必须要等 peer 给我们发 unchoke
消息才行。
注意:bittorrent 是一个 peer-to-peer
协议,因此不能像 HTTP 协议那样区分 client server,在双方交换信息的过程中,能够发送和接收的消息类型是一致的。
bittorrent 一共有下面 9 中消息类型
- 0 - choke
- 1 - unchoke
- 2 - interested
- 3 - not interested
- 4 - have
- 5 - bitfield
- 6 - request
- 7 - piece
- 8 - cancel
如果收到 bitfield
类型的消息,它告诉我们可以从 peer 那儿下载哪些 piece,从它的名字可以看出,是利用每个 byte 中的 8 个 bit 来表示的,充分利用空间。
如果收到 have
类型的消息,表示 peer 刚刚下载完并验证了哪些 piece。 格式是 have: <len=0005><id=4><piece index>
request
消息的格式是 request: <len=0013><id=6><index><begin><length>
- index: integer specifying the zero-based piece index
- begin: integer specifying the zero-based byte offset within the piece
- length: integer specifying the requested length.
piece
的格式是 piece: <len=0009+X><id=7><index><begin><block>
- index: integer specifying the zero-based piece index
- begin: integer specifying the zero-based byte offset within the piece
- block: block of data, which is a subset of the piece specified by index.
协议的分析就写到这里,对于某些具体的细节,如果以后我有新的理解再来更新。
理解了协议之后,就可以尝试写一个简单的 bittorrent client 了,期待有空能写一个。
参考资料
必须要吐槽一下,官网的资料写的还没有第三方网站清楚易懂。