背景
在上篇中,我们介绍了BT下载的原理,并通过示例搭建了一个p2p网络,了解了BT下载的原理和流程,从这篇文章将开始我们将用golang从零开始手写一个bt下载客户端。在开始动手之前,我们需要得到一个.torrent文件并解析它。
.torrent文件结构
这是一个文本文件,包含了要我们开始下载的全部信息:要分享的文件信息和连接到tracker服务器的信息。它使用bencode 编码 ,就像这样:
d8:announce41:#34;Debian CD from cdimage.debian.org"13:creation datei1573903810e9:httpseedsl145: lengthi262144e6: pieces 26800:�����PS�^�� (binary blob of the hashes of each piece)ee
bencode
上面那一堆“乱码”是用一种叫做bencode(发音:bee-encode)的编码方式生成的信息,它不是人类直接可读的,我们需要把它解码才能使用,但它能够高效的编码二进制数据,并且很容易解析。它使用的数据结构和JSON大致相同,共包含4种数据结构:string, integer ,list,dictionary。
- string:用一个数字前缀加冒号表示长度如字符串spam表示为4:spam
- integer:用i表示开始e表示结束,如数字7表示为i7e
- list:用l表示开始e表示结束,如[‘spam’,7]表示为l4:spami7ee
- dictionary:用d表示开始e表示结束,如{spam:7}表示为d4:spami7ee
我们可以使用现成的工具bencode editor来读写.torrent文件
在这个文件里,我们可以找出来tracker服务器地址,创建时间(用unix时间戳表示),文件名、大小以及文件的每个分片。
解析.torrent文件
实现一个bencode也许是很有趣的,但不是我们现在要关注的,我们将使用一个现成的库 github .com/jackpal/bencode-go来完成。
package torrent file
import (
" byte s"
"crypto/sha1"
"fmt"
"os"
"github.com/jackpal/bencode-go"
)
type TorrentFile struct {
Announce string
InfoHash [20]byte
PieceHashes [][20]byte
PieceLength int
Length int
Name 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 的信息,敬请关注。