.torrent文件使用的是它独有的bencode编码。
支持下列类型:字节串、整数、列表和字典。
1.字符串:<字符串的长度>:<字符串的内容>
例如:
announce,编码后为 8:announce
name,编码后为 4:name
2.数字的存储格式: i<十进制整型数>e
例如:
4,编码后为 i4e
1024,编码后为 i1024e
3.列表的存储格式:l<子元素>e
子元素可以是字符串,整数,列表和字典,或者是它们的组合体
例如:
[“name”, “age”, “addr”],编码后为 l4:name3:age4:addre
[1, 2, 3, 4],编码后为 li1ei2ei3ei4ee
4.字典的存储格式:d<关键字><值>e
key只能是经过bencode编码的字符串,value则可以是字符串,整数,列表和字典,或者是它们的组合体,key和value必须是成对出现的
例如:
{ “name” => “Exler”, “age” => “18” },编码后为 d4:name5:Exler3:agei18ee
{“list”=> [1, 2, 3, 4]},编码后为 d4:listli1ei2ei3ei4eee
{“stus”=> [{“name” => “Exler”, “age” => 18}, {“name” => “Jack”, “age” => 19}]},编码后为 d4:stusld4:name5:Exler3:agei18eeeld4:name4:Jack3:agei19eeee
这个稍微有点复杂,拆解下
d
4:stus
l
d
4:name
5:Exler
3:age
i18e
e
e
l
d
4:name
4:Jack
3:age
i19e
e
e
e
格式有两种类型
• 单文件
• 多文件
单文件结构树形图:
Single-File Torrent
├─announce
├─announce-list
├─comment
├─comment.utf-8
├─creation date
├─encoding
├─info
│ ├─length
│ ├─name
│ ├─name.utf-8
│ ├─piece length
│ ├─pieces
│ ├─publisher
│ ├─publisher-url
│ ├─publisher-url.utf-8
│ └─publisher.utf-8
└─nodes
多文件torrent结构树形图:
Multi-file Torrent
├─announce
├─announce-list
├─comment
├─comment.utf-8
├─creation date
├─encoding
├─info
│ ├─files
│ │ ├─length
│ │ ├─path
│ │ └─path.utf-8
│ ├─name
│ ├─name.utf-8
│ ├─piece length
│ ├─pieces
│ ├─publisher
│ ├─publisher-url
│ ├─publisher-url.utf-8
│ └─publisher.utf-8
└─nodes
• announce:Tracker的主服务器
• announce-list:Tracker服务器备用节点列表
• comment:种子文件的注释
• comment.utf-8:种子文件注释的utf-8编码
• creation date:种子文件建立的时间,是从1970年1月1日00:00:00到现在的秒数。
• encoding:种子文件的默认编码,比如GB2312,Big5,utf-8等
• info:所有关于下载的文件的信息都在这个字段里,它包括多个子字段,而且根据下载的是单个文件还是多个文件,子字段的项目会不同,具体介绍在后面。
• nodes:最后的一个字段是nodes字段,这个字段包含一系列ip和相应端口的列表,是用于连接DHT初始node。
multi-file的info字段是个files列表
files的结构如下:
• lenghth:文件的大小,用byte计算;
• path:文件的名字,在下载时不可更改;
• path.utf-8:文件名的UTF-8编码。
info中其他字段含义如下:
• name:推荐的文件夹名,此项可于下载时更改;
• name.utf-8:推荐的文件夹名的utf-8编码;
• piece length:每个文件块的大小,用Byte计算;
• publisher:文件发布者的名字;
• publisher.utf-8:文件发布者的名字的utf-8编码;
• publisher-url:文件发布者的网址;
• publisher-url.utf-8:文件发布者网址的utf-8编码。
解析的主要代码,具体项目的git地址放在文末了
使用github.com/jackpal/bencode-go
这个库进行反序列化的操作
单文件的解析
package torrentfile
import (
"BTClient/p2p"
"bytes"
"github.com/jackpal/bencode-go"
)
type singleTorrent struct {
// `bencode:""`
// tracker服务器的URL 字符串
Announce string `bencode:"announce"`
// 备用tracker服务器列表 列表
// 发现 announce-list 后面跟了两个l(ll) announce-listll
AnnounceList [][]string `bencode:"announce-list"`
// 种子的创建时间 整数
CreatDate int64 `bencode:"creation date"`
// 备注 字符串
Comment string `bencode:"comment"`
// 创建者 字符串
CreatedBy string `bencode:"created by"`
Info singleInfo `bencode:"info"`
// 包含一系列ip和相应端口的列表,是用于连接DHT初始node
Nodes [][]interface{} `bencode:"nodes"`
// 文件的默认编码
Encoding string `bencode:"encoding"`
// 备注的utf-8编码
CommentUtf8 string `bencode:"comment.utf-8"`
}
// 单文件
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"`
}
// 将pieces(以前是一个字符串)拆分为一片哈希(每个[20]byte).以便以后可以轻松访问各个哈希.
// 还计算了整个bencoded infodict(包含名称.大小和片段哈希的dict)的SHA-1哈希.
// 将其称为infohash.在与Tracker服务器和Peer设备对话时.它唯一地标识文件.
// fileParser 单文件解析
func (bto *singleTorrent) fileParser(file []byte) error {
// 可以进行 反序列化 key value取值
// fileMetaData, er := bencode.Decode(file)
// if er != nil {
// }
//fmt.Println(fileMetaData)
err := bencode.Unmarshal(bytes.NewReader(file), &bto)
return err
}
func (bto *singleTorrent) toTorrentFile() (TorrentFile, error) {
infoHash, err := hash(bto.Info)
if err != nil {
return TorrentFile{}, err
}
// 每个分片的 SHA-1 hash 长度是20 把他们从Pieces中切出来
pieceHashes, err := splitPieceHashes(bto.Info.Pieces)
if err != nil {
return TorrentFile{}, err
}
tf := TorrentFile{
Announce: bto.Announce,
Torrent: p2p.Torrent{
InfoHash: infoHash,
PieceHashes: pieceHashes,
PieceLength: bto.Info.PieceLength,
Length: bto.Info.Length,
Name: bto.Info.Name,
},
}
// 添加 备用节点
tf.AnnounceList = []string{}
for _, v := range bto.AnnounceList {
tf.AnnounceList = append(tf.AnnounceList, v[0])
}
return tf, nil
}
多文件的解析
package torrentfile
import (
"BTClient/p2p"
"bytes"
"github.com/jackpal/bencode-go"
)
// 多文件 包含5:files
type multipleTorrent struct {
// `bencode:""`
// tracker服务器的URL 字符串
Announce string `bencode:"announce"`
// 备用tracker服务器列表 列表
// 发现 announce-list 后面跟了两个l(ll) announce-listll
AnnounceList [][]string `bencode:"announce-list"`
// 种子的创建时间 整数
CreatDate int64 `bencode:"creation date"`
// 备注 字符串
Comment string `bencode:"comment"`
// 创建者 字符串
CreatedBy string `bencode:"created by"`
Info multipleInfo `bencode:"info"`
// 包含一系列ip和相应端口的列表,是用于连接DHT初始node
Nodes [][]interface{} `bencode:"nodes"`
// 文件的默认编码
Encoding string `bencode:"encoding"`
// 备注的utf-8编码
CommentUtf8 string `bencode:"comment.utf-8"`
}
type multipleInfo struct {
// 每个块的20个字节的SHA1 Hash的值(二进制格式)
Pieces string `bencode:"pieces"`
// 每个块的大小,单位字节 整数
PieceLength int `bencode:"piece length"`
// 文件长度 整数
Length int `bencode:"length,omitempty"`
// 目录名 字符串
Name string `bencode:"name"`
Files []struct {
// 文件长度 单位字节 整数
Length int `bencode:"length"`
// 文件的路径和名字 列表
Path []string `bencode:"path"`
// path.utf-8:文件名的UTF-8编码
PathUtf8 string `bencode:"path.utf-8,omitempty"`
} `bencode:"files"`
NameUtf8 string `bencode:"name.utf-8,omitempty"`
}
// multipleParser 多文件解析
func (bto *multipleTorrent) fileParser(file []byte) error {
err := bencode.Unmarshal(bytes.NewReader(file), &bto)
return err
}
func (bto *multipleTorrent) toTorrentFile() (TorrentFile, error) {
infoHash, err := hash(bto.Info)
if err != nil {
return TorrentFile{}, err
}
// 每个分片的 SHA-1 hash 长度是20 把他们从Pieces中切出来
pieceHashes, err := splitPieceHashes(bto.Info.Pieces)
if err != nil {
return TorrentFile{}, err
}
tf := TorrentFile{
Announce: bto.Announce,
Torrent: p2p.Torrent{
InfoHash: infoHash,
PieceHashes: pieceHashes,
PieceLength: bto.Info.PieceLength,
Length: bto.Info.Length,
Name: bto.Info.Name,
},
}
// 添加 备用节点
tf.AnnounceList = []string{}
for _, v := range bto.AnnounceList {
tf.AnnounceList = append(tf.AnnounceList, v[0])
}
// 构建 fileInfo 列表
var fileInfo []p2p.FileInfo
for _, v := range bto.Info.Files {
path := ""
for _, p := range v.Path {
path += "/" + p
}
fileInfo = append(fileInfo, p2p.FileInfo{
Path: path,
Length: v.Length,
})
}
tf.File = fileInfo
return tf, nil
}
这是torrent文件解析的部分,后期会更新种子文件下载的内容。
参考了下面的链接,不过这位大神只做了单文件下载,没有做多文件的,只支持TCP协议。