C#でバイト配列を操作する
-
03-07-2019 - |
質問
完全な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を、最初に新しいバイト配列に抽出せずに<=>関数に渡す便利な方法はありますか?
C ++では、次のことができました。
TCPHeader tcp = Parse( &packet[ 20 ] );
C#に類似したものはありますか?可能であれば、一時バイト配列の作成とそれに続くガベージコレクションを避けたい。
解決
.NETフレームワークで見ることができる一般的な方法は、オフセットと長さを指定することです。したがって、Parse関数は、渡された配列のオフセットと使用する要素の数も受け入れるようにします。
もちろん、C ++のようにポインタを渡す場合と同じルールが適用されます-配列を変更しないでください。そうしないと、データが正確にいつ使用されるかわからない場合、未定義の動作が発生する可能性があります。ただし、配列を変更する必要がなくなっても問題ありません。
他のヒント
この場合 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>
を使用しても配列はコピーされず、コンストラクタで境界チェックが行われます(したがって、誤った境界を指定しないようにします)。次に、セグメントで指定された境界で動作するように<=>メソッドを変更すると、大丈夫です。
完全なバイト配列を受け入れる便利なオーバーロードを作成することもできます。
// 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)
IEnumerable<byte>
ではなくbyte[]
を入力として受け入れ、C#3.0を使用している場合、次のように記述できます。
tcpbuffer.Skip(20).Take(20);
これはまだカバーの下に列挙子インスタンスを割り当てるため、割り当てを完全にエスケープしないため、少数のバイトでは、新しい配列を割り当ててバイトをコピーするよりも実際に遅くなる可能性があることに注意してください。
正直なところ、小さな一時配列の割り当てとGCについてはあまり心配しません。 .NETガベージコレクション環境は、特に配列の寿命が短い場合、このタイプの割り当てパターンで非常に効率的です。そのため、プロファイルを作成してGCが問題であるとわかった場合を除き、最も直感的な方法で記述します。パフォーマンスの問題がある場合は、それを修正します。
この種の制御が本当に必要な場合は、C#のunsafe
機能を確認する必要があります。 GCが移動しないように、ポインターを保持して固定することができます。
fixed(byte* b = &bytes[20]) {
}
ただし、この問題は、パフォーマンスの問題がない場合にマネージドコードのみを使用する場合には推奨されません。 Stream
クラスのようにオフセットと長さを渡すことができます。
parse()メソッドを変更できる場合は、処理を開始するオフセットを受け入れるように変更します。 TCPHeader Parse(byte [] buffer、int offset);
LINQを使用して次のようなことができます:
tcpbuffer.Skip(20).Take(20);
しかし、System.Buffer.BlockCopy / System.Array.Copyはおそらくより効率的です。
これは、cプログラマーからc#プログラマーに至るまでの解決方法です。 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);
}
C#でそのようなことができるとは思わない。 Parse()関数でオフセットを使用するか、最初から3バイト配列を作成できます。 1つはIPヘッダー用、もう1つはTCPヘッダー用、もう1つはペイロード用です。
検証可能なコードを使用してこれを行う方法はありません。 ParseメソッドがIEnumerable <!> lt; byte <!> gt;を処理できる場合LINQ式を使用できます
TCPHeader tcp = Parse(packet.Skip(20));
回答した一部の人々
tcpbuffer.Skip(20).Take(20);
間違っていました。これは優れたソリューションですが、コードは次のようになります。
packet.Skip(20).Take(20);
メインのパケットでSkipおよびTakeメソッドを使用する必要があり、投稿したコードに tcpbuffer が存在しないようにする必要があります。また、 System.Buffer.BlockCopy
を使用する必要はありません。
JaredPar はほぼ正しかったが、彼はTakeメソッドを忘れた
TCPHeader tcp = Parse(packet.Skip(20));
しかし、彼は tcpbuffer に問題はありませんでした。 投稿したコードの最終行は次のようになります。
TCPHeader tcp = Parse(packet.Skip(20).Take(20));
しかし、とにかくSystem.Buffer.BlockCopyをスキップアンドテイクの代わりに使用する場合は、スティーブンロビンスが答えたようにパフォーマンスが向上する可能性があるためです:&quot;しかし、System.Buffer.BlockCopy / System.Array.Copyはおそらくより効率的です;、または解析関数が IEnumerable&lt; byte&gt; を処理できないできない、または投稿された質問でSystem.Buffer.Blockに慣れている場合は、< strong>単純に tcpbufferをローカルではなく変数にするが、プライベートまたは保護またはパブリックまたは< strong> internal および static または field ではありません(つまり、投稿されたコードが存在する outside メソッドを定義および作成する必要があります実行)。したがって、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の前に this。を入力する必要がないことはわかっていますが、これはオプションですが、静的に定義した場合はできません。代わりに、クラス名を入力してからドット「。」を入力するか、そのままにしておく必要があります(フィールドの名前を入力するだけです)。
問題を反転させて、バッファをオーバーレイしてビットを引き出すクラスを作成してみませんか?
// 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 );
}