Golang ------ torrent文件下载 (1)

  1. Golang ------ torrent文件解析
  2. Golang ------ torrent文件下载 (1)
  3. Golang ------ torrent文件下载 (2)

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节点列表。

peer-to-peer

在开始之前我们需要了解一下P2P(peer-to-peer,对等计算)

传统的客户端/服务器(C/S架构)关系中,下载器连接到中央服务器(例如:在Netflix上观看电影或加载您正在阅读的网页)。

BitTorrent网络中的参与者(称为peers)之间交换下载文件的分块-这就是p2p(peer-to-peer)。

网络的参与者共享他们所拥有的一部分硬件资源(处理能力、存储能力、网络连接能力等),这些共享资源通过网络提供服务和内容,能被其他的对等节点(peer)直接访问而无需经过中间实体。此网络的参与者既是资源提供者(Server),又是资源获取者(Client)。
在这里插入图片描述
不难看出去中心化是P2P的一个很突出的特点。

获取peer节点列表

所以我们需要从一个地方获取peer节点列表,这个“地方”就是Tracker服务器,.torrent文件解析出来的 announce 字段就是Tracker服务器的URL(目前遇到过http协议和udp协议,我们主要分析http协议的实际文件下载流程)

在我们发起请求之前,在发起请求之前,我们先稍微理一下几个概念。

info hash 和 peerId

一起来猜想一下,我们对Tracker服务器发起的请求可能需要包含的内容,可能是下面这样的

“我(是谁)想要知道文件 debian-10.9.0-amd64-netinst.iso(文件id) 的所有peers节点列表”

这应该是问一个服务器获取数据一个必要条件吧,我们必须得告诉服务器,我们需要获取 peer列表 的对应的文件id,没有这个id,服务器也不知道该该你谁的 peer列表。

有时候我们还需要告诉服务器来获取这个列表的“我”是谁。

所以这里就引出了两个概念 info hash 和 peerId。

  • info_hash:标识我们要下载的文件。这是我们之前根据bencode的info字段内容,计算出的信息哈希。Tracker服务器将使用它来确定向我们显示哪些Peers。
  • peer_id:一个20字节的名称。用于向跟踪者和Peers标识自己。可以随机生成20个随机字节。真正的BitTorrent客户端具有ID. 例如ID -TR2940-k8hj0wgej6ch.用于标识客户端软件和版本-在这种情况下.TR2940代表传输客户端2.94.
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
  • Interval:告诉我们应该多久再次连接到Tracker服务器以刷新我们的Peers列表。值900表示我们应该每15分钟(900秒)重新连接一次.
  • peers:是另一个长二进制Blob。其中包含每个Peers的IP地址。它由六个字节组成.每个组中的前四个字节代表Peers的IPv4地址-每个字节代表IP中的数字。最后两个字节将端口表示为big-endian uint16。

Big-endian(大端模式,与之对应的是小端模式(Little-endian))
是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址中
数字0x12345678在内存中的大端模式表示形式为:
低地址 -----------------> 高地址
0x12 | 0x34 | 0x56 | 0x78

我们就可以从peers字段中获取真实下载文件的地址了。

失败的响应

d14:failure reason45:info hash is not authorized with this trackere

全部代码的Gitee
参考
全部代码的Gitee
参考了下面的链接,不过这位大神只做了单文件下载,没有做多文件的,只支持TCP协议。
原项目地址
博客
中文

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值