以 debian-10.9.0-amd64-netinst.iso
为例
debian操作系统下载页
种子下载页
种子链接
d
8:announce
41:http://bttracker.debian.org:6969/announce
7:comment
35:"Debian CD from cdimage.debian.org"
13:creation date
i1616846384e
9:httpseeds
l145:https://cdimage.debian.org/cdimage/release/10.9.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.9.0-amd64-netinst.iso145:https://cdimage.debian.org/cdimage/archive/10.9.0//srv/cdbuilder.debian.org/dst/deb-cd/weekly-builds/amd64/iso-cd/debian-10.9.0-amd64-netinst.isoe
4:info
d
6:length
i353370112e
4:name
31:debian-10.9.0-amd64-netinst.iso
12:piece length
i262144e
6:pieces
26960:�^K�#��$n*6���S#'��Lz* (binary blob of the hashes of each piece)
e
e
.torrent文件部分内容
经过解析,我们可以获取到一个Tracker的主服务器地址,以及可能存在的Tracker服务器备用节点列表(有些种子torrent文件里面是没有这个的)
就像这个debian-10.9.0-amd64-netinst.iso的torrent文件,解析出来就是没有备用节点列表的。
我们需要请求这个服务器地址获取可用的peer节点列表。
在开始之前我们需要了解一下P2P(peer-to-peer,对等计算)
传统的客户端/服务器(C/S架构)关系中,下载器连接到中央服务器(例如:在Netflix上观看电影或加载您正在阅读的网页)。
BitTorrent网络中的参与者(称为peers)之间交换下载文件的分块-这就是p2p(peer-to-peer)。
网络的参与者共享他们所拥有的一部分硬件资源(处理能力、存储能力、网络连接能力等),这些共享资源通过网络提供服务和内容,能被其他的对等节点(peer)直接访问而无需经过中间实体。此网络的参与者既是资源提供者(Server),又是资源获取者(Client)。
不难看出去中心化是P2P的一个很突出的特点。
所以我们需要从一个地方获取peer节点列表,这个“地方”就是Tracker服务器,.torrent文件解析出来的 announce
字段就是Tracker服务器的URL(目前遇到过http协议和udp协议,我们主要分析http协议的实际文件下载流程)
在我们发起请求之前,在发起请求之前,我们先稍微理一下几个概念。
一起来猜想一下,我们对Tracker服务器发起的请求可能需要包含的内容,可能是下面这样的
“我(是谁)想要知道文件 debian-10.9.0-amd64-netinst.iso(文件id) 的所有peers节点列表”
这应该是问一个服务器获取数据一个必要条件吧,我们必须得告诉服务器,我们需要获取 peer列表 的对应的文件id,没有这个id,服务器也不知道该该你谁的 peer列表。
有时候我们还需要告诉服务器来获取这个列表的“我”是谁。
所以这里就引出了两个概念 info hash 和 peerId。
import (
"bytes"
"crypto/sha1"
"fmt"
"github.com/jackpal/bencode-go"
)
type singleTorrent struct {
...
Info singleInfo `bencode:"info"`
...
}
// 单文件
type singleInfo struct {
Pieces string `bencode:"pieces"`
PieceLength int `bencode:"piece length"`
Length int `bencode:"length"`
Name string `bencode:"name"`
// 文件发布者
Publisher string `bencode:"publisher,omitempty"`
// 文件发布者的网址
PublisherUrl string `bencode:"publisher-url,omitempty"`
NameUtf8 string `bencode:"name.utf-8,omitempty"`
PublisherUtf8 string `bencode:"publisher.utf-8,omitempty"`
PublisherUrlUtf8 string `bencode:"publisher-url.utf-8,omitempty"`
MD5Sum string `bencode:"md5sum,omitempty"`
Private bool `bencode:"private,omitempty"`
}
// hash 生成info hash
func hash(i interface{
}) ([20]byte, error) {
// name, size, and piece hashes
var buf bytes.Buffer
err := bencode.Marshal(&buf, i)
if err != nil {
return [20]byte{
}, err
}
// info hash,在与跟踪器和对等设备对话时,它唯一地标识文件
h := sha1.Sum(buf.Bytes())
return h, nil
}
// splitPieceHashes 切割Pieces
func splitPieceHashes(pieces string) ([][20]byte, error) {
// SHA-1 hash的长度
hashLen := 20
buf := []byte(pieces)
if len(buf)%hashLen != 0 {
// 片段的长度不正确
err := fmt.Errorf("Received malformed pieces of length %d", len(buf))
return nil, err
}
// hash 的总数
numHashes := len(buf) / hashLen
hashes := make([][20]byte, numHashes)
for i := 0; i < numHashes; i++ {
copy(hashes[i][:], buf[i*hashLen:(i+1)*hashLen])
}
return hashes, nil
}
// buildTrackerURL 构建追踪器的url
// peerID 下载者的标识符
func (t *TorrentFile) buildTrackerURL(peerID [20]byte, port uint16) (*url.URL, error) {
base, err := url.Parse(t.Announce)
if err != nil {
return &url.URL{
}, err
}
params := url.Values{
"info_hash": []string{
string(t.InfoHash[:])},
"peer_id": []string{
string(peerID[:])},
// 客户端监听端口.典型的端口是6881-6889.
"port": []string{
strconv.Itoa(int(Port))},
// 上传总量
"uploaded": []string{
"0"},
// 下载总量
"downloaded": []string{
"0"},
// 此参数值为1,表示指望获得紧凑模式的节点列表.不然表示指望获得普通模式的节点列表.指出客户端是否支持压缩模式. 若是是,伙伴列表将被一个伙伴字符串所代替.每一个伙伴占6个字节.前4个字节是主机(网络字序) , 后2个字节是端口(网络字序).
"compact": []string{
"1"},
// 剩余下载字节, 十进制ASCII码数字
"left": []string{
strconv.Itoa(t.Length)},
}
base.RawQuery = params.Encode()
return base, nil
}
最后生成的发送请求url如下
http://bttracker.debian.org:6969/announce?compact=1&downloaded=0&info_hash=%9F%29%2C%93%EB%0D%BD%D7%FFzJ%A5Q%AA%A1%EA%7C%AF%E0%04&left=353370112&peer_id=%E2%DA%C30%E9%B8%25%98%CDr%89%92b%BB%29%1B%2C%A3%A5%B1&port=6882&uploaded=0
// requestPeers 获取 peers
func (t *TorrentFile) requestPeers(peerID [20]byte, port uint16) error {
// 构建URL
base, er := t.buildTrackerURL(peerID, port)
if er != nil {
return er
}
fmt.Println(base)
switch base.Scheme {
// 目前只适配了http协议
case "http", "https":
c := &http.Client{
Timeout: 30 * time.Second}
resp, err := c.Get(base.String())
if err != nil {
return err
}
defer resp.Body.Close()
trackerResp := bencodeTrackerResp{
}
all, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(all))
err = bencode.Unmarshal(bytes.NewReader(all), &trackerResp)
if err != nil {
return err
}
t.Peers, err = peers.Unmarshal([]byte(trackerResp.Peers))
return err
case "udp":
// host ip:port
_, err := net.Dial("udp", base.Host)
if err != nil {
fmt.Println(err)
return err
}
return nil
default:
return errors.New("未适配的协议")
}
}
Tracker响应给我们的数据为
d
8:interval
i900e
5:peers
252:(another long binary blob)
e
Big-endian(大端模式,与之对应的是小端模式(Little-endian))
是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中
数字0x12345678在内存中的大端模式表示形式为:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78
我们就可以从peers字段中获取真实下载文件的地址了。
失败的响应
d14:failure reason45:info hash is not authorized with this trackere
全部代码的Gitee
参考
全部代码的Gitee
参考了下面的链接,不过这位大神只做了单文件下载,没有做多文件的,只支持TCP协议。
原项目地址
博客
中文