社区微信群开通啦,扫一扫抢先加入社区官方微信群
社区微信群
在上篇中,我们介绍了BT下载的原理,并通过示例搭建了一个p2p网络,了解了BT下载的原理和流程,从这篇文章将开始我们将用golang从零开始手写一个bt下载客户端。在开始动手之前,我们需要得到一个.torrent文件并解析它。
这是一个文本文件,包含了要我们开始下载的全部信息:要分享的文件信息和连接到tracker服务器的信息。它使用bencode编码,就像这样:
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�^�� (binary blob of the hashes of each piece)ee
上面那一堆“乱码”是用一种叫做bencode(发音:bee-encode)的编码方式生成的信息,它不是人类直接可读的,我们需要把它解码才能使用,但它能够高效的编码二进制数据,并且很容易解析。它使用的数据结构和JSON大致相同,共包含4种数据结构:string,integer,list,dictionary。
我们可以使用现成的工具bencode editor来读写.torrent文件
在这个文件里,我们可以找出来tracker服务器地址,创建时间(用unix时间戳表示),文件名、大小以及文件的每个分片。
实现一个bencode也许是很有趣的,但不是我们现在要关注的,我们将使用一个现成的库github.com/jackpal/bencode-go来完成。
package torrentfileimport ("bytes""crypto/sha1""fmt""os""github.com/jackpal/bencode-go")type TorrentFile struct {Announce stringInfoHash [20]bytePieceHashes [][20]bytePieceLength intLength intName string}type bencodeInfo struct {Pieces string `bencode:"pieces"`PieceLength int `bencode:"piece length"`Length int `bencode:"length"`Name string `bencode:"name"`NameUtf8 string `bencode:"name.utf-8"`}type bencodeTorrent struct {Announce string `bencode:"announce"`Info bencodeInfo `bencode:"info"`}func Open(path string) (TorrentFile, error) {file, err := os.Open(path)if err != nil {return TorrentFile{}, err}defer file.Close()bto := bencodeTorrent{}err = bencode.Unmarshal(file, &bto)if err != nil {return TorrentFile{}, err}return bto.toTorrentFile()}func (bto *bencodeTorrent) toTorrentFile() (TorrentFile, error) {infoHash, err := bto.Info.hash()if err != nil {return TorrentFile{}, err}pieceHashes, err := bto.Info.splitPieceHashes()if err != nil {return TorrentFile{}, err}t := TorrentFile{Announce: bto.Announce,InfoHash: infoHash,PieceHashes: pieceHashes,PieceLength: bto.Info.PieceLength,Length: bto.Info.Length,Name: bto.Info.Name,}return t, nil}
为了保持结构的扁平化,我们把一个结构分成了几个,并使用一个工具函数来合并他们。
后面我们将从tracker获取到peers的信息,敬请关注。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!