2

バックグラウンド:

私は F# Records をよく利用しています。現在、私は独自のバイナリ プロトコル (非常に奇妙に設計されたプロトコル) のパケット分析と再生のプロジェクトに取り組んでいます。

パケットのスケルトン レコードを定義します。

type bytes = byte array
type packetSkeleton = {
    part1 : bytes
    part2 : bytes 
    ... }

これを使用してパケットを「分析」するのは簡単です (実際には、バイト フィールドに名前を付けるだけです)。

let dissect (raw : bytes) =
  let slice a b = raw.[a..b]
  { part1 = slice 0 4
    part2 = slice 4 5
    ... }

これは、長いパケットでも完全に機能します。スライスに予測可能なパターンがある場合は、きちんとした再帰関数を使用することもできます。

そこで、パケットを分析し、必要なフィールドを取り出して、分析から取得したフィールドを使用して packetSkeleton に基づいてパケットを作成します。

let createAuthStub a b c d e f g h ... =
   { part1 = a; part2 = b
     part3 = d; ...
   }

次に、入力されたスタブを作成した後、ネットワークに配置できる形式にデシリアライズする必要があります。

(* packetSkeleton -> byte array *)
let deserialise (packet : packetSkeleton) =
  [| packet.part1; packet.part2; ... |]

let xab = dissect buf
let authStub = createAuthStub xab.part1 1 2 xab.part9 ...

deserialise authStub |> send

したがって、レコード タイプ、特定のパケットのレコードの作成、およびデシリアライズされたバイト配列の 3 つの領域があることになります。コードの明瞭さの点で、これは私のデザインの選択としては不十分であることがわかります。この初期段階でも、すでに足を撃ち始めているのを感じています.

質問:

a) そのようなプロジェクトに正しいデータ型を使用していますか? 私のアプローチは正しいですか?
b) このコードをきれいに感じさせるのをあきらめるべきですか?

これをタッチアンドゴーでコーディングしているので、いくつかの洞察をいただければ幸いです。

PS この問題は C に非常に適していると思いますが、F# の方が楽しいです (さらに、後でディセクタを検証するのも魅力的です)。

4

2 に答える 2

2

パケットがかなり大きくなると、packetSkeleton扱いにくくなる可能性があります。別のオプションは、生のバイトを操作し、各部分を読み書きするモジュールを定義することです。

module Packet
  let Length = 42
  let GetPart1 src = src.[0..3]
  let SetPart1 src dst = Array.blit src 0 dst 0 4
  let GetPart2 src = src.[4..5]
  let SetPart2 src dst = Array.blit src 0 dst 4 2
  ...

open Packet 

let createAuthStub bytes b c =
  let resp = Array.zeroCreate Packet.Length
  SetPart1 (GetPart1 bytes) 
  SetPart2 b resp
  SetPart3 c resp
  SetPart4 (GetPart9 bytes) 
  resp

これにより、デシリアライゼーション関数の必要性がなくなります (おそらく、パフォーマンスが少し向上します)。

編集

ラッパー型の作成は別のオプションです

type Packet(bytes: byte[]) =
  new() = Packet(Array.zeroCreate Packet.Length)
  static member Length = 42
  member x.Part1
    with get() = bytes.[0..3]
    and set value = Array.blit value 0 bytes 0 4
    ...

コードを少し減らすかもしれません:

let createAuthStub (req: Packet) b c =
  let resp = Packet()
  resp.Part1 <- req.Part1
  resp.Part2 <- b
  resp.Part3 <- c
  resp.Part4 <- req.Part9
  resp
于 2012-10-04T02:57:00.137 に答える
1

あなたのアプローチは本質的に健全だと思いますが、もちろん、詳細を知らずに判断するのは困難です。

コードに示され、機能アーキテクチャの鍵となる重要なアイデアの 1 つは、タイプ (問題のドメインをモデル化するために使用される) と、ドメイン モデルの値を作成し、それを処理してフォーマットする処理機能を分離することだと思います。

あなたの場合:

  • 問題ドメインのタイプbytesとモデルpacketSkeleton
  • 関数はあなたのドメインを処理します(そして、引数としてcreateAuthStub全体をとった方が読みやすいかもしれないというダニエルに同意します)packetSkeleton
  • 関数deserializeはドメインをバイトに戻します

コードを構造化するこの方法は、プログラムのさまざまな問題を分離するので、非常に優れていると思います。私は、これをより一般的なプログラミング アプローチとして説明しようとする記事を書きました。

于 2012-10-04T02:33:35.120 に答える