Delphi を使用した通信およびデータ交換サーバーの確立 -トランシーバー テクノロジの分析 (パート 2)著者: 火鸟 [email protected] 2. トランシーバー サービスの詳細な説明 1.トランシーバー サービス分析の概要 トランシーバー サービスは、トランシーバー システムのコア コンポーネントであり、トランシーバー コンソールによって設定されたポートとチャネルの定義とパラメーターをシステム構成ライブラリから読み取り、実行時に通信ポートとその関係を動的に作成および制御します。 、データの受信、送信、バッファリングのスケジュール設定、ログとキューの管理など。トランシーバー シェルは、データの送受信がサポートされているすべてのタイプのポートの実装です。 2.トランシーバー サービスの設計の概要 トランシーバー サービスは、Delphi のサービス アプリケーションから開発されます。サービス アプリケーションは、ユーザー状態ではなくシステム状態で実行できます。オペレーティング システムのサービス コントロール マネージャー (SCM) は、プログラムの操作と管理を担当します。このサービスにはユーザー インターフェイスがなく、バックグラウンド プログラムに属します。トランシーバー カーネルは、トランシーバー シェルを確立および制御するトランシーバー クラスの一連のメソッドであり、トランシーバー シェルは通信を担当するオブジェクトのコレクションです。注: パフォーマンスと負荷の考慮により、トランシーバー カーネルはアーキテクチャ図の機能分割を論理的に実装するだけであり、構成モジュールは完全なオブジェクトベースの方法では実装されません。 3.トランシーバー サービス実装の概要 i. サービス アプリケーションを作成します。Delphi メイン メニューの [ファイル...] から [新規] > [サービス アプリケーション] を選択します。生成されたプログラムが表示されます。フレームワークは次のとおりです: PROgram Project1; uses SvcMgr, Unit1 in 'Unit1.pas' {Service1: TService};{$R *.RES}begin Application.CreateForm(TService1, Service1); Application.Run;end.unit Unit1;interfaceuses Windows、Messages、SysUtils、Classes、Graphics、Controls、SvcMgr、Dialogs;type TService1 = class(TService) private { プライベート宣言 } public function GetServiceController: TServiceController { パブリック宣言 } end;var Service1: TService1;実装{$R *.DFM}プロシージャServiceController(CtrlCode: DWord); stdcall;begin Service1.Controller(CtrlCode);end;関数 TService1.GetServiceController: TServiceController;begin Result := ServiceController;end;end では、uses ユニットで参照されるサービス管理に使用される SvcMgr に加えて、TService1 が TForm ではなく TServiced から継承し、オーバーロードされた GetServiceController 関数と、stdcall モードで呼び出される ServiceController プロセスを継承していることがわかります。 Delphi 特別なサービスプログラムはありませんが、Delphi ファンならまた応援したくなるかもしれません。これが Delphi RAD の大きな魅力です。さらに、サービス アプリケーションは実行時に直接デバッグできず、ユーザー インターフェイスがないため、デバッグとトラブルシューティングを容易にするために、開発中にデバッグ情報のインターフェイスレス出力を考慮する必要があります。 ii. 特定のニーズを満たすポート クラスを作成し、統合された実行および処理メカニズムを備えたトランシーバー カーネルを使用するには、トランシーバー シェルのポートには、Delphi の既存のコンポーネント クラスが必要です。現時点では、ニーズを満たすことができるクラスを作成する必要があります。例: type//ユーザー インターフェイスがないため、TControl ではなく TComponent から継承されます TFilePort=class(TComponent) private FilePath:string;//ファイルのフォルダーの場所を取得または保存します Prefix:string;//File prefix suffix:string; //File suffix end; TFilePort クラスを確立した後、トランシーバー カーネルは、FilePath で指定されたフォルダーから特定のファイルにアクセスするという目的を達成するために、統合されたクラス処理メソッドを使用できます。 Source として使用すると、条件を満たすファイルが特定のフォルダーから取得されます。 Target として使用すると、対応する Source から取得したデータが指定されたファイルに書き込まれます (実際、各 Port オブジェクトの実際のパラメーターは、システム構成ライブラリのポート テーブルの定義)。別の例: type TCOMPort=class(TComponent) private ComFace:string; //データを取得または送信するための COM インターフェイス end; TCOMPort は、指定された COM コンポーネント インターフェイスからデータを取得または送信するために使用されます。 Delphi では、OleVariant クラスは COM コンポーネント呼び出しを実装する方法の 1 つであり、TCOMPort クラスを使用する必要があるのは、必要なデータ アクセスが必要な場合にのみ、トランシーバーが TCOMPort によって定義された COM インターフェイスを OleVariant オブジェクトにインスタンス化するためです。使用後に解放されます。これにより、トランシーバーと COM サーバーの負荷が軽減されます。同じ考慮事項が他の同様のコンポーネントにも当てはまります。ここで著者が提供するクラスの例は単なるモデルであり、必要に応じて適切なメソッドやイベントを追加する必要があります。開発中に作成者によって実装されたクラスには、TCOMPort、TMSMQPort、TDBPort、TFilePort などが含まれます。複数のチャネルのサポート - ポートを宣言するオブジェクトの配列。トランシーバーは通信プロセスをソースからターゲットへのデータ フロー プロセスと見なし、このチャネルは少なくとも 2 つで構成されます。ポート (ソース用に 1 つ、ターゲット用に 1 つ) があるため、ソースとターゲットを自由に組み合わせて不特定の数の複数のチャネルを定義したい場合は、それらをソースとターゲットにそれぞれ宣言する必要があります。さまざまなポート クラスのオブジェクトの配列 (後で説明するように、それらに対応する関係を確立します)。例: private { プライベート宣言 }TCPSource:array of TServerSocket;//TCP ソースのオブジェクト配列 TCPTarget:TClientSocket の配列;//TCP ターゲットのオブジェクト配列 MailSource:TIdPOP3 の配列 //メール ソースのオブジェクト配列 MailTarget:array; of TIdSMTP; //メールターゲットのオブジェクト配列 fileSource:TFilePort の配列; // ファイル ソースのオブジェクト配列 fileTarget:array of TFilePort; // ファイル ターゲットのオブジェクト配列 comSource:array of TCOMPort; // COM ソースのオブジェクト配列 comTarget:array of TCOMPort; // COM ターゲットのオブジェクト配列;同じタイプのソースとターゲットのポート操作規則はまったく異なります。トランシーバーの概念では、これらはまったく異なるオブジェクトとみなされ、直接関連するものではありません。したがって、同じ種類のポートの場合、オブジェクト配列もソースとターゲットに応じて個別に作成されます。 iv. 実行時にオブジェクト配列をインスタンス化します。各オブジェクト配列内の要素の数は、実行時にポート ビルダーによって管理されます。ユーザーがトランシーバー コンソールを通じていくつかのタイプのポートを定義すると、ポート ビルダーはその数に応じてそれらをインスタンス化します。それぞれのパラメータ。そうしないと、オブジェクト配列はインスタンス化されません。ソース タイプのポート オブジェクトでは、Name プロパティが「受信」+ポート ID の形式に設定されます。これにより、データ ディスパッチャがオブジェクトを見つけて、さまざまなタイプのポート オブジェクトを均一にスケジュールできるようになります。 Tag 属性は、チャネル コントローラに、それが配置されているチャネルのターゲット ID 情報を提供するために使用されます。以下は、Port Builder の comSource オブジェクト配列のインスタンス化部分です begin // COM を作成/ ポートを受信します itmp:=high(comSource)+1;// comSource の現在の最大数を取得します。itmp は整数変数です SetLength(comSource ,itmp +1); // comSource 配列メンバーを追加します comSource [itmp]:=TCOMPort.Create(self); // メンバー comSource[itmp].Name:= をインスタンス化します。 'Receive'+inttostr(isource); //Name 属性を 'Receive'+ポート ID に設定し、isource は整数型の現在のポート ID です comSource [itmp].Tag:= itarget; // ターゲット ID を設定します。それが配置されているチャネル NullTest :=rece.Fields['Address'].value;//システム構成 COMFace の値を取得します。NullTest は Variant 変数です (NullTest <>null) および(trim(NullTest)<>'') then begincomSource [itmp].ComFace:=NullTest; // ComFaceNullTest:=rece.Fields['interval'].value に有効な値を割り当てます。 // システム設定で COM オブジェクトがデータを取得するためのトリガー時間間隔を取得します SetTimer(application.handle,isource,NullTest*60000 ,nil); //データを定期的に収集するために現在のポートのトリガー クロックを確立します。isource はポート IDendelsecomSource です。 [itmp].Tag:=-1;//初期化に失敗し、無効としてマークされます ポート終了; comSource は ComFace で定義されたインターフェイスを呼び出し、comTarget に対応する一定時間後にデータを取得するために使用されるソース クラスのポートです。 comTarget の ComFace へのデータの送信はリアルタイム プロセスであるため、トリガー間隔を使用する必要がなく、クロックを確立するための 2 つのステートメントを省略できる点が異なります。他のタイプの Port オブジェクトの作成と初期化も同様です。たとえば、別の MailTarget 実装フラグメント: begin //Create SMTP/Send Port itmp:=high(MailTarget)+1) SetLength(MailTarget,itmp+1); itmp].Name:='send'+ inttostr(itarget); MailTarget[itmp].Tag:=3; //ターゲットポートタイプ識別として設定 NullTest:=rece.Fields['Address'].value //メールサーバーアドレス if (NullTest <>null) and (trim(NullTest) <>'') then MailTarget[itmp].Host :=NullTest else bValid:=false; NullTest:=rece.Fields['Port'].value; //メール サーバー ポート if NullTest <>null then(if NullTest<>0 then MailTarget[itmp].Port :=NullTest)else bValid:=false; =rece.Fields['user'].value;//ログインユーザー名 if NullTest <>null thenMailTarget[itmp].UserId :=NullTest else bValid:=false; NullTest:=rece.Fields['password'].value;//ログイン パスワード………………………end;おそらくこれを持っているでしょう。混乱しています。実行時にポート ビルダーによって多数のトランシーバー シェル通信コンポーネントが作成されます。トランシーバー サービスのパフォーマンスは高くなりますか?実際、ポート ビルダーのミッションは、ServiceCreate イベントが発生すると一度完了します。シェル ポートの数は、シェル ポートの通信速度とトランシーバー サービスの全体的なパフォーマンスに影響するだけです。もちろん、システム リソースはもう少し消費される可能性があります。 v. イベントの動的な割り当てと処理 Transceiver Shell でサポートされているいくつかの通信ポートのうち、TServerSocket を使用します (Indy の通信コンポーネントを使用する傾向があるかもしれませんが、これは Transceiver Service の設計思想に違反するものではなく、単なる変更です)シェル レベルで、または単に追加されたもの)、実装された TCPSource はより特徴的なものです。ソース ポートとしての TServerSocket は、定期的にトリガーする必要がある COM や POP3 などのオブジェクトとは異なり、トランシーバー内にあるからです。 Service は、開始後は常に待機状態になります。これは、ClientSocket が接続してデータを送信するときに、対応するイベントを生成するコンポーネントです。以下は TCPSource のインスタンス化フラグメントです: begin //Create TCP/Receive Port itmp:=high(TCPSource)+1;SetLength(TCPSource,itmp+1); [ itmp].OnClientRead:=TCPServersClientRead;//OnClientReadイベントの処理プロセスをTCPServersClientRead TCPSourceに割り当てる[itmp].OnClientError:=TCPServerClientError; //OnClientError イベントの処理プロセスを TCPServerClientErrorTCPSource に割り当てます [itmp].Name:= 'Receive'+inttostr(isource) //Name プロパティを 'Receive'+Port ID に設定しますTCPSource [itmp ].Tag:=itarget; //そのチャネルのターゲット IDTCPSource を設定します [itmp].Socket.Data:=@ TCPSource [itmp].Tag;//この Port オブジェクトのターゲット ID を Socket オブジェクトにポインタ データとして添付します…………………end; 戻って、インスタンス化中にトリガーを確立します。クロックですが、クロックがトリガーされたときにイベントを処理するにはどうすればよいでしょうか?同様に、イベント処理の動的割り当ても同様です。 comSource のクロックの処理定義を ServiceCreate イベント処理に追加できます。 application.OnMessage:=Timer; アプリケーションからのメッセージが生成されると、Timer イベントでタイマーがトリガーされます。クロックによってトリガーされる WM_TIMER メッセージを使用して、ポート ID とタイプに従って特定のソース ポートのデータ取得メソッドを呼び出すことができます。 Procedure TCarrier.Timer(var Msg: TMsg; var Handled: Boolean);var stmp:string; Obj:TComponent;begin if Msg.message =WM_TIMER then//クロック メッセージの処理開始//メッセージをトリガーしたポート ID に従って、このメッセージを定義するオブジェクトを検索します Obj:=FindComponent( 'Receive'+inttostr (Msg.WParam)); if obj=nil then exit;//見つからない場合は処理を終了 stmp:=obj.ClassName;//この Port オブジェクトの型情報を取得するために反映する if stmp='TIdPOP3' then GetPOP3(TIdPOP3(Obj)); if stmp='TIdFTP' then GetFTP(TIdFTP(obj)); if stmp='TFilePort' then GetFile(TFilePort(Obj)); then GetCOM(TCOMPort(Obj));//COMSourceのデータ取得処理を呼び出す…………………… end;end; vi. データの取得 COMSource のデータ取得処理手順です。 ComFace COMInterface:=CreateOleObject (COMObj.ComFace); stmp:=COMInterface.GetData; // stmp<>#0 の間に、合意されたインターフェイス メソッドを呼び出します。 do // #0 は合意されたデータ抽出終了フラグ begin DataArrive(stmp, COMObj.Tag); // 統合処理のためにデータ ディスパッチャーに渡され、COMObj.Tag はオブジェクトが配置されているチャネルのターゲット ポート ID stmp:=COMInterface.GetData end ; COMInterface:= 未割り当て; end;end;//データ抽出操作を完了し、次のトリガー呼び出しまでコンポーネント オブジェクトを解放します。 以下は、TCPSource のデータ取得処理です。 プロシージャ TCarrier.TCPServersClientRead(Sender: TObject; Socket:TCustomWinSocket);beginDataArrive(socket.ReceiveText,integer(TServerWinSocket( sender).data ^));//統一処理のためにデータディスパッチャーに任せ、 2 番目のパラメーターは、Socket オブジェクトの送信側に付加されたターゲット ポート ID ポインター値です。さまざまな種類のソース ポート オブジェクトはさまざまな方法でデータを受信しますが、最終的には、受信したデータがデータ ディスパッチャーに渡されます。実装レベルから見ると、データ受信オブジェクトが追加され、そのデータ受信が実装されるたびに、新しいソース ポートがトランシーバー シェルに実装されます。注: ここでの作成者は、テキスト データの受信のみを実装しています。ユーザーは、メモリ オブジェクト、データ ストリーム、またはバイナリ データを受信し、受信コードにわずかな変更を加えるだけで済みます。 vii. データ スケジューリング トランシーバー サービスのデータ スケジューリングは、データ ディスパッチャー論理ユニットによって完了します。データ ディスパッチャーの主なタスクは、さまざまなソース ポートから受信したデータを均一に管理および制御し、チャネル コントローラーと連携して動作することです。チャネル定義に従って、異なるターゲット ポートへのデータ配信が行われ、送信結果が成功したかどうかを監視し、送信結果と設定に基づいてバッファリングとログ処理のためにデータをキュー マネージャーとログ レコーダーに送信する必要があるかどうかを決定します。システム構成ライブラリの。次に、データを送信するソース ポートの DataArrive メソッドを確認します。 / データが空の場合は、飛び出す iLogID:=-1; dTime:= now // 受信時刻 if sData[length(sdata)] =#0 then; sdata:=copy(sdata,1,length(sdata)-1);//C 言語との互換性のための文字列形式 bSendSeccess:=DataSend(sdata,PortID);//データ ディスパッチャーを呼び出してディスパッチ メソッドを送信します。PortID はターゲット ポート IDif (TSCfg.LogOnlyError=false) または (bSendSeccess=false) theniLogID:=writeLog(dTime, now,sData, PortID, bSendSeccess);//ログ処理ルールに従ってログを記録し、システム構成情報に結果を送信 if (TSCfg.Queueing=True) and (bSendSeccess=false) then PutQueue(dTime, now,sData, PortID, bSendSeccess, iLogID) ; / /上記のパッケージシステム構成情報のQueue構成定義に基づいてQueueの処理終了を決定します。システム構成情報や送信状況に応じてQueue処理を決定するDispatcherのDataArriveメソッドも、強制的なキューイング処理に対応できます。以下は、Data Dispatcher の DataSend メソッドであり、ターゲット ポート タイプに従ってデータを配布および処理するために使用されます。 Function TCarrier.DataSend(sData:String;PortID:Integer):boolean;var Obj:TComponent;begin DataSend:=false ;Obj:=FindComponent ('Send'+inttostr(PortID)) // (obj=nil) または (obj.Tag =-1) の場合、ポート ID に基づいてオブジェクトを検索します。 then exit;//オブジェクトが存在しないか、初期化エラーにより無効としてマークされています。ポート case obj.Tag of 1:DataSend:=PutTCP(TClientSocket(obj),sdata); (obj), sdata); 5:DataSend:=PutFTP(TIdFTP(obj),sdata); 7:DataSend:=PutHTTP(TIdHTTP(obj),sdata); 9:DataSend:=PutFile(TFilePort(obj),sdata); 11:DataSend:=PutMSMQ(TMSMQPort(obj),sdata); PutDB(TDBPort(obj),sdata); 15:DataSend:=PutCOM(TCOMPort (obj),sdata); …………… …………… end;end; オブジェクト配列が使用されていない場合は、それぞれのインスタンスが 1 つしかないことに注意してください。その場合、データ分散を処理するより良い方法はコールバック関数を使用することですが、この場合、オブジェクト配列のどのメンバーがデータを処理すべきかが分からなくなります。また、現在の処理方法ではトランシーバーカーネルとトランシーバーシェルが完全に分離されておらず、より抽象的で独立した処理方法を模索する必要がある。 viii. データ送信 TCPOBJ.PutTCP(TCPOBJ:TClientSocket;sdata:string):Boolean;var itime:integer;begin PutTCP:=false; try TCPOBJ.Close; gettickcount; // (TCPOBJ.Active=true) まで application.ProcessMessages を繰り返します。 (gettickcount-itime>5000); //接続が成功した場合、または TCPOBJ.Active の場合に 5 秒のタイムアウトが発生した場合はループから抜け出し、TCPOBJ.Socket.SendText(sdata) を開始します。戻り値は、データの送信が成功した場合です。 ExceptTCPOBJ.Close end; TCarrier.PutCOM(COMOBJ:TCOMPort;sdata:string):Boolean;var Com:OleVariant;begin PutCOM:=false; try Com:=CreateOleObject(COMOBJ.ComFace);// 定義済みインターフェイスを作成します PutCOM:=Com.PutData ( sdata);//事前定義されたメソッド Com:= Unassigned を呼び出します。他のタイプのポート送信も同様であるため、ここでは繰り返しません。ここまででSourceとTargetの基本的な処理が完了しました。基本的な通信機能が確立されており、さまざまな種類のソースとターゲットを自由に組み合わせることにより、まったく異なる通信機能を実現できます。複数のチャネルを作成することで、複数の異なる機能の通信処理を一元的に実装できます。 ix. キュー処理: 上記の DataArrive メソッドでデータが送信された後、Data Dispatcher はデータ ログ用に writeLog を呼び出し、キュー処理用に PutQueue メソッドを呼び出します。これら 2 つの関数は両方とも、次の手順に従ってデータ情報を処理します。システムパラメータはこの記事の焦点ではありません。キューのリトライ処理は、タイマー イベントのポート タイプごとに処理を分散する原理と似ており、キュー タイマーのトリガーに依存して、データベースからバッファリングされたデータを読み取り、ターゲット ポート ID に従って DataSend を再度呼び出します。データの送信を再試行します。送信が成功すると、このデータ送信のトランザクションは完了します。そうでない場合は、キューに再入力され、送信が成功するか、設定された最大再試行回数になるまで、次のトリガー時間まで待機します。に達します。三つ、開発経験の概要 この記事の焦点は、トランシーバーの中核となるアイデアと設計概念を説明することであるため、トランシーバーがバックグラウンド サービスとして考慮すべきマルチスレッド処理、オブジェクト プーリング、およびトランザクション サポート、およびより複雑な処理を簡素化および弱体化します。強力なソースおよびターゲット グループの管理と Cha。ネル統合、メモリ オブジェクト、データ ストリーム、バイナリ データの送受信機能、システム構成情報の読み取りとそのカプセル化クラスの実装、システムとデータのセキュリティなどについて説明します。読者が何らかの洞察と情報を提供できることを願っています。トランシーバーの設計思想を理解し、実際の開発作業でインスピレーションを刺激し、より優れた強力なソフトウェアを作成します。 Author: Firebird [email protected] Delphi を使用して通信およびデータ交換サーバーを確立する — トランシーバーの技術分析 (パート 1) Delphi を使用して通信およびデータ交換サーバーを確立する — トランシーバーの技術分析 (パート 2) C# によるコレクション クラスの実装 の概要。 NET コレクションと関連する古い技術的なもの: プログラムのショートカット/プログラムの削除/EXE の自己削除 DIY の古いもの: 子供の頃のプログラミング アルゴリズムの体験記