Вопрос

У меня есть массив байтов, который представляет собой полный пакет 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 и которую я рекомендую использовать здесь, — это указание смещения и длины.Поэтому сделайте так, чтобы ваша функция 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);

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

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

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

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

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

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

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

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

Но System.Buffer.BlockCopy/System.Array.Copy, вероятно, более эффективны.

Вот как я решил эту проблему, пройдя путь от программиста C до программиста C#.Мне нравится использовать MemoryStream для преобразования его в поток, а затем BinaryReader для разделения двоичного блока данных.Пришлось добавить две вспомогательные функции для преобразования сетевого порядка в прямой порядок байтов.Также о создании 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-байтовые массивы;один для заголовка IP, один для заголовка TCP и один для полезной нагрузки.

Для этого невозможно использовать проверяемый код.Если ваш метод Parse может работать с IEnumerable<byte>, вы можете использовать выражение LINQ.

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

Некоторые люди, ответившие

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

сделал это неправильно.Это отличный решение, но код должен выглядеть так:

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

Вы должны использовать методы Skip и Take на своем основном пакет, и tcpbuffer не должно существовать в опубликованном вами коде.Также вам не нужно использовать then System.Buffer.BlockCopy.

ДжаредПар было почти правильно, но он забыл метод Take

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

Но он не ошибся tcpbuffer.Последняя строка опубликованного вами кода должна выглядеть так:

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

Но если вы все равно хотите использовать System.Buffer.BlockCopy вместо Skip and Take, потому что, возможно, это лучше по производительности, как ответил Стивен Роббинс:«Но System.Buffer.BlockCopy/System.Array.Copy, вероятно, более эффективны», или ваша функция Parse не могу иметь дело с IEnumerable<byte>, или вы больше привыкли к System.Buffer.Block в своем опубликованном вопросе, тогда я бы рекомендовал просто просто сделать tcpbuffer не местный переменная, но частный или защищенный или общественный или внутренний и статический или нет поле (другими словами, он должен быть определен и создан снаружи метод, в котором выполняется опубликованный вами код).Таким образом, tcpbuffer будет создан только один раз, и его значения (байты) будут устанавливаться каждый раз, когда вы передаете код, опубликованный в строке 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 это необязательно, но если вы определили его статически, то вы не могу сделай это.Вместо этого вам придется ввести имя класса, а затем точку «.» или оставить его (просто введите имя поля и все).

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

// 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 );
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top