У меня есть байтовый массив, который представляет полный пакет 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, т. Е. Байтов 20-39 полного пакета TCP / IP, в функцию Parse
без предварительного извлечения его в новый байтовый массив?
В C ++ я мог бы сделать следующее:
TCPHeader tcp = Parse( &packet[ 20 ] );
Есть ли что-нибудь подобное в C #? Я хочу по возможности избежать создания и последующей сборки мусора временного байтового массива.
Обычная практика, которую Вы видите в платформе.NET, и что я рекомендую использовать здесь, указывает смещение и длину. Поэтому заставьте свою функцию Синтаксического анализа также принять, что смещение в переданном массиве и число элементов используют.
, Конечно, те же правила применяются, как будто необходимо было передать указатель как в C++ - массив не должен быть изменен, или иначе он может привести к неопределенному поведению, если Вы не уверены, когда точно данные будут использоваться. Но это не проблема, если Вы больше не собираетесь быть изменением массива.
Нет никакого способа использовать верифицируемый код, чтобы сделать это. Если Ваш метод Синтаксического анализа может иметь дело с наличием IEnumerable< byte> затем можно использовать выражение
TCPHeader tcp = Parse(packet.Skip(20));
LINQ TypeError: invalid type
, когда я пробую его Python 3.4.
– Brandon Rhodes
25.06.2014, 09:22
Я не думаю, что можно сделать что-то как этот в C#. Вы могли или сделать Синтаксический анализ (), функция использует смещение или создает 3 массива байтов для начала; один для Заголовка IP, один для Заголовка TCP и один для Полезной нагрузки.
Это - то, как я решил его прибывающий из того, чтобы быть c программистом c# программисту. Мне нравится использовать MemoryStream для преобразования его в поток и затем BinaryReader для разбивания двоичного блока данных. Должны были добавить две функции помощника для преобразования от сетевого порядка до прямого порядка байтов. Также для создания байта [] для отправки видят , там путь, бросает объект назад к нему исходный тип без specifing каждый случай? , который имеет функцию, которые допускают преобразование из массива объектов к байту [].
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);
, Но Система. Буфер. BlockCopy / Система. Массив. Копия, вероятно, более эффективна.
При реальной необходимости в подобных управление необходимо посмотреть unsafe
функция C#. Это позволяет Вам иметь указатель и прикреплять его так, чтобы GC не перемещал его:
fixed(byte* b = &bytes[20]) {
}
Однако эта практика не предлагается для работы с управляемым, только кодируют, при отсутствии проблем производительности. Вы могли передать смещение и длину как в Stream
класс.
Если IEnumerable<byte>
приемлемо как вход, а не byte[]
, и Вы используете C# 3.0, то Вы могли записать:
tcpbuffer.Skip(20).Take(20);
Примечание, что это все еще выделяет экземпляры перечислителя под покрытиями, таким образом, Вы не выходите из выделения в целом, и таким образом, для небольшого количества байтов это может на самом деле быть медленнее, чем выделение нового массива и копирование байтов в него.
я не волновался бы слишком много о выделении и GC небольших временных массивов, чтобы быть честным все же..NET собрала "мусор", среда чрезвычайно эффективна в этом типе шаблона выделения, особенно если массивы являются недолгими, поэтому если Вы не представили его и нашли, что GC проблема затем, я записал бы это самым интуитивным способом и согласовал бы проблемы производительности, когда Вы знаете, что у Вас есть они.
Я передал бы 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)
Почему бы не зеркально отразить проблему и создать классы, которые накладывают буфер для вытаскивания битов?
// 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 );
}