バイトストリームから可変長記述子を解析し、そのタイプに基づいて動作する
-
06-07-2019 - |
質問
コード内のさまざまな構造体/クラスとして表す一連の可変長記述子を含むバイトストリームから読み取ります。各記述子には、そのタイプを識別するために使用される他のすべての記述子と共通の固定長ヘッダーがあります。
各記述子を最適に解析および表現するために使用できる適切なモデルまたはパターンがあり、そのタイプに応じて適切なアクションを実行しますか?
解決
これらのタイプのパーサーを多数作成しました。
固定長ヘッダーを読み取り、単純なスイッチケースを使用して構造体に正しいコンストラクターにディスパッチし、固定ヘッダーとストリームをコンストラクターに渡して、ストリームの可変部分を消費できるようにすることをお勧めします。
他のヒント
これは、ファイル解析の一般的な問題です。通常、記述子の known 部分(この場合は幸いなことに固定長ですが、常にそうとは限りません)を読み取り、そこに分岐します。一般に、システムが広く柔軟であると期待しているため、ここでは戦略パターンを使用しますが、ストレートスイッチまたは工場でも同様に機能します。
もう1つの質問は、ダウンストリームコードを管理および信頼していますか?意味:工場/戦略の実装?その場合は、ストリームと消費すると予想されるバイト数をそれらに与えることができます(おそらく、デバッグアサーションを配置して、正確に正しい量を読み取ることを確認します)。
ファクトリ/戦略の実装を信頼できない場合(おそらく、ユーザーコードがカスタムデシリアライザーを使用できるようにする場合)、ストリームの上にラッパーを構築します(例: SubStream
からprotobuf-net )、予想されるバイト数の消費のみを許可し(その後EOFを報告)、このブロック外のシークなどの操作は許可しません。また、十分なデータが消費されていることをランタイムチェックで確認します(リリースビルドでも)。ただし、この場合、未読データをすべて読み取ってしまいます。つまり、ダウンストリームコードが20バイトを消費すると予想した場合、12 、次の8をスキップして次の記述子を読み取ります。
その上で展開するには;ここでの戦略設計には、次のようなものがあります。
interface ISerializer {
object Deserialize(Stream source, int bytes);
void Serialize(Stream destination, object value);
}
予想されるマーカーごとにそのようなシリアライザーの辞書(または数が少ない場合は単なるリスト)を作成し、シリアライザーを解決してから、 Deserialize
メソッドを呼び出します。マーカーが認識されない場合、(のいずれか):
- 指定されたバイト数をスキップ
- エラーをスロー
- バッファのどこかに余分なバイトを保存する(予期しないデータの往復を許可する)
上記の補足として-このアプローチ(戦略)は、システムがリフレクションまたはランタイムDSL(など)を介して実行時に決定される場合に役立ちます。システムがコンパイル時に完全に予測可能である場合(変更しないため、またはコード生成を使用しているため)、単純な switch
アプローチの方がより適切です適切-適切なコードを直接注入できるため、おそらく追加のインターフェイスは必要ありません。
覚えておくべき重要なことは、ストリームから読み込んでいて有効なヘッダー/メッセージが検出されない場合、最初のバイトのみを破棄してから再試行することです。多くの場合、代わりにパケットまたはメッセージ全体が破棄され、有効なデータが失われる可能性があります。
これは、ファクトリメソッドまたはおそらく Abstract Factory 。ヘッダーに基づいて、どのファクトリメソッドを呼び出すかを選択すると、関連するタイプのオブジェクトが返されます。
これが単にswitchステートメントにコンストラクターを追加するよりも良いかどうかは、作成するオブジェクトの複雑さと均一性に依存します。
提案します:
fifo = Fifo.new while(fd is readable) { read everything off the fd and stick it into fifo if (the front of the fifo is has a valid header and the fifo is big enough for payload) { dispatch constructor, remove bytes from fifo } }
このメソッドの場合:
- 不正なペイロードのエラーチェックを実行し、潜在的に不正なデータを破棄できます
- データはfdの読み取りバッファで待機していません(大きなペイロードでは問題になる可能性があります)
オブジェクト指向にしたい場合は、オブジェクト階層でビジターパターンを使用できます。私がそれをやった方法は次のようでした(ネットワークからキャプチャされたパケットを識別するために、あなたが必要とするものとほぼ同じです):
-
1つの親クラスを持つ巨大なオブジェクト階層
-
各クラスには、その親に登録する静的コンストラクターがあるため、親はその直接の子を知っています(これはc ++でした。このステップは、適切なリフレクションをサポートする言語では必要ないと思います)
-
各クラスには、バイトストリームの残りの部分を取得する静的コンストラクターメソッドがあり、それに基づいて、そのデータを処理する責任があるかどうかを決定しました
-
パケットが到着したとき、単純にそれをメインの親クラス(Packetと呼ばれる)の静的コンストラクターメソッドに渡し、そのパケットを処理する責任があるかどうかすべての子を順番にチェックし、これは、階層の一番下にある1つのクラスがインスタンス化されたクラスを返すまで再帰的に行われました。
-
静的な「コンストラクタ」のそれぞれ;メソッドは、バイトストリームから独自のヘッダーを切り取り、ペイロードのみをその子に渡します。
このアプローチの利点は、他のクラスを表示/変更する必要がある WITHOUT オブジェクト階層のどこにでも新しい型を追加できることです。パケットに対して非常にうまく機能しました。次のようになりました:
- パケット
- EthernetPacket
- IPPacket
- UDPPacket、TCPPacket、ICMPPacket
- ...
アイデアをご覧ください。