Главная
Блог разработчиков phpBB
 
+ 17 предустановленных модов
+ SEO-оптимизация форума
+ авторизация через соц. сети
+ защита от спама

BitTorrent Tracker на C#

Anna | 18.06.2014 | нет комментариев

Долгое время я искал в сети пример простейшего tracker-а на C#, но, к сожалению, мои искания триумфом не увенчались. Следственно я решил испробовать себя в написании tracker-а на C#, а получив больше-менее рабочую версию — поделиться навыком ее создания со всеми. А заодно и получить как дозволено огромнее советов по ее усовершенствованию.

Но давайте начнем с начала…

Первое, что я сделал — это открыл спецификацию протокола BitTorrent:www.bittorrent.org/beps/bep_0003.html.

В ней написано, что заказчик отправляет серверу GET-запрос со следующими данными: info_hash, peer_id, ip, port, uploaded, downloaded, left и event.

Соответственно, на сервере БД я сотворил следующую таблицу:

CREATE TABLE [dbo].[psy_trance_fm_bittorrent_announces]
(
	[id] [int] IDENTITY(1,1) NOT NULL,
	[info_hash] [char](40) NOT NULL,
	[peer_id] [char](40) NOT NULL,
	[ip] [varchar](512) NOT NULL,
	[port] [int] NOT NULL,
	[uploaded] [int] NOT NULL,
	[downloaded] [int] NOT NULL,
	[left] [int] NOT NULL,
	[event] [varchar](512) NULL
)

Также в спецификации написано, что сервер отправляет заказчику данные в формате text/plain в виде bencoded-словаря.

Bencoded-словарь может содержать четыре типа данных: string, int, list и dictionary.

Я написал четыре функции для кодирования в bencode — по одной для всякого типа данных:

        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();
        }

Обращаю ваше внимание на то, что для словарей я применял тип SortedDictionary<string, object>, а не Dictionary<string, object>. Все потому, что по спецификации ключи в словарях обязаны быть отсортированы.

Ну а дальше началось самое интересное…

Казалось бы, для того, Дабы получить из GET-запроса info_hash и peer_id довольно применять Request.QueryString[«info_hash»] и Request.QueryString[«peer_id»] соответственно, но эти способы возвращали полнейшую белиберду. Я длинное время не мог осознать, в чем же дело…

А дело было в дальнейшем: info_hash, передаваемый от заказчика к серверу, выглядит приблизительно так: %124Vx%9A%BC%DE%F1%23Eg%89%AB%CD%EF%124Vx%9A. Request.QueryString[info_hash] считает, что это строка в формате UTF-8 и декодирует ее.

В этом дозволено удостовериться, посмотрев Reflector-ом функцию FillFromString, скажем.

Для того, Дабы обойти данный момент, я решил поработать с Request.Url.Query, возвращающий «сырую» строку.

Собственно, я взял код функции FillFromString из Reflector-а и убрал из него пару строк, отвечающих за декодирование:

        string s = Request.Url.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);
        }

Ну а для того, Дабы воротить info_hash и peer_id в их начальный шестнадцатеричный формат я написал еще две строчки кода:

        parameters["@info_hash"] = BitConverter.ToString(HttpUtility.UrlDecodeToBytes((string)parameters["@info_hash"])).Replace("-", "").ToLower();
        parameters["@peer_id"] = BitConverter.ToString(HttpUtility.UrlDecodeToBytes((string)parameters["@peer_id"])).Replace("-", "").ToLower();

По спецификации, ip и event — опциональные параметры. Ip большиство заказчиков на сервер не передает, а event передает только в 3 случаях: started, completed и stopped.

Следственно я решил проверить, имеются ли они в коллекции параметров, ну а если нет — то добавить их:

        if (parameters.ContainsKey("@ip") == false)
        {
            parameters.Add("@ip", Request.UserHostAddress);
        }

        if (parameters.ContainsKey("@event") == false)
        {
            parameters.Add("@event", DBNull.Value);
        }

Дальше все легко. Проверяем, имеется ли в базе данных раздача, соответствующая переданным info_hash и peer_id, если нет, то добавляем ее, если да, то легко обновляем данные о раздаче.

psy_trance_fm.execute_scalar и psy_trance_fm.execute_non_query — это функции для работы с базой данных, они дюже классические и приводить их тут смысла не вижу.

        psy_trance_fm psy_trance_fm = new psy_trance_fm();

        if (psy_trance_fm.execute_scalar("SELECT * FROM [dbo].[psy_trance_fm_bittorrent_announces] WHERE [info_hash] = @info_hash AND [peer_id] = @peer_id", parameters, CommandType.Text) == null)
        {
            psy_trance_fm.execute_non_query("INSERT INTO [dbo].[psy_trance_fm_bittorrent_announces] ([info_hash], [peer_id], [ip], [port], [uploaded], [downloaded], [left], [event]) VALUES (@info_hash, @peer_id, @ip, @port, @uploaded, @downloaded, @left, @event)", parameters, CommandType.Text);
        }
        else
        {
            psy_trance_fm.execute_non_query("UPDATE [dbo].[psy_trance_fm_bittorrent_announces] SET [ip] = @ip, [port] = @port, [uploaded] = @uploaded, [downloaded] = @downloaded, [left] = @left, [event] = @event WHERE [info_hash] = @info_hash AND [peer_id] = @peer_id", parameters, CommandType.Text);
        }

Позже того, как мы записали данные в БД нам необходимо воротить bencoded-словарь заказчику.

Это делается дальнейшим образом:

        SortedDictionary<string, object> sorted_dictionary = new SortedDictionary<string, object>(StringComparer.Ordinal);

        sorted_dictionary.Add("interval", 60);

        List<object> peers = new List<object>();

        DataTable data_table = psy_trance_fm.fill("SELECT * FROM [dbo].[psy_trance_fm_bittorrent_announces] WHERE [info_hash] = @info_hash", parameters, CommandType.Text);

        foreach (DataRow data_row in data_table.Rows)
        {
            SortedDictionary<string, object> peer = new SortedDictionary<string, object>(StringComparer.Ordinal);

            peer.Add("peer id", data_row["peer_id"]);
            peer.Add("ip", data_row["ip"]);
            peer.Add("port", data_row["port"]);

            peers.Add(peer);
        }

        sorted_dictionary.Add("peers", peers);

        bencode bencode = new bencode();
        Response.Write(bencode.encode(sorted_dictionary));

Ну вот и все! Примитивный C# BitTorrent Tracker готов. Да, в нем отсутствуют какие-либо обработки ошибок, статистика и другое-другое-другое. Но он работает!

Я дюже верю, что разбирающиеся люди подскажут, как дозволено его усовершенствовать, какие в нем есть ошибки и вообще дадут побольше советов.

Спасибо, что прочитали!

 

Источник: programmingmaster.ru

 

Оставить комментарий
Форум phpBB, русская поддержка форума phpBB
Рейтинг@Mail.ru 2008 - 2017 © BB3x.ru - русская поддержка форума phpBB