完全な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#に似たものはありますか?可能であれば、一時バイト配列の作成とその後のガベージコレクションを避けたいと思います。
.NET Frameworkで確認できる一般的な方法であり、ここで使用することをお勧めしますが、オフセットと長さを指定することです。したがって、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>
を使用しても配列はコピーされず、コンストラクターで境界チェックが行われます(誤った境界を指定しないようにするため)。次に、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)
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);
これが私が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);
}
LINQを使用して次のようなことを行うことができます。
tcpbuffer.Skip(20).Take(20);
ただし、System.Buffer.BlockCopy /System.Array.Copyの方がおそらく効率的です。
C#ではそのようなことはできないと思います。 Parse()関数にオフセットを使用させるか、最初に3バイトの配列を作成することができます。 1つはIPヘッダー用、もう1つはTCPヘッダー用、もう1つはペイロード用です。
これを行うために検証可能なコードを使用する方法はありません。 ParseメソッドがIEnumerable <byte>を持つことを処理できる場合は、LINQ式を使用できます
TCPHeader tcp = Parse(packet.Skip(20));
答えた人もいます
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と入力します。代わりに、クラス名を入力してからドット「。」を入力するか、そのままにしておく必要があります(フィールドの名前を入力するだけですべてです)。
問題を裏返し、バッファをオーバーレイしてビットを引き出すクラスを作成してみませんか?
// 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 );
}