BitTorrentクライアントをプログラムします。 ピュアデルフィ

Igor Antonov(Spider_NET)C#torrentクライアントを作成することについての記事を書いてから8年が経ちましたが、Delphiでそれを行う最も簡単な例はネットワークに登場していません。



本格的なbittorrentクライアントを作成するような「困難な」問題におけるDelphi言語の非効率性に対する疑念を払拭するために、私はこの記事を書くことにしました。



私たちのDelphiトレントクライアントはオープンソースであり、DHT、マグネットリンク、順次ダウンロードなど、ほとんどすべての最新のbittorrentテクノロジーをサポートするとすぐに言わなければなりません。



Delphiで既製のクライアントソースをインターネットで検索すると結果が得られましたが、これらの結果は理想とはほど遠いものでした。 最初の結果は、アルファ版を備えた長い間放棄されたTorrent Torque (2007)です。 TorrentTorque正常にコンパイルおよびテストできませんでした。

次の検索結果は、あまり知られていないAres Galaxy runetであることが判明しました。これは、非常に実用的であり、トレントクライアントのある国では人気があります。 コンパイルで苦しんでも、私は切望されたコードをテストすることができましたが、結局のところ、開発者によって長い間修正されなかった欠陥があることが判明しました。 さらに、Ares GalaxyはDelphi 7で記述されています。つまり、RAD Studioの新しいバージョンでコンパイルするには、膨大な量のコードを書き直す必要があります。 しかし、これは私を止めるものではなく、この問題を解決する別の方法を見つけました。



アレスギャラクシーのソースを理解し始めると、多くの操作が1つのスレッドで実行され、その結果、リスト内のすべてのトレントのダウンロードプロセスが短時間停止することがわかりました。 そのため、別のスレッドでコードの実行を遅くする欠陥とレンダリングされたプロシージャを修正することにしました。



肯定的な結果を受け取ったので、ソースをsourceforge.netに配置することにしました。 このコードは、Alexander Alekseevが一連の記事「プラグインシステムの開発」で詳細に説明したプラグインシステムを使用して、BTService dllライブラリの形式で実行されました。 そのため、このようなプラグインシステムを使用すると、Delphiだけでなく他のプログラミング言語でも、RAD Studioコンパイラでbittorrentクライアントを作成できます。 BTServiceライブラリとそのソースは、 http ://btservice.sourceforge.net/で入手できます。



それでは、BTServiceライブラリに基づいたシンプルなクライアントの作成を始めましょう。



クライアントインターフェイスは、最小限のコンポーネントセットでクラシックスタイルで実行できますが、ライブラリの基本機能が表示されます。







ツールバーの5つのボタン:マグネットリンクの追加、トレントの追加、トレントの作成、トレントの開始、トレントの停止。



トレントのリストを標準のTListViewに表示します。 ファイル、接続されたピア、およびトラッカーのリストもTListViewに配置され、TPeControlタブを開くと表示されます。 まあ、メインのStatusBarフォームの下部には、リストで選択されたトレントのマグネットリンク、4つのトレント状態、およびリスト内のすべてのトレントの一般的なダウンロードおよびアップロード速度が表示されます。



次に、順番に、トレントの作成、開始、停止のイベントを処理します。



トレントの作成とビットトレントプロトコルの仕様に関連するすべての詳細については説明しません。 イゴールは、彼の記事「BitTorrent Client Code。 パート1。」 すべてのトレント作成コードは、BTServiceライブラリで利用できます。 その実装に興味のある方は、ライブラリのソースコードをご覧ください。 さて、ライブラリと対話するコードのみを示します。



まず、「新しいトレントを作成」フォームを作成します。 「Add File」、「Add Folder」、「Create」の3つのボタンを配置します。 TPageControlを追加します。 最初のタブでは、主要なパラメーターを配置します。 パラメータ「Part size」はTComboboxで実行され、「Start download」および「Private torrent」はTCheckBoxで実行されます。 TPageControlの他のタブには、トレントファイルにトラッカー、Webシードアドレス、コメントを追加するTMemoフィールドを配置します。 トレントを作成するプロセスは、2つのTProgressBarに表示されます。 1つ目は単一ファイルのハッシュ実行を示し、2つ目はすべてのトレントファイルをハッシュする一般的なプロセスを示します。







「ファイルの追加」ボタンの場合、コードは次のようになります。



procedure TfCreateTorrent.btnAddFileClick(Sender: TObject); begin FormStyle := fsNormal; if OpenDialog1.Execute then begin ComboBox1.Text := PChar(OpenDialog1.Filename); //   btnCreate.Enabled := true; end; FormStyle := fsStayOnTop; end;
      
      





[フォルダの追加]ボタンの場合、コードは次のようになります。



 procedure TfCreateTorrent.btnAddFolderClick(Sender: TObject); var chosenDirectory: string; begin FormStyle := fsNormal; if SelectDirectory(' : ', '', chosenDirectory) then begin ComboBox1.Text := chosenDirectory; //   btnCreate.Enabled := true; end; FormStyle := fsStayOnTop; end;
      
      





つまり、「ソース選択」フィールド(ComboBox1.Text)に、トレントに追加するもの、フォルダー内の1つのファイル、または複数のファイルに応じて、ファイルまたはフォルダーのパスが追加されます。



次に、[作成]ボタンでコードを記述します。



トレントファイル作成コード
 procedure TfCreateTorrent.btnCreateClick(Sender: TObject); var BTCreateTorrent: IBTCreateTorrent; X: Integer; FindBTPlugin: Boolean; Id: string; begin if ButtonSave then begin if ComboBox1.Text[length(ComboBox1.Text)] = '\' then ComboBox1.Text := copy(ComboBox1.Text, 1, length(ComboBox1.Text) - 1); if FileExists(ComboBox1.Text) then begin SaveDialog1.Filter := 'Torrent Files (*.torrent)|*.torrent'; SaveDialog1.Filename := ComboBox1.Text + '.torrent'; SaveDialog1.DefaultExt := 'Torrent files (*.torrent)'; FormStyle := fsNormal; if SaveDialog1.Execute then begin FormStyle := fsStayOnTop; TorrentFileName := SaveDialog1.Filename; //     - ButtonSave := False; btnCreate.Caption := ''; EnterCriticalSection(TorrentSection); try for X := 0 to Plugins.Count - 1 do begin if (Supports(Plugins[X], IBTCreateTorrent, BTCreateTorrent)) then //   IBTCreateTorrent,    - begin FindBTPlugin := true; break; end; end; finally LeaveCriticalSection(TorrentSection); end; if FindBTPlugin then begin Id := IntToStr(CreateTorrentID) EnterCriticalSection(TorrentSection); try try BTCreateTorrent.SingleFileTorrent((Id), (ComboBox1.Text), (SaveDialog1.Filename), (mmoComment.Lines.Text), (GetAnnounceURL), (mmWebSeeds.Lines.Text), CheckBox2.checked, ComboBox2.ItemIndex, False, False, '', '', '', ''); //   -   BTService    except end; finally LeaveCriticalSection(TorrentSection); end; repeat application.ProcessMessages; EnterCriticalSection(TorrentSection); try try GetInGeted(BTCreateTorrent.GetInfoTorrentCreating(Id)); //       except end; finally LeaveCriticalSection(TorrentSection); end; if (Stop) and (not(GetedStatus = 'stoped')) then begin EnterCriticalSection(TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread(Id); //   - except end; finally LeaveCriticalSection(TorrentSection); end; end; WaitingCreation; if (Stop) and (not(GetedStatus = 'stoped')) then begin EnterCriticalSection(TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread(Id); //   - except end; finally LeaveCriticalSection(TorrentSection); end; end; sleep(10); until (GetedStatus = 'completed') or (GetedStatus = 'stoped'); end; try ReleaseCreateTorrentThread(BTCreateTorrent, Id); //    - except end; if (GetedStatus = 'completed') then begin sGauge2.Position := sGauge2.Max; sGauge1.Position := sGauge1.Max; StatusBar1.Panels[0].Text := ' -  !'; if CheckBox1.checked then StartTorrent; end; if (GetedStatus = 'stoped') then begin StatusBar1.Panels[0].Text := '.'; end; Stop := False; btnCreate.Enabled := true; ButtonSave := true; btnCreate.Caption := ''; end; end else if DirectoryExists(ComboBox1.Text) then begin SaveDialog1.Filter := 'Torrent Files (*.torrent)|*.torrent'; SaveDialog1.Filename := ComboBox1.Text + '.torrent'; SaveDialog1.DefaultExt := 'Torrent files (*.torrent)'; FormStyle := fsNormal; if SaveDialog1.Execute then begin FormStyle := fsStayOnTop; TorrentFileName := SaveDialog1.Filename; //     - ButtonSave := False; btnCreate.Caption := ''; EnterCriticalSection(TorrentSection); try for X := 0 to Plugins.Count - 1 do begin if (Supports(Plugins[X], IBTCreateTorrent, BTCreateTorrent)) then //   IBTCreateTorrent,    - begin FindBTPlugin := true; break; end; end; finally LeaveCriticalSection(TorrentSection); end; if FindBTPlugin then begin Id := IntToStr(CreateTorrentID) EnterCriticalSection(TorrentSection); try try BTCreateTorrent.CreateFolderTorrent((Id), (ComboBox1.Text), (SaveDialog1.Filename), (mmoComment.Lines.Text), (GetAnnounceURL), (mmWebSeeds.Lines.Text), CheckBox2.checked, ComboBox2.ItemIndex, False, False, '', '', '', ''); //   -   BTService     except end; finally LeaveCriticalSection(TorrentSection); end; repeat application.ProcessMessages; EnterCriticalSection(TorrentSection); try GetInGeted(BTCreateTorrent.GetInfoTorrentCreating(Id)); //       finally LeaveCriticalSection(TorrentSection); end; if (Stop) and (not(GetedStatus = 'stoped')) then begin EnterCriticalSection(TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread(Id); //   - except end; finally LeaveCriticalSection(TorrentSection); end; end; WaitingCreation; if (Stop) and (not(GetedStatus = 'stoped')) then begin EnterCriticalSection(TorrentSection); try try BTCreateTorrent.StopCreateTorrentThread(Id); //   - except end; finally LeaveCriticalSection(TorrentSection); end; end; sleep(10); until (GetedStatus = 'completed') or (GetedStatus = 'stoped'); EnterCriticalSection(TorrentSection); try try ReleaseCreateTorrentThread(BTCreateTorrent, Id); //    - except end; finally LeaveCriticalSection(TorrentSection); end; if (GetedStatus = 'completed') then begin sGauge2.Position := sGauge2.Max; sGauge1.Position := sGauge1.Max; StatusBar1.Panels[0].Text := ' -  !'; if CheckBox1.checked then StartTorrent; end; if (GetedStatus = 'stoped') then begin StatusBar1.Panels[0].Text := '.'; end; Stop := False; btnCreate.Enabled := true; ButtonSave := true; btnCreate.Caption := ''; end; end; end else begin Stop := False; btnCreate.Enabled := true; ButtonSave := true; btnCreate.Caption := ''; end; end else begin Stop := true; btnCreate.Enabled := False; ButtonSave := true; StatusBar1.Panels[0].Text := ' ...'; btnCreate.Caption := '...'; end; end;
      
      







コードからわかるように、最初のステップはトレントを保存するディレクトリを選択することです。 そして、BTServiceプラグインからSingleFileTorrentプロシージャを呼び出すIBTCreateTorrentインターフェイスの検索があります。 このプロシージャは、1つのファイルのコンテンツを使用してトレントファイルを作成するプロセスを開始し、ファイルが含まれるフォルダに対してCreateFolderTorrentプロシージャが起動されます。 その後、繰り返しループが開始され、GetInfoTorrentCreating関数が定期的に呼び出され、トレントの作成中にプラグインからのアクションの結果と、実行されたハッシュの割合に関する情報が返されます。 結果がGetedStatus = 'completed'で返される場合、トレントの作成は正常に完了しており、ループを終了できます。



リストに急流を追加するには、「急流の追加」フォームを作成します。 その上に「ダウンロード」と「リストに追加」の2つのボタンを配置します。 1つ目はリストにトレントを追加してすぐにダウンロードプロセスを開始し、2つ目はリストにトレントを追加して、それに対する追加のアクションを待機します。 トレントに関する情報を表示するには、TEdit( "Torrent File:")、TComboBox( "Save to:")、TLabel( "Torrent Name:"、 "Description:"、 "Date:")およびTListViewのリストにフォームを追加します。トレントのファイルとフォルダの内容を表示します。







トレントアップロードおよびダウンロードコード
 procedure TfAddTorrent.btnDownloadClick(Sender: TObject); begin if AddTask(true, false) then close; end; function TfAddTorrent.AddTask(Now: Boolean; ShowPrev: Boolean): Boolean; var find: Boolean; TorrentDataSL: TStringList; X: Integer; DataTask: TTask; BTPluginAddTrackers: IBTServicePluginAddTrackers; begin Result := false; if Trim(HashValue) = '' then begin MessageBox(Handle, PChar('        -'), PChar(Options.Name), MB_OK or MB_ICONWARNING or MB_TOPMOST); Exit; end; find := false; with TasksList.LockList do try for X := 0 to Count - 1 do begin DataTask := Items[X]; if DataTask.Status <> tsDeleted then if DataTask.HashValue = HashValue then begin find := true; break; end; end; finally TasksList.UnLockList; end; if find then begin if MessageBox(Application.Handle, PChar('   ,     .      ?'), PChar(Options.Name), MB_OKCANCEL or MB_ICONWARNING) = ID_OK then begin for X := 0 to Plugins.Count - 1 do begin if (Supports(Plugins[X], IBTServicePluginAddTrackers, BTPluginAddTrackers)) then begin try BTPluginAddTrackers.AddTrackers(HashValue, trackers); //    - except end; break; end; end; end; Exit; end; TorrentDataSL := TStringList.Create; try TorrentDataSL.Insert(0, BoolToStr(true)); if Now then TorrentDataSL.Insert(1, BoolToStr(true)) else TorrentDataSL.Insert(1, BoolToStr(false)); TorrentDataSL.Insert(2, Edit1.Text); TorrentDataSL.Insert(3, ExcludeTrailingBackSlash(cbDirectory.Text)); TorrentDataSL.Insert(4, IntToStr(0)); TorrentDataSL.Insert(5, Edit2.Text); AddTorrent(TorrentDataSL.Text, HashValue, Now, ShowPrev); finally TorrentDataSL.Free; end; try ForceDirectories(ExcludeTrailingBackSlash(cbDirectory.Text)); except end; SaveTasksList; Result := true; end; function TfAddTorrent.AddTorrent(TorrData: string; HashValue: string; Now: Boolean; ShowPrev: Boolean): Boolean; var AddDataTask: TTask; AddedData: TStringList; CreaName, CreatedName: string; Plugin2: IAddDownload; IndexPlugin2: Integer; Silent: Boolean; Down: Boolean; begin Result := false; AddedData := TStringList.Create; try AddedData.Text := TorrData; try Silent := StrToBool(AddedData[0]); Down := StrToBool(AddedData[1]); except Down := true; Silent := true; end; if Silent then begin AddDataTask := TTask.Create; AddDataTask.TorrentFileName := AddedData[2]; //    - AddDataTask.HashValue := HashValue; // info hash AddDataTask.LinkToFile := 'magnet:?xt=urn:btih:' + AnsiLowerCase(AddDataTask.HashValue); // magnet- AddDataTask.Directory := ExcludeTrailingBackSlash(AddedData[3]); //       AddDataTask.ID := Options.LastID + 1; //     Options.LastID := AddDataTask.ID; CreaName := AddedData[5]; CreaName := trimleft(CreaName); CreaName := trimright(CreaName); CreatedName := CreaName; AddDataTask.FileName := CreatedName; //       AddDataTask.Description := ''; if CheckBox1.Checked then AddDataTask.ProgressiveDownload := true //     else AddDataTask.ProgressiveDownload := false; //     if Down then AddDataTask.Status := tsQueue //     else AddDataTask.Status := tsReady; //     AddDataTask.TotalSize := SizeTorrent; //      AddDataTask.LoadSize := 0; AddDataTask.TimeBegin := 0; AddDataTask.TimeEnd := 0; AddDataTask.TimeTotal := 0; AddDataTask.MPBar := TAMultiProgressBar.Create(nil); //   Plugin2 := nil; DeterminePlugin2('bittorrent', IServicePlugin, Plugin2, IndexPlugin2); if Plugin2 <> nil then if Plugins[IndexPlugin2] <> nil then if (Plugins[IndexPlugin2].TaskIndexIcon > 0) then AddDataTask.TaskServPlugIndexIcon := Plugins[IndexPlugin2] .TaskIndexIcon else begin if pos('magnet:?', AnsiLowerCase(AddDataTask.LinkToFile)) = 1 then AddDataTask.TaskServPlugIndexIcon := 34; end; TasksList.Add(AddDataTask); Result := true; PostMessage(Options.MainFormHandle, WM_MYMSG, 0, 12345); if Now then LoadTorrentThreads.Add(TLoadTorrent.Create(false, AddDataTask, true)); //      end; finally AddedData.Free; end; end;
      
      







トレントを追加する手順では、トレントのリストに情報ハッシュが存在するかどうかを確認します。 情報ハッシュが見つかった場合、リストにトレントを追加する代わりに、トレントBTPluginAddTrackers.AddTrackers(HashValue、トラッカー)からトラッカーのアドレスを追加することをお勧めします。そうしない場合、トレントをリストに追加し続けます。 トレントをTasksList.Add(AddDataTask)リストに追加すると、TLoadTorrentストリーム(uTorrentThreadsモジュール)が作成されます。これにより、BTPlugin.StartTorrent(DataTorrent)トレントが起動され、繰り返しループも実行されます。 = BTPlugin.GetInfoTorrent(DataTask.HashValue)。



TListView OnDataイベントは、受信した情報を表示します。



受信した情報の表示コード
 procedure TfMainForm.lvTasksData(Sender: TObject; Item: TListItem); var i: Integer; Task: TTask; Procent, Procent2: string; EndPoint: Integer; begin with TasksList.LockList do try for i := 0 to Count - 1 do begin if i = Item.Index then begin Task := Items[Item.Index]; //   case Task.Status of tsReady: Item.ImageIndex := 0; //   tsQueue: Item.ImageIndex := 1; //   tsError: Item.ImageIndex := 2; //   tsErroring: Item.ImageIndex := 2; //    tsLoading: Item.ImageIndex := 3; //  tsStoping: Item.ImageIndex := 4; //  tsStoped: Item.ImageIndex := 5; //  tsLoad: Item.ImageIndex := 6; //  tsProcessing: Item.ImageIndex := 8; //  tsSeeding: Item.ImageIndex := 9; //  tsBittorrentMagnetDiscovery: Item.ImageIndex := 10; // Magnet- tsDelete: Item.ImageIndex := 11; //  tsDeleted: Item.ImageIndex := 11; //  end; Item.SubItems.Add(Task.FileName); //   Item.SubItems.Add(Task.LinkToFile); //  Item.SubItemImages[1] := 12; //  case Task.Status of tsReady: Item.SubItems.Add(''); tsQueue: Item.SubItems.Add(' '); tsError: Item.SubItems.Add(''); tsErroring: Item.SubItems.Add(''); tsLoading: begin if Task.TotalSize > 0 then begin Procent := FloatToStr((Task.LoadSize / Task.TotalSize) * 100); begin EndPoint := pos(',', Procent); if EndPoint <> 0 then begin Procent2 := copy(Procent, 1, EndPoint + 1); Item.SubItems.Add(Procent2 + '% ' + ''); end else begin try Procent2 := FloatToStrF(StrToInt(Procent), ffFixed, 6, 1); except end; Item.SubItems.Add(Procent2 + '% ' + ''); end; end; end else Item.SubItems.Add(''); end; tsStoping: Item.SubItems.Add(''); tsStoped: if (Task.TotalSize > 0) then Item.SubItems.Add(FloatToStrF((Task.LoadSize / Task.TotalSize) * 100, ffFixed, 6, 1) + '% ' + '') else Item.SubItems.Add('0% ' + ''); tsLoad: Item.SubItems.Add(''); tsProcessing: Item.SubItems.Add(''); tsSeeding: Item.SubItems.Add(''); tsBittorrentMagnetDiscovery: Item.SubItems.Add('Magnet-'); tsDelete: Item.SubItems.Add(''); tsDeleted: Item.SubItems.Add(''); tsFileError: Item.SubItems.Add(' '); tsAllocating: Item.SubItems.Add(''); tsFinishedAllocating: Item.SubItems.Add(' '); tsRebuilding: Item.SubItems.Add(''); tsJustCompleted: Item.SubItems.Add(''); tsCancelled: Item.SubItems.Add(''); tsUploading: Item.SubItems.Add(''); tsStartProcess: Item.SubItems.Add(''); end; if Task.Speed > 0 then begin Item.SubItems.Add(GetTimeStr((Task.TotalSize - Task.LoadSize) div Task.Speed)); //  end else Item.SubItems.Add(''); if Task.TotalSize > 0 then Item.SubItems.Add(BytesToText(Task.TotalSize)) //  else Item.SubItems.Add(''); Item.SubItems.Add(BytesToText(Task.LoadSize)); //  if Task.Speed > 0 then Item.SubItems.Add(BytesToText(Task.Speed) + '/s') //  else Item.SubItems.Add(''); if Task.NumConnectedSeeders > 0 then Item.SubItems.Add(IntToStr(Task.NumConnectedSeeders)) //  else Item.SubItems.Add(''); if Task.NumConnectedLeechers > 0 then Item.SubItems.Add(IntToStr(Task.NumConnectedLeechers)) //  else Item.SubItems.Add(''); if Task.UploadSpeed > 0 then Item.SubItems.Add(BytesToText(Task.UploadSpeed) + '/s') //   else Item.SubItems.Add(''); if Task.UploadSize > 0 then Item.SubItems.Add(BytesToText(Task.UploadSize)) //  else Item.SubItems.Add(''); break; end; end; finally TasksList.UnLockList; end; end;
      
      







それで、テストの時が来ました。 クライアントコードは完成していますが、トレントのリストにすでに表示されているマグネットリンクを表示する代わりに、クライアントのステータスバーに配置したプログレスバーで補足することにしました。 これが必要なのは、ダウンロードがどのように順番に発生するかを確認するためです。

コンパイル後、クライアントを起動し、「Add torrent」ボタンをクリックしてリストにトレントを追加します。 「順次ダウンロード」マークを設定せずに1つのトレントを追加し、もう1つでこのマークを設定してダウンロードが開始されるのを待ちます。 その結果、ダウンロード中に次の画像が表示されます。











つまり、「Sequential download」というラベルを設定したリストでトレントが選択されると、ダウンロードが順次行われ、別のトレントの場合、トレントの一部のダウンロードが順番なしに選択的に行われます。



その結果、Delphiで完全に実行された有効なトレントクライアントと、現代のクライアントの一貫した機能が得られました。 BTService BittorrentライブラリソースとDelphiTorrentクライアントソース(サンプルディレクトリ)は、SVNで利用可能です: svn.code.sf.net/p/btservice/svn



Windowsでのみ使用できるトレントクライアントを作成しました。 したがって、AndroidおよびIOS用のクライアントの作成について説明する継続を期待する必要があります。これには、すべての前提条件があるためです。



All Articles