从零开始用Go构建BitTorrent客户端
你是否好奇过,当我们访问The Pirate Bay时,是如何凭空下载到一个MP3文件的?在本文中,我们将实现足够的BitTorrent协议来下载Debian操作系统。如果你对代码感兴趣,可以查看源代码,或者直接跳到最后的部分。
BitTorrent简介
BitTorrent是一种用于在互联网上下载和分发文件的协议。与传统的客户端/服务器模型(例如,在Netflix上观看电影或加载你现在阅读的网页)不同,BitTorrent网络中的参与者称为peer(对等节点),它们彼此之间下载文件片段——这就是它被称为点对点(P2P)协议的原因。接下来,我们将探讨它的原理,并构建自己的客户端来找到其他peer并交换数据。
寻找Peer
现在的问题是:我们想要使用BitTorrent下载文件,但它是点对点协议,我们不知道去哪里找到其他peer。这就像搬到一个新城市尝试结交朋友一样——也许我们会去当地的酒吧或聚会活动!集中式的场所正是**tracker(跟踪器)**的概念所在。这些中心化的服务器会将peer互相介绍给对方。
当然,如果这些集中式服务器帮助peer交换非法内容,它们可能会被执法机构查封。你可能还记得一些追踪器如TorrentSpy、Popcorn Time和KickassTorrents被没收和关闭的消息。新的方法通过使peer发现过程也变得分布式来去除中间人。虽然我们不会实现这些功能,但如果你感兴趣,可以研究一下DHT、PEX和磁力链接等概念。
解析.torrent文件
.torrent文件描述了要下载的文件内容以及连接到tracker的信息。它是启动下载过程所需的一切。Debian的.torrent文件如下所示:
d8:announce41:http://bttracker.debian.org:6969/announce7:comment35:"Debian CD from cdimage.debian.org"13:creation datei1573903810e9:httpseedsl145:https://cdimage.debian.org/cdimage/release/10.2.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.2.0-amd64-netinst.iso145:https://cdimage.debian.org/cdimage/archive/10.2.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.2.0-amd64-netinst.isoe4:infod6:lengthi351272960e4:name31:debian-10.2.0-amd64-netinst.iso12:piece lengthi262144e6:pieces26800:�����PS�^�� (二进制散列块)e
这段混乱的内容以Bencode格式编码(读作“bee-encode”),我们需要将其解码。Bencode可以编码类似于JSON的结构类型,如字符串、整数、列表和字典。Bencoded数据不如JSON易于人类阅读或编写,但它能高效处理二进制数据,且解析起来非常简单。
经过整理后,我们的.torrent文件如下:
d
8:announce
41:http://bttracker.debian.org:6969/announce
7:comment
35:"Debian CD from cdimage.debian.org"
13:creation date
i1573903810e
4:info
d
6:length
i351272960e
4:name
31:debian-10.2.0-amd64-netinst.iso
12:piece length
i262144e
6:pieces
26800:�����PS�^�� (每段文件的SHA-1哈希值组成的二进制块)
e
e
在这个文件中,我们可以找到tracker的URL、创建日期(Unix时间戳)、文件名称和大小,以及一个包含每个**片段(piece)**SHA-1哈希值的大二进制块。每个片段的大小通常在256KB到1MB之间。这意味着一个大文件可能由数千个片段组成。我们将从其他peer下载这些片段,根据.torrent文件中的哈希值进行校验,然后将它们组装成完整的文件。
从Tracker获取Peer
现在我们已经拥有了关于文件及其tracker的信息,让我们联系tracker来宣布我们的存在,并检索其他peer的列表。我们只需要向.torrent文件中提供的announce
URL发送GET请求,并附加一些查询参数即可。
以下是构建tracker URL的函数:
func (t *TorrentFile) buildTrackerURL(peerID [20]byte, port uint16) (string, error) {
base, err := url.Parse(t.Announce)
if err != nil {
return "", err
}
params := url.Values{
"info_hash": []string{string(t.InfoHash[:])},
"peer_id": []string{string(peerID[:])},
"port": []string{strconv.Itoa(int(port))},
"uploaded": []string{"0"},
"downloaded": []string{"0"},
"compact": []string{"1"},
"left": []string{strconv.Itoa(t.Length)},
}
base.RawQuery = params.Encode()
return base.String(), nil
}
解析Tracker响应
我们收到的响应也是Bencode编码的:
d
8:interval
i900e
5:peers
252:(另一个长二进制块)
e
interval
告诉我们应该多久再次连接tracker刷新peer列表。例如,900表示每隔15分钟(900秒)重新连接一次。peers
是一个包含每个peer IP地址的长二进制块。它由**六个字节