web-dev-qa-db-ja.com

C#でのバイト配列の操作

完全な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 );

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

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

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

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

13
Matt Davis

.NET Frameworkで確認できる一般的な方法であり、ここで使用することをお勧めしますが、オフセットと長さを指定することです。したがって、Parse関数が、渡された配列のオフセットと使用する要素の数も受け入れるようにします。

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

24
Spodi

この場合、 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)
22
casperOne

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

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

これでも列挙子インスタンスが内部で割り当てられるため、割り当てを完全にエスケープすることはありません。したがって、バイト数が少ない場合は、新しい配列を割り当ててその配列にバイトをコピーするよりも実際には遅くなる可能性があります。

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

4
Greg Beech

この種の制御が本当に必要な場合は、C#のunsafe機能を確認する必要があります。それはあなたがポインタを持っていて、GCがそれを動かさないようにそれを固定することを可能にします:

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

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

3
Mehrdad Afshari

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

2
DotNET

これが私がcプログラマーからc#プログラマーになることからそれを解決した方法です。私はMemoryStreamを使用してストリームに変換し、次にBinaryReaderを使用してデータのバイナリブロックを分解するのが好きです。ネットワーク順序からリトルエンディアンに変換するには、2つのヘルパー関数を追加する必要がありました。また、送信する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);
  }
1
Rex Logan

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

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

ただし、System.Buffer.BlockCopy /System.Array.Copyの方がおそらく効率的です。

1
Steven Robbins

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

0
Aistina

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

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

答えた人もいます

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

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

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

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

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

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

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

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

ただし、Skip and Takeの代わりにSystem.Buffer.BlockCopyを使用する場合は、Steven Robbinsが次のように答えたため、パフォーマンスが向上する可能性があります。関数の解析できませんIEnumerable<byte>を処理するか、投稿された質問でSystem.Buffer.Blockに慣れている場合は、単に tcpbufferを作成することをお勧めしますローカルではない変数ですが、プライベートまたは保護されたまたはパブリックまたは内部および- 静的かどうかフィールド(つまり、定義して作成する必要があります外部投稿されたコードが実行されるメソッド)。したがって、tcpbufferはonceのみ作成され、System.Buffer.BlockCopy行に投稿したコードを渡すたびに、tcpbufferの値(バイト)が設定されます。

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

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の前にthis。と入力する必要がないことはご存知ですが、これはオプションですが、静的に定義した場合は、cannotと入力します。代わりに、クラス名を入力してからドット「。」を入力するか、そのままにしておく必要があります(フィールドの名前を入力するだけですべてです)。

0
user2133061

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

// 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 );
}
0
headsling