13

完全な TCP/IP パケットを表すバイト配列があります。明確にするために、バイト配列は次のように並べられます。

(IP ヘッダー - 20 バイト)(TCP ヘッダー - 20 バイト)(ペイロード - X バイト)

バイト配列を受け取り、オブジェクトをParse返す関数がありTCPHeaderます。次のようになります。

TCPHeader Parse( byte[] buffer );

元のバイト配列を考えると、これが私が今この関数を呼び出している方法です。

byte[] tcpbuffer = new byte[ 20 ];
System.Buffer.BlockCopy( packet, 20, tcpbuffer, 0, 20 );
TCPHeader tcp = Parse( tcpbuffer );

Parse最初に新しいバイト配列に抽出せずに、TCP バイト配列、つまり完全な TCP/IP パケットのバイト 20 ~ 39 を関数に渡す便利な方法はありますか?

C++ では、次のことができます。

TCPHeader tcp = Parse( &packet[ 20 ] );

C#に似たようなものはありますか? 可能であれば、一時的なバイト配列の作成とその後のガベージ コレクションを回避したいと考えています。

4

11 に答える 11

24

.NET フレームワークで見られる一般的な方法であり、ここで使用することをお勧めしますが、オフセットと長さを指定することです。したがって、Parse 関数には、渡された配列のオフセットと、使用する要素の数も受け入れるようにしてください。

もちろん、C++ のようにポインターを渡す場合と同じ規則が適用されます。配列を変更してはなりません。そうしないと、データが正確にいつ使用されるかわからない場合、未定義の動作が発生する可能性があります。しかし、配列を変更する予定がなくなった場合、これは問題ありません。

于 2009-01-03T16:05:17.327 に答える
22

ArraySegment<byte>この場合は を渡します。

Parseメソッドを次のように変更します。

// Changed TCPHeader to TcpHeader to adhere to public naming conventions.
TcpHeader Parse(ArraySegment<byte> buffer)

そして、呼び出しを次のように変更します。

// Create the array segment.
ArraySegment<byte> seg = new ArraySegment<byte>(packet, 20, 20);

// Call parse.
TcpHeader header = Parse(seg);

を使用してArraySegment<T>も配列はコピーされず、コンストラクターで境界チェックが行われます (誤った境界を指定しないようにするため)。次にParse、セグメントで指定された境界で動作するようにメソッドを変更すると、問題ありません。

完全なバイト配列を受け入れる便利なオーバーロードを作成することもできます。

// Accepts full array.
TcpHeader Parse(byte[] buffer)
{
    // Call the overload.
    return Parse(new ArraySegment<byte>(buffer));
}

// Changed TCPHeader to TcpHeader to adhere to public naming conventions.
TcpHeader Parse(ArraySegment<byte> buffer)
于 2009-01-03T16:06:46.247 に答える
4

IEnumerable<byte>ではなく が入力として受け入れられ、 C# 3.0 を使用している場合byte[]は、次のように記述できます。

tcpbuffer.Skip(20).Take(20);

これは依然として列挙子インスタンスをカバーの下に割り当てるため、割り当てを完全に回避するわけではないことに注意してください。そのため、バイト数が少ない場合、新しい配列を割り当ててバイトをコピーするよりも実際には遅くなる可能性があります。

正直なところ、小さな一時配列の割り当てとGCについてはあまり心配しません。.NET ガベージ コレクション環境は、このタイプの割り当てパターンで非常に効率的です。特に配列の寿命が短い場合は、そのため、プロファイルを作成して GC が問題であることが判明しない限り、最も直感的な方法で記述します。パフォーマンスの問題があることがわかっている場合は、パフォーマンスの問題を修正します。

于 2009-01-03T16:05:01.647 に答える
3

この種の制御が本当に必要な場合はunsafe、C# の機能を確認する必要があります。ポインタを固定して、GC が移動しないようにすることができます。

fixed(byte* b = &bytes[20]) {
}

ただし、この方法は、パフォーマンスの問題がない場合、マネージのみのコードで作業することはお勧めしません。クラスのようにオフセットと長さを渡すことができStreamます。

于 2009-01-03T16:06:06.293 に答える
2

parse() メソッドを変更できる場合は、処理を開始するオフセットを受け入れるように変更します。TCPHeader Parse( byte[] buffer , int オフセット);

于 2009-01-03T16:11:44.113 に答える
1

LINQ を使用して、次のようなことを行うことができます。

tcpbuffer.Skip(20).Take(20);

しかし、 System.Buffer.BlockCopy / System.Array.Copy はおそらくより効率的です。

于 2009-01-03T16:09:14.090 に答える
1

これが、ACプログラマーからAC#プログラマーになるまでの解決方法です。私は、MemoryStream を使用してそれをストリームに変換し、次に BinaryReader を使用してデータのバイナリ ブロックを分割するのが好きです。ネットワーク順序からリトル エンディアンに変換する 2 つのヘルパー関数を追加する必要がありました。また、送信する byte[] を作成する 場合は、すべてのケースを指定せずにオブジェクトを元の型にキャストする方法はありますか? これには、オブジェクトの配列から byte[] への変換を可能にする関数があります。

  Hashtable parse(byte[] buf, int offset )
  {

     Hashtable tcpheader = new Hashtable();

     if(buf.Length < (20+offset)) return tcpheader;

     System.IO.MemoryStream stm = new System.IO.MemoryStream( buf, offset, buf.Length-offset );
     System.IO.BinaryReader rdr = new System.IO.BinaryReader( stm );

     tcpheader["SourcePort"]    = ReadUInt16BigEndian(rdr);
     tcpheader["DestPort"]      = ReadUInt16BigEndian(rdr);
     tcpheader["SeqNum"]        = ReadUInt32BigEndian(rdr);
     tcpheader["AckNum"]        = ReadUInt32BigEndian(rdr);
     tcpheader["Offset"]        = rdr.ReadByte() >> 4;
     tcpheader["Flags"]         = rdr.ReadByte() & 0x3f;
     tcpheader["Window"]        = ReadUInt16BigEndian(rdr);
     tcpheader["Checksum"]      = ReadUInt16BigEndian(rdr);
     tcpheader["UrgentPointer"] = ReadUInt16BigEndian(rdr);

     // ignoring tcp options in header might be dangerous

     return tcpheader;
  } 

  UInt16 ReadUInt16BigEndian(BinaryReader rdr)
  {
     UInt16 res = (UInt16)(rdr.ReadByte());
     res <<= 8;
     res |= rdr.ReadByte();
     return(res);
  }

  UInt32 ReadUInt32BigEndian(BinaryReader rdr)
  {
     UInt32 res = (UInt32)(rdr.ReadByte());
     res <<= 8;
     res |= rdr.ReadByte();
     res <<= 8;
     res |= rdr.ReadByte();
     res <<= 8;
     res |= rdr.ReadByte();
     return(res);
  }
于 2009-01-03T18:16:54.987 に答える
0

答えてくれた何人か

tcpbuffer.Skip(20).Take(20);

間違った。これは優れたソリューションですが、コードは次のようになります。

packet.Skip(20).Take(20);

メインパケットでSkip メソッドと Take メソッドを使用する必要があり、投稿したコードにtcpbufferが存在しないようにする必要があります。また、 then を使用する必要はありませんSystem.Buffer.BlockCopy

JaredParはほぼ正しかったが、Take メソッドを忘れていた

TCPHeader tcp = Parse(packet.Skip(20));

しかし、彼はtcpbufferを間違えていませんでした。投稿したコードの最後の行は次のようになります。

TCPHeader tcp = Parse(packet.Skip(20).Take(20));

ただし、Skip と Take の代わりに System.Buffer.BlockCopy を使用したい場合は、Steven Robbins が答えたようにパフォーマンスが向上する可能性があります。「しかし、System.Buffer.BlockCopy / System.Array.Copy はおそらくより効率的です」解析関数が を処理できないIEnumerable<byte>か、投稿された質問で System.Buffer.Block に慣れている場合は、単純にtcpbufferをローカル変数ではなく、privateまたはprotectedまたはpublicまたはinternalおよびstaticまたは notフィールドにすることをお勧めします(inつまり、外部で定義および作成する必要があります投稿されたコードが実行されるメソッド)。したがって、tcpbuffer は1 回だけ作成され、その値 (バイト) は System.Buffer.BlockCopy 行に投稿したコードを渡すたびに設定されます。

このようにして、コードは次のようになります。

class Program
{
    //Your defined fields, properties, methods, constructors, delegates, events and etc.
    private byte[] tcpbuffer = new byte[20];
    Your unposted method title(arguments/parameters...)
    {
    //Your unposted code before your posted code
    //byte[] tcpbuffer = new byte[ 20 ]; No need anymore! this line can be removed.
    System.Buffer.BlockCopy( packet, 20, this.tcpbuffer, 0, 20 );
    TCPHeader tcp = Parse( this.tcpbuffer );
    //Your unposted code after your posted code
    }
    //Your defined fields, properties, methods, constructors, delegates, events and etc.
}

または単に必要な部分のみ:

private byte[] tcpbuffer = new byte[20];
...
{
...
        //byte[] tcpbuffer = new byte[ 20 ]; No need anymore! This line can be removed.
        System.Buffer.BlockCopy( packet, 20, this.tcpbuffer, 0, 20 );
        TCPHeader tcp = Parse( this.tcpbuffer );
...
}

あなたがした場合:

private byte[] tcpbuffer;

代わりに、コンストラクターに次の行を追加する必要があります。

this.tcpbuffer = new byte[20];

また

tcpbuffer = new byte[20];

これを入力する必要はありませんtcpbuffer の前はオプションですが、静的に定義した場合、それはできません。代わりに、クラス名を入力してからドット「.」を入力するか、そのままにしておく必要があります (フィールドの名前を入力するだけです)。

于 2014-04-28T16:17:33.553 に答える
0

問題をひっくり返して、バッファをオーバーレイしてビットを引き出すクラスを作成してみませんか?

// member variables
IPHeader ipHeader = new IPHeader();
TCPHeader tcpHeader = new TCPHeader();

// passing in the buffer, an offset and a length allows you
// to move the header over the buffer
ipHeader.SetBuffer( buffer, 0, 20 );

if( ipHeader.Protocol == TCP )
{
    tcpHeader.SetBuffer( buffer, ipHeader.ProtocolOffset, 20 );
}
于 2009-01-03T16:42:03.803 に答える
0

C# でそのようなことはできないと思います。Parse() 関数でオフセットを使用するか、最初に 3 バイト配列を作成することができます。1 つは IP ヘッダー用、1 つは TCP ヘッダー用、もう 1 つはペイロード用です。

于 2009-01-03T16:03:43.383 に答える
0

検証可能なコードを使用してこれを行う方法はありません。Parse メソッドが IEnumerable<byte> を処理できる場合は、LINQ 式を使用できます

TCPHeader tcp = Parse(packet.Skip(20));
于 2009-01-03T16:06:22.643 に答える