zoukankan      html  css  js  c++  java
  • BT Tracker的原理及.Net Core简单实现Tracker Server

    最近很忙,自上次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", 60);
                    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(1);
    
                SortedDictionary<string, object> parameters = new SortedDictionary<string, object>(StringComparer.Ordinal);
    
                int num = (s != null) ? s.Length : 0;
                for (int i = 0; i < num; i++)
                {
                    int startIndex = i;
                    int num4 = -1;
                    while (i < num)
                    {
                        char ch = s[i];
                        if (ch == '=')
                        {
                            if (num4 < 0)
                            {
                                num4 = i;
                            }
                        }
                        else if (ch == '&')
                        {
                            break;
                        }
                        i++;
                    }
                    string str = null;
                    string str2 = null;
                    if (num4 >= 0)
                    {
                        str = s.Substring(startIndex, num4 - startIndex);
                        str2 = s.Substring(num4 + 1, (i - num4) - 1);
                    }
                    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进行下载测试并辅种测试:

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

  • 相关阅读:
    selenium webdriver使用过程中出现Element is not currently visible and so may not be interacted with的处理方法
    转:使用C#的HttpWebRequest模拟登陆网站
    第一个WCF服务
    WinForm DataGridview.AutoSizeColumnsMode属性
    Winform 程序中DataGridView 控件添加行号
    DevExpress GridControl行颜色标识
    基于T4模板的文档生成
    NHibernate 支持的数据库及配置参数
    MahApps.Metro
    Log4Net 配置文件样例
  • 原文地址:https://www.cnblogs.com/yangzhili/p/10092675.html
Copyright © 2011-2022 走看看