CのBitTorrentトラッカー#

長い間、C#で最も単純なトラッカーの例をネットワークで検索していましたが、残念ながら、検索は失敗しました。 そのため、C#でトラッカーを作成することに挑戦し、多かれ少なかれ機能するバージョンを受け取った後、それを作成した経験をすべての人と共有しました。 同時に、その改善について可能な限り多くのヒントを入手してください。



しかし、最初から始めましょう...



私が最初にしたことは、BitTorrentプロトコル仕様を開くことでした。



クライアントは、info_hash、peer_id、ip、port、uploaded、downloaded、left、eventの各データを含むサーバーにGETリクエストを送信します。



したがって、データベースサーバーで、次のテーブルを作成しました。



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 )
      
      







また、サーバーは、ベンコードされた辞書の形式でテキスト/プレーン形式でクライアントにデータを送信することも仕様に書かれています。



Bencodedディクショナリには、string、int、list、およびdictionaryの4つのデータタイプを含めることができます。



bencodeで4つのエンコード関数を作成しました-各データ型に1つです:



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







辞書には、Dictionary <string、object>ではなくSortedDictionary <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形式の文字列と見なし、デコードします。



たとえば、リフレクター関数FillFromStringを見て、これを確認できます。



この点を回避するために、生の文字列を返すRequest.Url.Queryを使用することにしました。



実際、ReflectorからFillFromString関数コードを取得し、デコードの原因となるいくつかの行を削除しました。



  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を元の16進形式に戻すために、さらに2行のコードを書きました。



  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は大部分のクライアントをサーバーに送信せず、イベント転送は開始、完了、停止の3つの場合にのみ行われます。



したがって、それらがパラメーターコレクションにあるかどうかを確認することにしましたが、そうでない場合は追加します。



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







データベースにデータを記録した後、ベンコードされた辞書をクライアントに返す必要があります。



これは次のように行われます。



  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が用意されています。 はい、エラー処理や統計などはありません。 しかし、それは機能します!



人々を理解することで、それを改善する方法、それに含まれるエラー、そして一般にさらなるヒントが得られることを本当に願っています。



読んでくれてありがとう!



All Articles