ネットワークプロトコルの設計

ハブ経由でプロトコルの設計に関する記事を検索しましたが、驚いたことに何も見つかりませんでした。 おそらく、sabzhに関するあなたの考えを共有する価値があります。 型への分割は純粋に私のものであり、ディレクトリで見つけたものと一致しない可能性があることをすぐに言わなければなりません。 また、C / C ++言語が使用されることにも事前に同意します。



はじめに



プロトコルの主なタイプを検討してください。



質疑応答



これらのプロトコルは、小さなデータの通信に基づいています。 通常、通信プロトコルは強く型付けされています。 例は、モデム用のよく知られているATコマンドです。

このタイプのプロトコルは、処理が最も簡単です(データ分離のある行の基本的な構文解析が必要です)。 しかし、多かれ少なかれ深刻なタスクでそのようなプロトコルと通信することは非常に簡単ではありません。 このようなプロトコルは、スカラータイプ(文字列、数値)の小さなデータチャンクの送信に適しています。

それにもかかわらず、そのようなプロトコルの設計も必要です。





クライアントからサーバーに10個の数字を転送し、応答で文字列または数字を取得する必要があります(入力データによって異なります)。 これを行うには、「握手」の段階、データを送信する段階、応答を受信する段階が必要です。

「ハンドシェイク」 :「HELLO」という単語を前後に送信するだけで十分です(サーバーの代わりに別のクライアントに到達できることがわかっている場合は、クライアントとサーバーの挨拶を分離できます。たとえば、「HELLOCL」(クライアントから)と「HELLOSRV」サーバー))。 処理は基本的であり、文字列の比較になります。

データ転送 :コマンド「SEND x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 」をクライアントからの送信コマンドとして受け取り、「OK SEND」をサーバー応答として受け取ります。 この処理も基本的なもので、文字列の比較とsscanf()の呼び出しになります。

応答の受信 :サーバーが「ANSWER STR string 」(応答が文字列の場合)または「ANSWER NUM number 」(応答が数値の場合)を送信することに同意します。 クライアントはOK ANSWERコマンドで応答します。



すべてがシンプルで明確に思えます。 しかし。 実際、この方法では、送信された回答がどの番号のセットに属するかを理解できません。 この問題を解決するのは簡単です。 データを送信するときは、「SEND id NUMS x1 x2 x3 x4 x5 x6 x7 x8 x9 x10 」および「OK SEND id 」というコマンドを使用します。idは、この一連の番号の一意の識別子です。 応答するとき、対応するコマンドは次のようになります:「ANSWER id STR string 」、「ANSWER id NUM number 」、「OK ANSWER id 」。 このようなプロトコルは、ほとんどの場合すでに網羅されています。



構造



このタイプのプロトコルが最も一般的です。 その基本は、送信されるデータの一部の厳密な型指定です。 つまり、そのようなパケット(オフセットとその長さ)にそのようなデータとそのようなデータが含まれることを事前に知っています(一部のフィールドのオフセットと長さも構造で指定できますが、最初に指定されたオフセットが主に使用されます)。 そのようなプロトコルは、ほとんどすべて低レベルのプロトコルです。

そのようなプロトコルの疑いのない利点(特にすべてのフィールドの厳密に指定されたオフセットと長さの条件下で)は、処理が非常に簡単です。 サイズを確認し、パッケージに対応する構造体のインスタンスでmemcpy()コマンドを実行するだけです。

このようなプロトコルを設計するときは、Cに構造体を格納する機能を覚えておく必要があります。つまり、パッキングと呼ばれるものを意味します。 実際、構造のインスタンスは、メモリ内で特定の多重度で整列する必要があります(多重度はプログラム全体に対して設定されます)。 デフォルトでは、4の多重度が使用されます(この値は、たとえば名前空間stdに強く影響するため、変更することはお勧めしません)。 つまり、構造のサイズは常に4の倍数になります(構造のサイズが14の場合、意味のない2バイトが最後に追加され、サイズは16になります)。 したがって、サーバーとクライアントでこのパラメーターの同じ値を処理する必要があります。

また、このようなプロトコルを設計する際の主な間違いは、メモリにマルチバイトタイプを格納することに注意を払わないことです。 x86はリトルエンディアン(最年少から最古)の形式でそれらを格納することを覚えておく必要があり、ネットワークプロトコルの標準に従って、たとえばSPARCコンピューターでは、ビッグエンディアン(最古から最年少)の形式で格納する必要があります。 したがって、マルチバイト型がどの順序で来るかを知り、必要に応じてそれらを反転する必要があります。 さらに、高速が必要な場合、大量の交換データがあり、ローテーションから逃れることはできません(たとえば、分散コンピューティングのクロスアーキテクチャシステムを開発するときに基準が関連します)、ローテーション機能に30分を費やす必要がありますが、できるだけ速く書き込みます。 このような場合、標準のhtonl()、ntohl()が間に合わない場合があります。



タグ



タグ付けされたプロトコルを参照してください。今ではファッショナブルなXMLのようなプロトコルです。 そのようなプロトコルは、非常に冗長ですが、それでも処理が簡単で完全に柔軟です。 主な問題は冗長性であり、処理速度はそれほど高くありません。

そのようなプロトコルを設計する際の主な間違いは、すべてを一度に突き出すことです。 プロトコルの要件を明確に定義し、本当に必要な機能のサブセットを分離する必要があります。 このような設計へのアプローチはおそらく拡張性があまりありませんが、処理時間を節約することができます。 さらに、パーサーモジュールを適切に設計することにより、拡張性の問題を最小限に抑えることができます(共通コードにいくつかの関数とチェックを追加します)。

個人的には(速度が要求され、構造化されたネットワークアプリケーションが専門であるため)、このアプローチは不要で無駄に思えます。



タグ+構造



おそらく最も興味深いタイプのプロトコルです。 高い解析速度と柔軟性を組み合わせることができます。

このプロトコルのパケットはタイプごとに分けられます。 タイプ従属のツリーを設計することもできます(たとえば、パッケージAはパッケージBにのみ含めることができますが、個別に移動することはできません)。 このようなパッケージの基礎は、厳密に定義されたヘッダー構造部分(それ自体の各タイプ)+データの非剛性部分です(構造内の最後のパラメーターの可変長を持つ構造データでも同じアプローチが使用されます)。

このようなプロトコルの解析は、構造プロトコルの解析よりも複雑ですが、純粋にタグ付けされたプロトコルの解析よりも簡単で高速です。

通常、パッケージのタイプはヘッダー部分の最初のフィールドとして書き込まれ、ヘッダーを構造にコピーし、データをメモリの一部にコピーする必要な関数を呼び出すだけです(通常はchar *で十分ですが、場合によっては特定の構造の配列に直接コピーする方が便利です)。

設計における主な間違いは、「タグプロトコルのように」作成し、小さなヘッダーを使用してさまざまなタイプの束を作成することです。 これにより、解析速度が低下し、このタイプのすべての利点が無効になります。 したがって、スローネスと低速のバランスを取る必要があります。 実践が示しているように、ほとんどの場合、このプロトコルが通常のデータ構造である場合、理想的なタグブレークはクラスブレークと同じです。



PSこの投稿は、特定の実装を対象としたものではなく、レビューです。 特定の実装(よく知られているものと、他の実装の両方)について読みたい人がいる場合は、コメントを書いてください。




All Articles