最近很忙,自上次Blog被盗 帖子全部丢失后也很少时间更新Blog了,闲暇在国外站点查阅资料时正好看到一些Tracker 的协议资料,也就今天记录并实践了下,再次分享给大家希望可以帮到需要的小伙伴。

首先我们来了解下BT Tracker

一、做种

现在很多BT软件都提供了做种功能,在做种时,我们都必须指定tracker服务器地址,如果该地址无效,则做出来的种子对BT协议来说是没有任何实际意义的。

二、bt tracker服务

对于纯BT协议来说,每个BT网络中至少要有一台Tracker服务器(追踪服务器),tracker主要基本工作有以下几个方面:

  • 记录种子信息(torrent文件信息)
  • 记录节点信息
  • 计算并返回节点列表给BT客户端

每次我们利用BT软件做完种子后,总要找个论坛之类的来上传自己的种子,这样别人就可以下载到这个种子。为什么要上传种子呢?原因:

  • 上传种子,其实就是把种子信息记录到tracker服务器上
  • 种子可以在论坛传播,种子的扩展程度就决定了种子的健康度和下载度

当其他用户用BT软件打开种子后,BT软件会对种子进行解析(BDecode),主要得到种子的相关信息,包括:文件名、文件大小、tracker地址等。然后BT软件会向tracker地址发送请求报文,开始进行下载。BT向tracker发送的是Get请求,请求的内容主要有以下几个方面:

info_hash

必填

种子文件info字段的SHA1值(20字节)

peer_id

必填

节点标识,由BT客户端每次启动时随机生成

port

必填

节点端口,主要用于跟其他节点交互

uploaded

必填

总共上传的字节数,初始值为0

downloaded

必填

总共下载的字节数,初始值为0

left

必填

文件剩余的待下载字节数

numwant

必填

BT客户端期望得到的节点数

ip

选填

BT客户端IP,选填的原因是Tracker可以得到请求的IP地址,不需要客户端直接上传

event

选填

started/stopped/completed/空。当BT客户端开始种子下载时,第一个发起的请求为started,

在下载过程中,该值一直为空,直到下载完成后才发起completed请求。做种过程中,发送

的event也为空。如果BT客户端停止做种或退出程序,则会发起stopped请求。

tracker收到该请求后主要进行以下几步处理:

1. 根据info_hash查找种子信息,如果tracker没有该种子的任何信息,tracker服务器可以返回错误或返回0个种子数

2. 如果tracker找到了种子信息,接下来就会去查找是否数据库中已存在该peer_id的节点。接下来根据event的值进行相关处理。

3. 如果event是stopped,说明该节点已不可用,系统会删除tracker上关于该节点的记录信息。

4. 如果event是completed,说明种子节点+1,非种子-1。

5. 如果event是started,说明这是种子第一次连接tracker,tracker需要记录该节点信息,此外如果left=0,说明这是一个种子节点。

6. 如果event是空,则说明节点正在下载或上传,需要更新tracker服务器上该节点的信息。

7. 最后tracker从本地挑选出numwant个节点信息返回给BT客户端,实际返回的节点数不一定就是numwant,tracker只是尽量达到这个数量。

Tracker响应

Tracker正常返回的信息结构主要是:

interval

必填

请求间隔(秒)

complete

选填

种子节点数

Incomplete

选填

非种子节点数

peers

ip

必填

IP地址

peer_id

选填

节点标识

port

必填

端口

如果Tracker检查发现异常,可以返回错误信息:

failure reason

错误原因

Tracker如何挑选种子节点并返回给客户端?

最普遍也是最简单的方式,那就是随机返回,tbsource采用的就是随机返回的机制。不少研究论文也提出了相关的算法,如IP地址策略和阶段返回策略。

IP地址策略是指根据IP地址所含拓扑信息来判断两个节点的距离,从而返回距离请求节点较近的节点列表。该方法主要适用于IPV6。

阶段返回策略,根据节点的下载进度,返回下载进度相近的节点列表。

个人观点:无论tracker采用什么算法,对BT客户端来说,能够提高的下载效率都是很有限的,采用“高级”的算法有时反而会增加tracker的负载。因此随机返回还算是比较高效的。

Bt协议中,有两个策略可以用来提高整个BT网络的健壮性和下载速度,它们分别是:最少片段优先策略(BT客户端处理)和最后阶段模式。为了响应“最后阶段模式”,当种子节点的下载进度大于80%(个人指定)时,tracker服务器应该尽量返回种子节点给客户端,帮助客户端尽快完成下载,使其成为种子节点。

三、private tracker原理

Privatetracker简称PT,目前主要应用于高清视频下载。其实PT就是“我为人人,人人为我”这个目标的最佳实践者。在实际的BT下载过程中,用户通过种子下载完文件后,出于“自私”的考虑(怕占用自己带宽),往往会退出做种,从而降低种子的热度。这就是为什么一个种子过了一段时间后,往往下载速度很慢或下载不完。

为了真正地实现BT理念,PT强制每个下载者必须上传一定量数据后,才能进行下载。如何保证这种行为呢?

现在的PT一般存在于网络社区中,每个注册网络社区的用户都会分配到一个随机的KEY,任何从社区下载的种子,都会包含用户的KEY。每次用户通过种子下载时,都会连接到社区的tracker服务器上,tracker服务器会检查KEY对应用户的上传下载量,如果上传量不满足标准,则tracker服务器会记录相关信息,并对该用户的下载及社区活动进行相关限制。

了解的基础的一些原理后 我们从实践开始入手:

封装Tracker类及数据请求上下文:

namespace WebApplication8
{
public class TrackerContext : DbContext
{
public DbSet<Tracker> Bittorrents { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=Tracker.db");
}
} public class Tracker
{
public int Id { get; set; }
//InfoHash
public string InfoHash { get; set; }
//PeerId
public string PeerId { get; set; }
//客户端的IP地址
public string Ip { get; set; }
//客户端的端口
public int Port { get; set; }
//上传的字节数量
public int Uploaded { get; set; }
//下载的字节数量
public int Downloaded { get; set; }
//文件剩余的待下载字节数
public int Left { get; set; }
//客户端 事件 started/stopped/completed/空
public string Event { get; set; }
}
}

服务端 简单实现:

namespace WebApplication8.Controllers
{
[Route("[controller]")]
[ApiController]
public class announceController : ControllerBase
{
private TrackerContext _db;
public announceController(TrackerContext db)
{
_db = db;
}
// GET api/values
[HttpGet]
public string Get()
{
try
{
//?info_hash=o%b8t%7c~%e86%fc2%878%5c%f5%fbj0%40%26-a&peer_id=-UT354S-%e8%ad%86%f5%0d%ee%86%40%9aXo%f9&port=53974&uploaded=0&downloaded=0&left=0&corrupt=0&key=E96680BC&event=started&numwant=200&compact=1&no_peer_id=1
var dic = GetDic(Request.QueryString.ToString());
var infoHash = BitConverter.ToString(HttpUtility.UrlDecodeToBytes(dic["@info_hash"].ToString())).Replace("-", "").ToLower();
var peer_id = BitConverter.ToString(HttpUtility.UrlDecodeToBytes(dic["@peer_id"].ToString())).Replace("-", "").ToLower(); //判断是否存在该tracker
var entity = _db.Bittorrents.FirstOrDefault(p => p.InfoHash == infoHash && p.PeerId == peer_id);
//不存在插入tracker信息
if (entity == null)
{
_db.Bittorrents.Add(new Tracker
{
InfoHash = infoHash,
Ip = Request.HttpContext.Connection.RemoteIpAddress.ToString(),
Left = Convert.ToInt32(dic["@left"]),
Uploaded = Convert.ToInt32(dic["@uploaded"]),
Downloaded = Convert.ToInt32(dic["@downloaded"]),
Event = dic["@event"].ToString(),
PeerId = peer_id,
Port = Convert.ToInt32(dic["@port"])
});
_db.SaveChanges();
}
else
{
//存在更新Tracker信息
entity.Ip = Request.HttpContext.Connection.RemoteIpAddress.ToString();
entity.Uploaded = Convert.ToInt32(dic["@uploaded"]);
entity.Downloaded = Convert.ToInt32(dic["@downloaded"]);
entity.Left = Convert.ToInt32(dic["@left"]);
entity.Port = Convert.ToInt32(dic["@port"]);
entity.Event = dic.ContainsKey("@event") ? dic["@event"].ToString() : null;
_db.SaveChanges();
}
dic.Clear();
//构造tracker信息列表 返回给客户端 interval 客户端心跳请求间隔 单位:秒 会间隔后自动心跳上报客户端的信息
dic.Add("interval", );
List<object> peers = new List<object>();
_db.Bittorrents.Where(p => p.InfoHash == infoHash).ToList().ForEach(o =>
{
SortedDictionary<string, object> peer = new SortedDictionary<string, object>(StringComparer.Ordinal); peer.Add("peer id", o.PeerId);
peer.Add("ip", o.Ip);
peer.Add("port", o.Port); peers.Add(peer);
});
dic.Add("peers", peers);
return encode(dic);
}
catch (Exception)
{
throw new Exception("请遵循Tracker协议,禁止浏览器直接访问");
} } public SortedDictionary<string, object> GetDic(string query)
{
string s = query.Substring(); SortedDictionary<string, object> parameters = new SortedDictionary<string, object>(StringComparer.Ordinal); int num = (s != null) ? s.Length : ;
for (int i = ; i < num; i++)
{
int startIndex = i;
int num4 = -;
while (i < num)
{
char ch = s[i];
if (ch == '=')
{
if (num4 < )
{
num4 = i;
}
}
else if (ch == '&')
{
break;
}
i++;
}
string str = null;
string str2 = null;
if (num4 >= )
{
str = s.Substring(startIndex, num4 - startIndex);
str2 = s.Substring(num4 + , (i - num4) - );
}
else
{
str2 = s.Substring(startIndex, i - startIndex);
} parameters.Add("@" + str, str2);
}
return parameters;
}
public string encode(string _string)
{
StringBuilder string_builder = new StringBuilder(); string_builder.Append(_string.Length);
string_builder.Append(":");
string_builder.Append(_string); return string_builder.ToString();
}
public string encode(int _int)
{
StringBuilder string_builder = new StringBuilder(); string_builder.Append("i");
string_builder.Append(_int);
string_builder.Append("e"); return string_builder.ToString();
}
public string encode(List<object> list)
{
StringBuilder string_builder = new StringBuilder(); string_builder.Append("l"); foreach (object _object in list)
{
if (_object.GetType() == typeof(string))
{
string_builder.Append(encode((string)_object));
} if (_object.GetType() == typeof(int))
{
string_builder.Append(encode((int)_object));
} if (_object.GetType() == typeof(List<object>))
{
string_builder.Append(encode((List<object>)_object));
} if (_object.GetType() == typeof(SortedDictionary<string, object>))
{
string_builder.Append(encode((SortedDictionary<string, object>)_object));
}
} string_builder.Append("e"); return string_builder.ToString();
} public string encode(SortedDictionary<string, object> sorted_dictionary)
{
StringBuilder string_builder = new StringBuilder(); string_builder.Append("d"); foreach (KeyValuePair<string, object> key_value_pair in sorted_dictionary)
{
string_builder.Append(encode((string)key_value_pair.Key)); if (key_value_pair.Value.GetType() == typeof(string))
{
string_builder.Append(encode((string)key_value_pair.Value));
} if (key_value_pair.Value.GetType() == typeof(int))
{
string_builder.Append(encode((int)key_value_pair.Value));
} if (key_value_pair.Value.GetType() == typeof(List<object>))
{
string_builder.Append(encode((List<object>)key_value_pair.Value));
} if (key_value_pair.Value.GetType() == typeof(SortedDictionary<string, object>))
{
string_builder.Append(encode((SortedDictionary<string, object>)key_value_pair.Value));
}
} string_builder.Append("e"); return string_builder.ToString();
}
}
}

Tracker 地址http://192.168.50.11:5000/announce  我是在本地部署进行了测试

sqlite数据库种的Tracker信息:

在我的另一台Nas进行下载测试并辅种测试:

至此我进行了做种下载测试均一切正常,如果大家在阅读此文有疑问之处还及不足之处望留言 再次感谢阅读。

BT Tracker的原理及.Net Core简单实现Tracker Server的更多相关文章

  1. [.NET Core] 简单读取 json 配置文件

    简单读取 json 配置文件 背景 目前发现网上的 .NET Core 读取配置文件有点麻烦,自己想搞个简单点的. .NET Core 已经不使用之前的诸如 app.config 和 web.conf ...

  2. [.Net Core] 简单使用 Mvc 内置的 Ioc

    简单使用 Mvc 内置的 Ioc 本文基于 .NET Core 2.0. 鉴于网上的文章理论较多,鄙人不才,想整理一份 Hello World(Demo)版的文章. 目录 场景一:简单类的使用 场景二 ...

  3. [.Net Core] 简单使用 Mvc 内置的 Ioc(续)

    简单使用 Mvc 内置的 Ioc(续) 本文基于 .NET Core 2.0. 上一章<[.Net Core] 简单使用 Mvc 内置的 Ioc>已经对日常 Mvc 中的 Ioc 的简单用 ...

  4. ASP.NET Core 简单实现七牛图片上传(FormData 和 Base64)

    ASP.NET Core 简单实现七牛图片上传(FormData 和 Base64) 七牛图片上传 SDK(.NET 版本):https://developer.qiniu.com/kodo/sdk/ ...

  5. P2P原理及UDP穿透简单说明(转)

    源: P2P原理及UDP穿透简单说明

  6. P2P原理及UDP穿透简单说明

    转:http://http://andylin02.iteye.com/blog/444666 P2P原理及UDP穿透简单说明 本文章出自cnntec.com的AZ猫著,如需要转发,请注明来自cnnt ...

  7. Fiddler-009-AutoResponder 简单的 MOCK SERVER 应用实例

    在我们日常的测试中经常需要测试特定的响应对应的客户端展示样式是否正确无误,实现测试方法一般有如下三种: 创建新的测试数据(工作量较大) 修改已有测试数据(例如修改对应的状态码,若是最终需要测试的按钮状 ...

  8. 最简单删除SQL Server中所有数据的方法

     最简单删除SQL Server中所有数据的方法 编写人:CC阿爸 2014-3-14 其实删除数据库中数据的方法并不复杂,为什么我还要多此一举呢,一是我这里介绍的是删除数据库的所有数据,因为数据之间 ...

  9. 编写一个简单的Web Server

    编写一个简单的Web Server其实是轻而易举的.如果我们只是想托管一些HTML页面,我们可以这么实现: 在VS2013中创建一个C# 控制台程序 编写一个字符串扩展方法类,主要用于在URL中截取文 ...

随机推荐

  1. eclipse中的实用快捷键

    之前有写过“myeclipse实用快捷键”,今天总结一下“eclipse中的快捷键”. 1.打开文件Crtl+Shift+R: 2. 打开类文件包括能看到字在哪个jar   Ctrl+Shift+T: ...

  2. ELK 日志管理系统,再次尝试记录

    简介: 第二次尝试 ELK 记录... 工作流程: 1.客户端的 Logstash 将日志信息采集到之后传输给 Redis 做消息队列 2.然后服务端的 Logstash 将日志从 Redis 中取出 ...

  3. 【原】Coursera—Andrew Ng机器学习—课程笔记 Lecture 16—Recommender Systems 推荐系统

    Lecture 16 Recommender Systems 推荐系统 16.1 问题形式化 Problem Formulation 在机器学习领域,对于一些问题存在一些算法, 能试图自动地替你学习到 ...

  4. Invalid command 'Header', perhaps misspelled or defined by a module not included in the server configuration

    在Apache的配置文件 httpd.conf 中开启  LoadModule headers_module modules/mod_headers.so  即可解决这个问题.

  5. Excel VBA入门(三) 流程控制1-条件选择

    VBA中的流程控制分为两种,其一是条件结构式的,即根据条件判断的结果去选择性执行相应的语句(块):另一种是循环,即循环地执行语句(块).本节介绍第一种. 1. IF if 语句其实包含有几种形式: ① ...

  6. cdoj31-饭卡(card) (01背包)

    http://acm.uestc.edu.cn/#/problem/show/31 饭卡(card) Time Limit: 3000/1000MS (Java/Others)     Memory ...

  7. 依靠反射来个Dbutils

    闲来无事,写个dbutils玩玩,不完善,满足基本增删改查,上代码 1.Dbutils package db; import annotation.Table; import java.util.*; ...

  8. 混合开发Js bridge新秀-DSBridge iOS篇

    这个DSBridge 和我之前开发做的混合开发 用的方式 很相似,所以觉得很是不错,推荐给你大家. DSBridge-IOS:https://github.com/wendux/DSBridge-IO ...

  9. jmeter-plugins-dubbo & DevToolBox

    jmeter-plugins-dubbo使用 A. 下载jmeter并安装,http://jmeter.apache.org/download_jmeter.cgi(文中使用的版本是3.3,理论上高版 ...

  10. SpringBoot集成freemarker和thymeleaf模板

    1.在MAVEN工程POM.XML中引入依赖架包 <!-- 引入 freemarker 模板依赖 --> <dependency> <groupId>org.spr ...