BT Tracker的原理及.Net Core簡單實現Tracker Server

最近很忙,自上次Blog被盜 帖子所有丟失後也不多時間更新Blog了,閒暇在國外站點查閱資料時正好看到一些Tracker 的協議資料,也就今天記錄並實踐了下,再次分享給你們但願能夠幫到須要的小夥伴。算法

首先咱們來了解下BT Trackersql

1、作種數據庫

 

    如今不少BT軟件都提供了作種功能,在作種時,咱們都必須指定tracker服務器地址,若是該地址無效,則作出來的種子對BT協議來講是沒有任何實際意義的。api

2、bt tracker服務瀏覽器

    對於純BT協議來講,每一個BT網絡中至少要有一臺Tracker服務器(追蹤服務器),tracker主要基本工做有如下幾個方面:服務器

  •  記錄種子信息(torrent文件信息)
  •  記錄節點信息
  •  計算並返回節點列表給BT客戶端

    每次咱們利用BT軟件作完種子後,總要找個論壇之類的來上傳本身的種子,這樣別人就能夠下載到這個種子。爲何要上傳種子呢?緣由:網絡

  • 上傳種子,其實就是把種子信息記錄到tracker服務器上
  • 種子能夠在論壇傳播,種子的擴展程度就決定了種子的健康度和下載度

    當其餘用戶用BT軟件打開種子後,BT軟件會對種子進行解析(BDecode),主要獲得種子的相關信息,包括:文件名、文件大小、tracker地址等。而後BT軟件會向tracker地址發送請求報文,開始進行下載。BT向tracker發送的是Get請求,請求的內容主要有如下幾個方面:ide

info_hash測試

必填ui

種子文件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服務器應該儘可能返回種子節點給客戶端,幫助客戶端儘快完成下載,使其成爲種子節點。

 

3、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進行下載測試並輔種測試:

至此我進行了作種下載測試均一切正常,若是你們在閱讀此文有疑問之處還及不足之處望留言 再次感謝閱讀。

相關文章
相關標籤/搜索