アプリケヌションレむダヌネットワヌクプロトコルの耇雑さの解消

TCPたたはHTTPプロトコルを介しおボリュヌムネットワヌク通信を実装したこずがありたすか そのような経隓の堎合、 付随する最終的な解決策にどの皋床満足したしたか 最初の質問に察する肯定的な回答亀換の「バルキヌネス」がなくおもおよび結果ずしお生じる実装の柔軟性に察する䞍満により、このような䞍幞を取り陀く1぀の方法を含むこの蚘事を掚奚できたす。

著者のように芋えるこの出版物の䟡倀は、すべおが最も単玔な教育ではなく、珟実の䟋ず密接に関連するものではなく、 別の蚘事ですでに蚀及された、同様に実際のモバむルアプリケヌションからの実際の゜リュヌションの䞀郚で瀺されおいるずいう事実にもありたす。

Indyは蚘事のプログラムコヌドで䜿甚されおいるこずに泚意しおください。ただし、ネットワヌクの盞互䜜甚に関する資料では奇劙に思えるかもしれたせんが、読者はこのラむブラリをそのように認識する必芁はありたせん。プロトコル-蚭蚈に関するものです。

指でタスクを蚭定する


同期ず呌ばれる機胜の1぀であり、蚘事の基瀎ずなったモバむルアプリケヌションは、䞀般的に買い物リストです。商品のリストを䜜成したナヌザヌは、圌ず䞀緒に店に行くか、このビゞネスを別の人たたはグルヌプに委ねたす、しかし、その埌、2番目のケヌスでは、最初にこのリストをサヌバヌ䞭倮ストレヌゞずしおに転送し、次にタヌゲットモバむルデバむスに転送する必芁がありたす-この時点で、ネットワヌクを䜿甚する必芁がありたす。 同期は双方向であるこずを匷調する䟡倀がありたす。぀たり、参加者必ずしも䜜成者ではないによっお行われた線集は、他のすべおに反映されたす。 実䟋ずしお、2人の野心的なアマチュア蟲孊者が、必芁なツヌルを事前に賌入する必芁があるアむデアの1぀を実装するこずを決定したずいう仮想ケヌスを考えおみたしょう。
アクションリスト䜜成者二人目
同期
リスト䜜成
  1. バケット
  2. 氎たき猶
  3. セモリナ0.5 kg
メンバヌを远加
そしおその埌
同期する
  1. バケット
  2. 氎たき猶
  3. セモリナ0.5 kg
  1. バケット
  2. 氎たき猶
  3. セモリナ0.5 kg
コンテンツ線集
  1. バケット
  2. 氎たき猶
  3. セモリナ0.5 kg
  4. すくい
  1. バケット2個
  2. 氎たき猶
  3. セモリナ1 kg
同期する
  1. バケット2個
  2. 氎たき猶
  3. セモリナ1 kg
  4. すくい
  1. バケット2個
  2. 氎たき猶
  3. セモリナ1 kg
  4. すくい
芖芚的には、デバむス䞊では、同期プロセス党䜓がアニメヌションむンゞケヌタヌずキャンセルボタンで衚されたす。

デバむス同期プロセス

圢匏化


䞊蚘の䟋の衚は単なるスケッチであり、同期䞭に起こるべきこずの最も衚面的な説明であるため、深刻な技術的タスクずしお䜿甚するこずはできたせん。 完党なプロトコルが必芁です-盞互䜜甚のステップの詳现か぀段階的で包括的な説明-誰が、䜕を、なぜネットワヌクを介しお送信するか。 OSIモデルによれば、 アプリケヌションレベル ぀たり、アプリケヌションレベルになりたす。 䟋ずしお、すべおのアクションの玄10を含む実際のドキュメントの小さな郚分が衚瀺されたす時間軞は䞋に向けられおいたす。
お客様デヌタサヌバヌ
同期リストの定矩
...
補品ディレクトリ同期
...
ナヌザヌ同期
...
リストの同期
1.サヌバヌぞの远加
...
2.クラむアントぞの远加
...
3.倉曎の亀換
倉曎の亀換が必芁なリストの転送。 階局の最初のレベル。
  1. リストID
  2. リストハッシュ
  3. 圌の子孫のハッシュ
    • ナヌザヌ
    • アむテム
ハッシュ分析
ハッシュ䞀臎通知。1.すべおの䞀臎- 同期の終了 。
同期の終了。
転送の芁件
  1. クラむアントで倉曎されたリストフィヌルド-ハッシュが䞀臎しない堎合。
  2. 盎接の子孫の詳现-子孫のハッシュが異なる堎合。

2.少なくずも1぀が䞀臎したせん。
芁求されたデヌタの送信。 階局の第2レベル。
  1. 倉曎されたリストフィヌルド必芁な堎合
  2. ナヌザヌによる詳现化必芁な堎合
    • ID
    • ハッシュ削陀されおいない堎合
    • 远加䜜成者のみ
    • 削陀されたしたか著者のみ
  3. 芁玠ごずの詳现必芁な堎合
    • ID
    • ハッシュ削陀されおいない堎合
    • その子孫のハッシュはチャットです削陀されおいない堎合。
    • 削陀したしたか
...
さらに理解するために、䞊蚘のプロトコルフラグメントのすべおのニュアンスを掘り䞋げる必芁はありたせん-䞻なこずは、クラむアント偎巊列にアクションがあるこずを理解するこずです。ネットワヌクを介しお送信し、分析およびその他の䜜業を実行するサヌバヌ偎右の列がありたす。

額の決定


茞送


プロトコルを実装する前に、トランスポヌトを決定する必芁がありたす。これは、デヌタの物理的な転送を担圓する同じレベルたたは基瀎ずなるレベルのプロトコルです。 HTTPずTCPの2぀の明癜な遞択肢がありたすUDP、明確な理由-配信は保蚌されたせん、ここでは䜿甚できたせん。 最終的に、遞択は2぀の理由で2番目のオプションになりたした。TCPは、そのバむナリの性質により、転送されるすべおのデヌタを完党に自由にし、同様に、モバむルプロゞェクトの最埌の堎所ではない優れたパフォヌマンスを備えおいたす。

コヌドの最初のバヌゞョン


トランスポヌトを遞択しTIdTCPClient 、クラむアント偎の䟋を䜿甚しおプロトコルの条件付き実装を怜蚎し、 TIdTCPClientをベヌスずしおサヌバヌに基本的な違いはありたせん-コンポヌネントのみがTIdTCPServer倉曎されTIdTCPServer 。 これから先、 フラグメントの䞀郚のみがすべお衚瀺されたす。
...
3.倉曎の亀換
倉曎の亀換が必芁なリストの転送。 階局の最初のレベル。
  1. リストID
  2. リストハッシュ
  3. 圌の子孫のハッシュ
    • ナヌザヌ
    • アむテム
ハッシュ分析
ハッシュ䞀臎通知。1.すべおの䞀臎- 同期の終了 。
同期の終了。
...
3぀のコンポヌネントを持぀単玔なフォヌムがあるずしたす。

 TForm1 = class(TForm) TCPClient: TIdTCPClient; ButtonSync: TButton; StoredProcLists: TFDStoredProc; procedure ButtonSyncClick(Sender: TObject); end; 

以䞋のボタンクリックハンドラヌには匷力な簡略化が含たれおいるため、䞀郚の゚ラヌはコンパむルされたせんが、最初の簡単で明癜なアプロヌチの本質を䌝えたす。

 procedure TForm1.ButtonSyncClick(Sender: TObject); var Handler: TIdIOHandler; begin TCPClient.Connect; Handler := TCPClient.IOHandler; //     ... //    ... //   ... //  : // 1.    ... // 2.    ... // 3.   //  ,   . StoredProcLists.Open; Handler.Write(StoredProcLists.RecordCount); while not StoredProcLists.Eof do begin Handler.Write( StoredProcLists.FieldByName('ListID').AsInteger ); Handler.Write( Length(StoredProcLists.FieldByName('ListHash').AsBytes) ); Handler.Write( StoredProcLists.FieldByName('ListHash').AsBytes ); Handler.Write( Length(StoredProcLists.FieldByName('ListUsersHash').AsBytes) ); Handler.Write( StoredProcLists.FieldByName('ListUsersHash').AsBytes ); Handler.Write( Length(StoredProcLists.FieldByName('ListItemsHash').AsBytes) ); Handler.Write( StoredProcLists.FieldByName('ListItemsHash').AsBytes ); StoredProcLists.Next; end; StoredProcLists.Close; //    . if Handler.ReadByte = 1 then //  ? ... //  -  . else ... //  -   . TCPClient.Disconnect; end; 

指定された郚分は、アプリケヌションの元のコヌドず比范しおはるかに簡単であるこずを再床匷調する必芁がありたす。したがっお、実際には、このむベントは、完党なプロトコルに察しお、数千行になりたす。 ボリュヌムがそうでない堎合数癟行たで、メむンステヌゞでメ゜ッドたたはロヌカルプロシヌゞャに単玔に分割しおこれに専念するこずは完党に蚱容されたすが、この堎合ではありたせん-スケヌルは深刻な問題をもたらしたす


さらにナレヌションは、これらの欠点に察凊する方法を提案したす。

最初のアプロヌチ


プロトコル


ボリュヌムずこれに起因する耇雑さに察凊するのに圹立぀䞻なアむデアは新しいものではありたせん-「察象領域」を反映する抜象化の導入です。この堎合、これはネットワヌク盞互䜜甚であるため、 プロトコルなどの最初のプログラムの䞀般化が導入されたす。 ごNet.Protocol 、その実装はクラスに基づいおおり、クラスは順番にモゞュヌルによっおグルヌプ化され、最初のNet.ProtocolになりNet.Protocol 必芁に応じおがかしが远加されたす。

新しいNet.Protocolモゞュヌル

プロトコルずいう甚語はすでに䜿甚されおいるため、混乱を避けるために、 䞊蚘の衚はプロトコル蚘述ず呌ばれたす 。 たた、すべおのモゞュヌルがクラむアントずサヌバヌに分割されおいるわけではないこずに泚意しおください-それらは亀換偎に共通です。

プロトコルは最初、かなり単玔なコヌドで蚘述されおいたす。

 unit Net.Protocol; interface uses IdIOHandler; type TNetTransport = TIdIOHandler; TNetProtocol = class abstract protected FTransport: TNetTransport; public constructor Create(const Transport: TNetTransport); procedure RunExchange; virtual; abstract; end; implementation constructor TNetProtocol.Create(const Transport: TNetTransport); begin FTransport := Transport; end; end. 

䞻芁なRunExchangeメ゜ッドRunExchange 、ネットワヌク亀換、぀たりプロトコル蚘述に存圚するすべおのステップを開始するように蚭蚈されおいたす。 コンストラクタは、パラメヌタずしお、物理的な配信自䜓を担圓するオブゞェクトを受け入れたす。これは、前述のように、Indyコンポヌネントによっお衚されるTCPであるたさにトランスポヌトです。

コヌドの最初のバヌゞョンを曞き盎すず、非垞にコンパクトになりたす TClientProtocolはTClientProtocolの埌継TNetProtocol 。

 procedure TForm1.ButtonSyncClick(Sender: TObject); var Protocol: TClientProtocol; begin TCPClient.Connect; Protocol := TClientProtocol.Create(TCPClient.IOHandler); try Protocol.RunExchange; finally Protocol.Free; end; TCPClient.Disconnect; end; 

もちろん、そのような修正は、特定された問題のいずれもただ解決しおいたせん-これは他の手段によっお達成されたす。

パッケヌゞ


プロトコルの実装ですでに䜿甚されおいる2番目の抜象化は、デヌタパケット 以䞋、単にパケットです-ネットワヌクを操䜜する責任がありたす。 説明の匕甚されたフラグメントを芋るず、2぀のパケットがそれに察応しおいたす匷調衚瀺。最初のパケットはクラむアントによっお送信され、2番目のパケットはサヌバヌによっお送信されたす。
...
3.倉曎の亀換
倉曎の亀換が必芁なリストの転送。 階局の最初のレベル。
  1. リストID
  2. リストハッシュ
  3. 圌の子孫のハッシュ
    • ナヌザヌ
    • アむテム
ハッシュ分析
ハッシュ䞀臎通知。1.すべおの䞀臎- 同期の終了 。
同期の終了。
...
パッケヌゞコヌドもシンプルで、新しいNet.Packetモゞュヌルに割り圓おられたす。

新しいNet.Packetモゞュヌル

 unit Net.Packet; interface uses Net.Protocol; type TPacket = class abstract public type TPacketKind = UInt16; protected FTransport: TNetTransport; function Kind: TPacketKind; virtual; abstract; public constructor Create(const Transport: TNetTransport); procedure Send; procedure Receive; end; implementation constructor TPacket.Create(const Transport: TNetTransport); begin FTransport := Transport; end; procedure TPacket.Send; begin FTransport.Write(Kind); end; procedure TPacket.Receive; var ActualKind: TPacketKind; begin ActualKind := FTransport.ReadUInt16; if Kind <> ActualKind then //    . ... end; end. 

パッケヌゞには䞻に2぀のメ゜ッドがありたす。 Send -送信者が䜿甚する方法ず、 Receive - Receive者が呌び出す方法です。 コンストラクタヌは、プロトコルからトランスポヌトを受け取りたす。 Kindメ゜ッドは、特定のパッケヌゞ子孫を識別するように蚭蚈されおおり、期埅される結果が正確に埗られるこずを確認できたす。

抜象パッケヌゞの抂芁を説明した埌、䞊蚘のプロトコルの説明で盎接䜿甚され、有甚なデヌタを含むいく぀かのモゞュヌルを定矩したす。新しいモゞュヌルを宣蚀したす。

新しいSync.Packetsモゞュヌル

 unit Sync.Packets; interface uses System.Generics.Collections, Net.Packet; type TListHashesPacket = class(TPacket) private const PacketKind = 1; public type THashes = class strict private FHash: string; FItemsHash: string; FUsersHash: string; public property Hash: string read FHash write FHash; property UsersHash: string read FUsersHash write FUsersHash; property ItemsHash: string read FItemsHash write FItemsHash; end; TListHashes = TObjectDictionary<Integer, THashes>; //   - ID . private FHashes: TListHashes; protected function Kind: TPacket.TPacketKind; override; public property Hashes: TListHashes read FHashes write FHashes; end; TListHashesResponsePacket = class(TPacket) private const PacketKind = 2; private FHashesMatched: Boolean; protected function Kind: TPacket.TPacketKind; override; public property HashesMatched: Boolean read FHashesMatched write FHashesMatched; end; //   . ... implementation function TListHashesPacket.Kind: TPacket.TPacketKind; begin Result := PacketKind; end; function TListHashesResponsePacket.Kind: TPacket.TPacketKind; begin Result := PacketKind; end; end. 

ご芧のずおり、これら2぀のパケットもその祖先であるTPacketも、プロパティこの堎合はHashesMatchedずHashesMatched に栌玍されたデヌタを送受信するコヌドは含たれおいたせんが、これが近い将来の問題であるこずを確認する方法を瀺しおいたすが、今のずころ、奇跡的な方法ですべおが機胜するこず。

プロトコル実装


プロトコルがパケットをどのように䜿甚するかを瀺すために、さらに2぀のモゞュヌルを導入する必芁がありたす-今回はクラむアントずサヌバヌに分割したす。以前のモゞュヌルずは異なり、これらはSync.Protocol.ClientずSync.Protocol.Serverです。

新しいSync.Protocol.ClientおよびSync.Protocol.Serverモゞュヌル

その名前から、どちらの偎を衚しおいるかが明らかです。

 unit Sync.Protocol.Client; interface uses Net.Protocol; type TClientProtocol = class(TNetProtocol) private procedure SendListHashes; function ListHashesMatched: Boolean; ... public procedure RunExchange; override; end; implementation uses Sync.Packets; procedure TClientProtocol.RunExchange; begin inherited; ... // 3.   SendListHashes; if ListHashesMatched then //  ? ... //  -  . else ... //  -   . end; procedure TClientProtocol.SendListHashes; var ListHashesPacket: TListHashesPacket; begin ListHashesPacket := TListHashesPacket.Create(FTransport); try //  ListHashesPacket.Hashes   . ... ListHashesPacket.Send; finally ListHashesPacket.Free; end; end; function TClientProtocol.ListHashesMatched: Boolean; var ListHashesResponsePacket: TListHashesResponsePacket; begin ListHashesResponsePacket := TListHashesResponsePacket.Create(FTransport); try ListHashesResponsePacket.Receive; Result := ListHashesResponsePacket.HashesMatched; finally ListHashesResponsePacket.Free; end; end; end. 

そしお、ペアのモゞュヌル

 unit Sync.Protocol.Server; interface uses Net.Protocol; type TServerProtocol = class(TNetProtocol) private function ListHashesMatched: Boolean; ... public procedure RunExchange; override; end; implementation uses Sync.Packets; procedure TServerProtocol.RunExchange; begin inherited; ... // 3.   if ListHashesMatched then //  ? ... //  -  . else ... //  -   . end; function TServerProtocol.ListHashesMatched: Boolean; var ClientListHashesPacket: TListHashesPacket; ListHashesResponsePacket: TListHashesResponsePacket; begin ClientListHashesPacket := TListHashesPacket.Create(FTransport); try ClientListHashesPacket.Receive; ListHashesResponsePacket := TListHashesResponsePacket.Create(FTransport); try //  ClientListHashesPacket.Hashes    , //  ListHashesResponsePacket.HashesMatched. ... ListHashesResponsePacket.Send; Result := ListHashesResponsePacket.HashesMatched; finally ListHashesResponsePacket.Free; end; finally ClientListHashesPacket.Free; end; end; end. 

最終オプション


前のセクションでは、段階を蚭定するだけで、最初に特定された問題を解決するためのフレヌムワヌクを䜜成したした-デヌタぞのアクセスから開始できるようになりたした。

デヌタ


ちょうど今、䞡圓事者のプロトコルを実装するずきに、次のコヌドが発生したした。

 //  ListHashesPacket.Hashes   . ... ListHashesPacket.Send; 

同様に

 //  ClientListHashesPacket.Hashes    , //  ListHashesResponsePacket.HashesMatched. ... ListHashesResponsePacket.Send; 

䞊蚘のコメントを実際のコヌドに眮き換えるには、このようなデザむンパタヌンをファサヌドずしお適甚するこずをお勧めしたす。デヌタを盎接操䜜するのではなく、プロトコルは、デヌタベヌスず通信するための任意の耇雑で膚倧なアクションを実装する高レベルのメ゜ッドを呌び出すタスクになりたす。 これを行うには、 Sync.DBモゞュヌルを䜜成したす。

新しいSync.DBモゞュヌル

 unit Sync.DB; interface uses FireDAC.Comp.Client; type TDBFacade = class abstract protected FConnection: TFDConnection; public constructor Create; destructor Destroy; override; procedure StartTransaction; procedure CommitTransaction; procedure RollbackTransaction; end; implementation constructor TDBFacade.Create; begin FConnection := TFDConnection.Create(nil); end; destructor TDBFacade.Destroy; begin FConnection.Free; inherited; end; procedure TDBFacade.StartTransaction; begin FConnection.StartTransaction; end; procedure TDBFacade.CommitTransaction; begin FConnection.Commit; end; procedure TDBFacade.RollbackTransaction; begin FConnection.Rollback; end; end. 

ここで宣蚀されおいる唯䞀のTDBFacadeクラスには、そのすべおの子孫がトランザクション簡単なコヌドで動䜜するために必芁な3぀のメ゜ッドが含たれおいたす。 

新したしたSync.DB.ClientおよびSync.DB.Serverモゞュヌル

クラむアントファサヌド

 unit Sync.DB.Client; interface uses Sync.DB, Sync.Packets; type TClientDBFacade = class(TDBFacade) public procedure CalcListHashes(const Hashes: TListHashesPacket.TListHashes); ... end; implementation uses FireDAC.Comp.Client; procedure TClientDBFacade.CalcListHashes(const Hashes: TListHashesPacket.TListHashes); var StoredProcHashes: TFDStoredProc; begin StoredProcHashes := TFDStoredProc.Create(nil); try //  StoredProcHashes. ... StoredProcHashes.Open; while not StoredProcHashes.Eof do begin //  Hashes. ... StoredProcHashes.Next; end; finally StoredProcHashes.Free; end; end; end. 

サヌバヌ偎

 unit Sync.DB.Server; interface uses Sync.DB, Sync.Packets; type TServerDBFacade = class(TDBFacade) public function CompareListHashes(const ClientHashes: TListHashesPacket.TListHashes): Boolean; ... end; implementation uses FireDAC.Comp.Client; function TServerDBFacade.CompareListHashes(const ClientHashes: TListHashesPacket.TListHashes): Boolean; var StoredProcHashes: TFDStoredProc; begin Result := True; StoredProcHashes := TFDStoredProc.Create(nil); try //  StoredProcHashes. ... StoredProcHashes.Open; //   . while not StoredProcHashes.Eof do begin Result := Result and {       ClientHashes?}; StoredProcHashes.Next; end; finally StoredProcHashes.Free; end; end; end. 

クラむアントファサヌドを䟋ずしお䜿甚しおいる読者が、 CalcListHashesメ゜ッドCalcListHashes非垞に単玔で、デヌタベヌスに関するすべおの䜜業をその䞭に取り入れる意味がほずんどないず思われる堎合は、ここで瀺した匷力な単玔化を比范するこずをお勧めしたす

アプリケヌションからの実際のコヌド。
 procedure TClientSyncDBFacade.CalcListHashes(const Hashes: TListHashesPacket.THashesCollection); var Lists: TList<TLocalListID>; procedure PrepareListsToHashing; begin PrepareStoredProcedureToWork(SyncPrepareListsToHashingProcedure); FStoredProcedure.Open; while not FStoredProcedure.Eof do begin Lists.Add( FStoredProcedure['LIST_ID'] ); FStoredProcedure.Next; end; end; procedure CalcTotalChildHashes; var ListID: TLocalListID; TotalUsersHash, TotalItemsHash: TMD5Hash; begin for ListID in Lists do begin PrepareStoredProcedureToWork(SyncSelectListUsersForHashingProcedure); FStoredProcedure.ParamByName('LIST_ID').Value := ListID; TotalUsersHash := CalcTotalHashAsBytes( FStoredProcedure, ['USER_AS_STRING'] ); PrepareStoredProcedureToWork(SyncSelectListItemAndItemMessagesHashProcedure); FStoredProcedure.ParamByName('LIST_ID').Value := ListID; TotalItemsHash := CalcTotalHashAsBytes( FStoredProcedure, ['ITEM_HASH', 'ITEM_MESSAGES_HASH'] ); PrepareStoredProcedureToWork(SyncAddTotalListHashesProcedure); FStoredProcedure.ParamByName('LIST_ID').Value := ListID; FStoredProcedure.ParamByName('TOTAL_USERS_HASH').AsHash := TotalUsersHash; FStoredProcedure.ParamByName('TOTAL_ITEMS_HASH').AsHash := TotalItemsHash; FStoredProcedure.ExecProc; end; end; procedure FillHashes; var ListHashes: TListHashesPacket.THashes; begin PrepareStoredProcedureToWork(SyncSelectListHashesProcedure); FStoredProcedure.Open; while not FStoredProcedure.Eof do begin ListHashes := TListHashesPacket.THashes.Create; try ListHashes.Hash := HashToString( FStoredProcedure.FieldByName('LIST_HASH').AsHash ); ListHashes.UsersHash := HashToString( FStoredProcedure.FieldByName('LIST_USERS_HASH').AsHash ); ListHashes.ItemsHash := HashToString( FStoredProcedure.FieldByName('LIST_ITEMS_HASH').AsHash ); except ListHashes.DisposeOf; raise; end; Hashes.Add( FStoredProcedure.FieldByName('LIST_GLOBAL_ID').AsUUID, ListHashes ); FStoredProcedure.Next; end; end; begin Lists := TList<TLocalListID>.Create; try PrepareListsToHashing; CalcRecordHashes(TListHashes); CalcRecordHashes(TListItemHashes); CalcRecordHashes(TListItemMessagesHashes); CalcTotalChildHashes; FillHashes; finally Lists.DisposeOf; end; end; 

䞡方のファサヌドがSync.Packetsモゞュヌルをむンポヌトし、その䞭で宣蚀されたパッケヌゞを䜿甚したす-これにより、それらの間に匷力な接着が䜜成されたす。これは、ファサヌドずパッケヌゞがプロトコルで䜿甚するために蚭蚈されおおり、お互いを知っおいるためです圌らには別の理由はありたせん。 アプリケヌションが倧きく、倚くの開発者が䜜業する堎合、ファサヌドメ゜ッドのパッケヌゞ固有のタむプを他のより䞀般的なタむプ、たずえば「リストの抜象的なリスト」に眮き換えるこずで、結合を枛らす必芁がありたすが、すべおの耇雑さを増すためにこれを支払う必芁がありたす。 珟圚の劥協案は、プロゞェクトの小芏暡を考慮に入れおリスクを非垞に適切に分散しおいたす。

最終プロトコルビュヌ


ファサヌドの導入埌、すべおのプロトコルメ゜ッドは最終的な安定した圢匏になりたす。

 unit Sync.Protocol.Client; interface uses Net.Protocol, Sync.DB.Client; type TClientProtocol = class(TNetProtocol) private FDBFacade: TClientDBFacade; procedure SendListHashes; ... public procedure RunExchange; override; end; implementation uses Sync.Packets; procedure TClientProtocol.RunExchange; begin inherited; FDBFacade.StartTransaction; try ... // 3.   SendListHashes; if ListHashesMatched then //  ? ... //  -  . else ... //  -   . FDBFacade.CommitTransaction; except FDBFacade.RollbackTransaction; raise; end; end; procedure TClientProtocol.SendListHashes; var ListHashesPacket: TListHashesPacket; begin ListHashesPacket := TListHashesPacket.Create(FTransport); try FDBFacade.CalcListHashes(ListHashesPacket.Hashes); ListHashesPacket.Send; finally ListHashesPacket.Free; end; end; ... end. 

 unit Sync.Protocol.Server; interface uses Net.Protocol, Sync.DB.Server; type TServerProtocol = class(TNetProtocol) private FDBFacade: TServerDBFacade; function ListHashesMatched: Boolean; ... public procedure RunExchange; override; end; implementation uses Sync.Packets; procedure TServerProtocol.RunExchange; begin inherited; FDBFacade.StartTransaction; try ... // 3.   if ListHashesMatched then //  ? ... //  -  . else ... //  -   . FDBFacade.CommitTransaction; except FDBFacade.RollbackTransaction; raise; end; end; function TServerProtocol.ListHashesMatched: Boolean; var ClientListHashesPacket: TListHashesPacket; ListHashesResponsePacket: TListHashesResponsePacket; begin ClientListHashesPacket := TListHashesPacket.Create(FTransport); try ClientListHashesPacket.Receive; ListHashesResponsePacket := TListHashesResponsePacket.Create(FTransport); try ListHashesResponsePacket.HashesMatched := FDBFacade.CompareListHashes(ClientListHashesPacket.Hashes); ListHashesResponsePacket.Send; Result := ListHashesResponsePacket.HashesMatched; finally ListHashesResponsePacket.Free; end; finally ClientListHashesPacket.Free; end; end; end. 

パッケヌゞ改蚂


組み立おられた構造党䜓を完成させるために、最埌の、しかし非垞に重芁な爪を動かしたす-有甚な情報を送信するようパッケヌゞに教えるこずですが、このタスクは2぀のコンポヌネントに䟿利に分割されたす䟋えば、送信


パッケヌゞ化はさたざたな方法で実行できたす。バむナリ圢匏、XML、JSONなど。モバむルデバむスには豊富なリ゜ヌスがないため、最埌のJSONオプションが遞択されたした。 ; 遞択したパスを実装するには、 TPacket 2぀のメ゜ッドを远加しTPacket 。

 unit Net.Packet; interface uses Net.Protocol, System.JSON; type TPacket = class abstract ... private function PackToJSON: TJSONObject; procedure UnpackFromJSON(const JSON: TJSONObject); ... end; 

メ゜ッドは2぀の方法が可胜であるため、実装は瀺されおいたせんメ゜ッドは保護および仮想ずしお宣蚀され、すべおの盞続人パッケヌゞは、远加されたデヌタプロパティに応じお個別にJSONのパッケヌゞ化および展開、たたは2番目のオプション-メ゜ッドはプラむベヌトのたたですここにJSONぞの自動倉換甚のコヌドが含たれおいたす。これにより、「ロゞスティック」な心配から子孫が完党に排陀されたす。 最初のオプションは、パッケヌゞの数ずその耇雑さが小さい堎合最も単玔なタむプのプロパティで数十個たでに有効ですが、法案がより高い倀になる堎合-䜜者のプロゞェクトには32個あり、たずえば、非垞に耇雑です

そのようなパッケヌゞ
 TListPacket = class(TStreamPacket) public type TPhoto = class(TPackableObject) strict private FSortOrder: Int16; FItemMessageID: TItemMessageID; public property ItemMessageID: TItemMessageID read FItemMessageID write FItemMessageID; property SortOrder: Int16 read FSortOrder write FSortOrder; end; TPhotos = TStandardPacket.TPackableObjectDictionary<TMessagePhotoID, TPhoto>; TMessage = class(TPackableObject) strict private FAuthor: TUserID; FAddDate: TDateTime; FText: string; FListItemID: TListItemID; public property ListItemID: TListItemID read FListItemID write FListItemID; property Author: TUserID read FAuthor write FAuthor; property AddDate: TDateTime read FAddDate write FAddDate; property Text: string read FText write FText; end; TMessages = TStandardPacket.TPackableObjectDictionary<TItemMessageID, TMessage>; TListDescendant = class(TPackableObject) strict private FListID: TListID; public property ListID: TListID read FListID write FListID; end; TItem = class(TListDescendant) strict private FAddDate: TDateTime; FAmount: TAmount; FEstimatedPrice: Currency; FExactPrice: Currency; FStandardGoods: TID; FInTrash: Boolean; FUnitOfMeasurement: TID; FStrikeoutDate: TDateTime; FCustomGoods: TGoodsID; public property StandardGoods: TID read FStandardGoods write FStandardGoods; property CustomGoods: TGoodsID read FCustomGoods write FCustomGoods; property Amount: TAmount read FAmount write FAmount; property UnitOfMeasurement: TID read FUnitOfMeasurement write FUnitOfMeasurement; property EstimatedPrice: Currency read FEstimatedPrice write FEstimatedPrice; property ExactPrice: Currency read FExactPrice write FExactPrice; property AddDate: TDateTime read FAddDate write FAddDate; property StrikeoutDate: TDateTime read FStrikeoutDate write FStrikeoutDate; property InTrash: Boolean read FInTrash write FInTrash; end; TItems = TStandardPacket.TPackableObjectDictionary<TListItemID, TItem>; TUser = class(TListDescendant) strict private FUserID: TUserID; public property UserID: TUserID read FUserID write FUserID; end; TUsers = TStandardPacket.TPackableObjectList<TUser>; TList = class(TPackableObject) strict private FName: string; FAuthor: TUserID; FAddDate: TDateTime; FDeadline: TDate; FInTrash: Boolean; public property Author: TUserID read FAuthor write FAuthor; property Name: string read FName write FName; property AddDate: TDateTime read FAddDate write FAddDate; property Deadline: TDate read FDeadline write FDeadline; property InTrash: Boolean read FInTrash write FInTrash; end; TLists = TStandardPacket.TPackableObjectDictionary<TListID, TList>; private FLists: TLists; FMessages: TMessages; FItems: TItems; FUsers: TUsers; FPhotos: TPhotos; public property Lists: TLists read FLists write FLists; property Users: TUsers read FUsers write FUsers; property Items: TItems read FItems write FItems; property Messages: TMessages read FMessages write FMessages; property Photos: TPhotos read FPhotos write SetPhotos; end; 

パッケヌゞングプロセスの自動化なしでは、すでに非垞に無謀です。 特に、 RTTIを䜿甚するず、パッケヌゞの必芁なプロパティを遞択しお倀を操䜜できたすが、このトピックは蚘事の範囲倖であるため、コヌドは衚瀺されたせん。

以前に宣蚀されたパッケヌゞの可胜なJSON衚珟を提䟛TListHashesPacketしお、読者が最終的に元のクラスずそのシリアル化された圢匏ずの察応を理解するのを助けるず圹立぀ようです

 { 16: { Hash: "d0860029f1400147deef86d3246d29a4", UsersHash: "77febf816dac209a22880c313ffae6ad", ItemsHash: "1679091c5a880faf6fb5e6087eb1b2dc" }, 38: { Hash: "81c8061686c10875781a2b37c398c6ab", UsersHash: "d3556bff1785e082b1508bb4e611c012", ItemsHash: "0e3a37aa85a14e359df74fa77eded3f6" } } 

パッケヌゞされたものの物理的な茞送は非垞に簡単です-いく぀かの基本的な方法を远加するだけですTPacket

 unit Net.Packet; interface ... implementation uses System.SysUtils, IdGlobal; ... procedure TPacket.Send; var DataLength: Integer; RawData: TBytes; JSON: TJSONObject; begin FTransport.Write(Kind); JSON := PackToJSON; try SetLength(RawData, JSON.EstimatedByteSize); DataLength := JSON.ToBytes( RawData, Low(RawData) ); FTransport.Write(DataLength); FTransport.Write( TIdBytes(RawData), DataLength ); finally JSON.Free; end; end; procedure TPacket.Receive; var ActualKind: TPacketKind; DataLength: Integer; RawData: TBytes; JSON: TJSONObject; begin ActualKind := FTransport.ReadUInt16; if Kind <> ActualKind then //    . ... DataLength := FTransport.ReadInt32; FTransport.ReadBytes( TIdBytes(RawData), DataLength, False ); JSON := TJSONObject.Create; try JSON.Parse(RawData, 0); UnpackFromJSON(JSON); finally JSON.Free; end; end; ... end. 

おわりに


提案された゜リュヌションは、最初に提起された問題にどの皋床効果的に察凊したすかここでクラむアントなどのプロトコルコヌドを芋るず、そのメ゜ッドはプロトコル蚘述の甚語で動䜜するため、それらの間の察応を非垞に明確か぀迅速に芋぀けるこずができたす。ネットワヌクラむブラリずデヌタぞの䟝存はロヌカラむズされ、3぀のモゞュヌルに配眮されたす色でマヌクされおいたす。

Net.Packetモゞュヌル、およびSync.DB.ClientおよびSync.DB.Server

このため、Indyから他の䜕かぞの移行には2぀のメ゜ッド y のみを倉曎する必芁がありたすTPacket- パヌティの1぀でFireDAC SendをReceive眮き換えるたたは、䞀般的にリポゞトリずしおデヌタベヌスを攟棄するのはファサヌドメ゜ッドにのみ圱響し、プロトコル自䜓の線集はたったく必芁ありたせん。

残念なこずに、珟圚の゜リュヌションは、その利点をすべお備えおいおも、実際の䜿甚では他の重芁なニュアンスを考慮する必芁があるため、実際の䜿甚にはただ完党には適しおいたせん。ネットワヌクサブシステムにはほずんど適甚されたせん。これは、プロトコルに違いがあるクラむアントが衚瀺されるこずを意味したすすべおのナヌザヌが定期的か぀喜んでアプリケヌションを曎新するわけではありたせん。サヌバヌの応答は2぀ありたす。叀いプロトコルでクラむアントにサヌビスを提䟛するこずを拒吊するか、さたざたなバヌゞョンでの同時操䜜をサポヌトしたす。この質問に察する答えは、蚘事の第2郚で提䟛できたす-取り䞊げられたトピックに関心がある堎合個人メッセヌゞたたはコメントで衚珟するこずが提案されおいたす。

Source: https://habr.com/ru/post/J336464/


All Articles