Работа с байтовыми массивами в 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, т. Е. Байтов 20-39 полного пакета TCP / IP, в функцию Parse без предварительного извлечения его в новый байтовый массив?

В C ++ я мог бы сделать следующее:

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

Есть ли что-нибудь подобное в C #? Я хочу по возможности избежать создания и последующей сборки мусора временного байтового массива.

13
задан 19.11.2019, 22:17

10 ответов

Обычная практика, которую Вы видите в платформе.NET, и что я рекомендую использовать здесь, указывает смещение и длину. Поэтому заставьте свою функцию Синтаксического анализа также принять, что смещение в переданном массиве и число элементов используют.

, Конечно, те же правила применяются, как будто необходимо было передать указатель как в C++ - массив не должен быть изменен, или иначе он может привести к неопределенному поведению, если Вы не уверены, когда точно данные будут использоваться. Но это не проблема, если Вы больше не собираетесь быть изменением массива.

24
ответ дан 19.11.2019, 22:21
  • 1
    > > > импортируйте ctypes > > > = (1,2,3) > > > ctypes.addressof (a) Traceback (новый вызов в последний раз): Файл " < input> " строка 1, в < module> TypeError: недопустимый тип > > > идентификатор (a) 4493268872 > > > –  29.08.2011, 02:28

Нет никакого способа использовать верифицируемый код, чтобы сделать это. Если Ваш метод Синтаксического анализа может иметь дело с наличием IEnumerable< byte> затем можно использовать выражение

TCPHeader tcp = Parse(packet.Skip(20));
LINQ
0
ответ дан 19.11.2019, 22:18
  • 1
    Я соглашаюсь с Barry: вышеупомянутый код приводит к TypeError: invalid type, когда я пробую его Python 3.4. – Brandon Rhodes 25.06.2014, 09:22

Я не думаю, что можно сделать что-то как этот в C#. Вы могли или сделать Синтаксический анализ (), функция использует смещение или создает 3 массива байтов для начала; один для Заголовка IP, один для Заголовка TCP и один для Полезной нагрузки.

0
ответ дан 19.11.2019, 22:18
  • 1
    Лучшее решение IMO состояло бы в том, чтобы использовать ArraySegment< T> который делает проверку границ Вас, так, чтобы Вы don' t должны копировать его везде. – casperOne 19.11.2019, 22:19

Это - то, как я решил его прибывающий из того, чтобы быть 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);
  }
1
ответ дан 19.11.2019, 22:19
  • 1
    Вы могли бы хотеть посмотреть stackoverflow.com/questions/2871 , если производительность - то, что Вы после и переключатель от классов до структур. Я также оставил бы все в сетевом порядке и только преобразовал бы, когда Вам нужен он. – Rex Logan 19.11.2019, 22:19
  • 2
    Это - конечно, простой, изящный способ сделать это. I' ve определил классы для IP, TCP и заголовков UDP. Внутренне, я использую функции BitConverter для извлечения значений и IPAddress. NetworkToHostOrder для свопинга байтов. Я могу запустить некоторые тесты для наблюдения, какой подход более эффективен. – Matt Davis 19.11.2019, 22:19

Вы могли использовать LINQ, чтобы сделать что-то как:

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

, Но Система. Буфер. BlockCopy / Система. Массив. Копия, вероятно, более эффективна.

1
ответ дан 19.11.2019, 22:19

При реальной необходимости в подобных управление необходимо посмотреть unsafe функция C#. Это позволяет Вам иметь указатель и прикреплять его так, чтобы GC не перемещал его:

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

Однако эта практика не предлагается для работы с управляемым, только кодируют, при отсутствии проблем производительности. Вы могли передать смещение и длину как в Stream класс.

3
ответ дан 19.11.2019, 22:20

Если IEnumerable<byte> приемлемо как вход, а не byte[], и Вы используете C# 3.0, то Вы могли записать:

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

Примечание, что это все еще выделяет экземпляры перечислителя под покрытиями, таким образом, Вы не выходите из выделения в целом, и таким образом, для небольшого количества байтов это может на самом деле быть медленнее, чем выделение нового массива и копирование байтов в него.

я не волновался бы слишком много о выделении и GC небольших временных массивов, чтобы быть честным все же..NET собрала "мусор", среда чрезвычайно эффективна в этом типе шаблона выделения, особенно если массивы являются недолгими, поэтому если Вы не представили его и нашли, что GC проблема затем, я записал бы это самым интуитивным способом и согласовал бы проблемы производительности, когда Вы знаете, что у Вас есть они.

4
ответ дан 19.11.2019, 22:20
  • 1
    Спасибо, Greg. По правде говоря, я haven' t представил его. Но здравый смысл говорит, что выделение нового массива и копирование 20 байтов менее эффективны, чем простое использование как есть массива. Учитывая количество пакетов, я должен быть максимально эффективным. Плюс, это смотрит ' neater' без выделения и копии. – Matt Davis 19.11.2019, 22:21

Я передал бы 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
ответ дан 19.11.2019, 22:20
  • 1
    ooops! ArraySegment< byte> b2 = новый ArraySegment< byte> (b1, 20, b1. Длина 20); – gimel 19.11.2019, 22:21
  • 2
    ArraySegment< byte> seg = новый ArraySegment< byte> (пакет, 20, пакет. Длина 1); – gimel 19.11.2019, 22:21

Если можно изменить синтаксический анализ () метод, изменить его для принятия смещения, где обработка должна начаться. Синтаксический анализ TCPHeader (байт [] буфер, международное смещение);

2
ответ дан 19.11.2019, 22:22

Почему бы не зеркально отразить проблему и создать классы, которые накладывают буфер для вытаскивания битов?

// 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
ответ дан 19.11.2019, 22:23

Теги

Похожие вопросы