Pytanie Praca z tablicami bajtów w C #


Mam tablicę bajtów reprezentującą kompletny pakiet TCP / IP. W celu wyjaśnienia, tablica bajtów jest uporządkowana w następujący sposób:

(Nagłówek IP - 20 bajtów) (Nagłówek TCP - 20 bajtów) (Ładunek - X bajty)

mam Parse funkcja, która akceptuje tablicę bajtów i zwraca a TCPHeader obiekt. To wygląda tak:

TCPHeader Parse( byte[] buffer );

Biorąc pod uwagę pierwotną tablicę bajtów, oto sposób, w jaki teraz wywołuję tę funkcję.

byte[] tcpbuffer = new byte[ 20 ];
System.Buffer.BlockCopy( packet, 20, tcpbuffer, 0, 20 );
TCPHeader tcp = Parse( tcpbuffer );

Czy istnieje wygodny sposób przekazania tablicy bajtów TCP, tj. Bajtów 20-39 pełnego pakietu TCP / IP, do Parse funkcja bez wyodrębniania jej do nowej tablicy bajtów?

W C ++ mogłem wykonać następujące czynności:

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

Czy jest coś podobnego w C #? Chcę, jeśli to możliwe, uniknąć tworzenia i późniejszego czyszczenia pamięci tymczasowej tablicy bajtów.


13
2018-01-03 16:00


pochodzenie


Oszczędzaj czas / wysiłek i korzystaj z istniejącej struktury przechwytywania / analizowania sieci, takiej jak SharpPcap lub Pcap.Net. Zapisywanie parsera nagłówków TCP jest podobne do pisania skryptu perl, który analizuje HTML. Istnieje wiele różnych stylów tego koła, które można znaleźć na wolności. - Evan Plaice
Możliwy duplikat: stackoverflow.com/questions/1055923/... - Jon


Odpowiedzi:


Powszechną praktyką, którą można zobaczyć w środowisku .NET i którą zalecam tutaj, jest określenie przesunięcia i długości. Zatem spraw, aby funkcja Parse również akceptowała przesunięcie w podanej macierzy i liczbę elementów do użycia.

Oczywiście obowiązują te same reguły, co w przypadku przekazywania wskaźnika, jak w C ++ - tablica nie powinna być modyfikowana, ponieważ może to spowodować niezdefiniowane zachowanie, jeśli nie jesteś pewien, kiedy dokładnie dane będą używane. Ale to nie jest problem, jeśli nie zamierzasz modyfikować tablicy.


24
2018-01-03 16:05



Chociaż to rozwiązuje problem, pytający powiedział: "Czy istnieje WYGODNA metoda przekazywania tablicy bajtów TCP ...?". Odpowiedź z @casperOne wydaje się lepiej pasować do tego pytania. - DanielCuadra


Chciałbym przejść ArraySegment<byte> w tym przypadku.

Zmieniłbyś swój Parse metoda do tego:

// Changed TCPHeader to TcpHeader to adhere to public naming conventions.
TcpHeader Parse(ArraySegment<byte> buffer)

A potem zmienisz połączenie na to:

// Create the array segment.
ArraySegment<byte> seg = new ArraySegment<byte>(packet, 20, 20);

// Call parse.
TcpHeader header = Parse(seg);

Używając ArraySegment<T> nie skopiuje tablicy, a zrobi to sprawdzając dla ciebie w konstruktorze (tak, że nie określisz niepoprawnych granic). Potem zmieniasz Parse metoda pracy z granicami określonymi w segmencie i powinieneś być w porządku.

Możesz nawet stworzyć przeciążenie wygody, które będzie akceptować całą tablicę bajtów:

// 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
2018-01-03 16:06



ArraySegment <byte> seg = new ArraySegment <byte> (pakiet, 20, pakiet.Length-1); - gimel
ooops! ArraySegment <byte> b2 = new ArraySegment <byte> (b1, 20, b1.Length-20); - gimel
Ale ... czy to nie tworzy nowej klasy dla GC do zbierania, której pytający chciał uniknąć? - mafu
@mafu OP chciał zapobiec kopiowaniu segmentu tablica bajtów; ArraySegment jest otoczyć tablica, nie wykonuje kopii. Zasadniczo daje widok na tablicę, która nie pozwala na pracę poza tymi granicami. - casperOne
Ciekawi Cię, dlaczego ta odpowiedź nie jest preferowana. Czy w porównaniu z odpowiedzią powyżej występują implikacje dotyczące wydajności? - Jon


Jeżeli IEnumerable<byte> jest dopuszczalne jako dane wejściowe, a nie byte[]i używasz C # 3.0, wtedy możesz napisać:

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

Zauważ, że to nadal alokuje instancje modułu wyliczającego pod okładki, więc nie unikniesz całkowicie alokacji, a więc dla małej liczby bajtów może to być wolniejsze niż przydzielanie nowej tablicy i kopiowanie do niej bajtów.

Nie martwiłbym się zbytnio o alokację i GC małych tymczasowych tablic, ale szczerze mówiąc. Środowisko zebrane w środowisku .NET jest niezwykle wydajne w tego rodzaju schemacie alokacji, szczególnie jeśli tablice są krótkotrwałe, więc jeśli nie profilowałeś go i nie znalazłeś problemu z GC, zapisałbym go w najbardziej intuicyjny sposób i napraw problemy z wydajnością, gdy wiesz, że je masz.


4
2018-01-03 16:05



Dzięki, Greg. Prawdę mówiąc, nie sprofilowałem tego. Ale zdrowy rozsądek mówi, że przydzielenie nowej tablicy i skopiowanie 20 bajtów jest mniej wydajne niż zwykłe użycie tablicy. Biorąc pod uwagę liczbę pakietów, muszę być tak skuteczny, jak to tylko możliwe. Poza tym wygląda "schludniej" bez przydziału i kopii. - Matt Davis
Kopia tablicy wykonana w pytaniu jest szybsza niż użycie Linq do tego celu. To i tak nie rozwiązuje problemu tworzenia kopii tablicy. - mafu
Jednak całkowicie zgadzam się, że małe kopie w tym formacie raczej nie spowodują problemów. W końcu pakiety TCP mają względnie ograniczony rozmiar i ilość. Miałem problemy tylko z niewielkimi alokacjami tablicy w programie, który dosłownie stworzył miliardy kopii małej tablicy, ale jeśli nie chodzi o rejestrator TCP u dostawcy Internetu, podejrzewam, że tak nie jest. - mafu


Jeśli naprawdę potrzebujesz takiej kontroli, musisz na nią spojrzeć unsafe funkcja C #. To pozwala mieć wskaźnik i przypiąć go tak, aby GC go nie przesunął:

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

Jednak ta praktyka nie jest zalecana do pracy z kodem zarządzanym tylko, jeśli nie występują problemy z wydajnością. Możesz przekazać przesunięcie i długość, jak w Stream klasa.


3
2018-01-03 16:06





Jeśli możesz zmienić metodę parse (), zmień ją, aby zaakceptować offset, od którego powinno rozpocząć się przetwarzanie. TCPHeader Parse (bufor byte [], int offset);


2
2018-01-03 16:11





Możesz użyć LINQ, aby zrobić coś takiego:

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

Ale System.Buffer.BlockCopy / System.Array.Copy są prawdopodobnie bardziej wydajne.


1
2018-01-03 16:09





Tak to rozwiązałem, będąc z programisty c do programisty c #. Chciałbym użyć MemoryStream, aby przekonwertować go do strumienia, a następnie BinaryReader, aby rozbić binarny blok danych. Musiał dodać dwie funkcje pomocnicze do konwersji z porządku sieciowego na małego endiana. Również do budowania bajt [] do wysłania patrz Czy istnieje sposób na przywrócenie obiektu oryginalnemu typowi bez określania każdego przypadku? który ma funkcję, która pozwala na konwersję z tablicy obiektów na bajt [].

  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
2018-01-03 18:16



Jest to z pewnością prosty, elegancki sposób na zrobienie tego. Zdefiniowałem klasy dla nagłówków IP, TCP i UDP. Wewnętrznie, używam funkcji BitConverter, aby wyodrębnić wartości i IPAddress.NetworkToHostOrder, aby zamienić bajty. Mogę uruchomić kilka testów, aby zobaczyć, które podejście jest bardziej wydajne. - Matt Davis
możesz na to patrzeć stackoverflow.com/questions/2871 jeśli wydajność jest tym, czego oczekujesz i przełączasz się z klas na struktury. Pozostawiłbym wszystko w porządku sieciowym i konwertowałem tylko wtedy, gdy tego potrzebujesz. - Rex Logan


Nie sądzę, możesz zrobić coś takiego w C #. Możesz albo sprawić, że funkcja Parse () użyje offsetu, albo utworzyć tablice 3-bajtowe na początek; jeden dla nagłówka IP, jeden dla nagłówka TCP i jeden dla ładunku.


0
2018-01-03 16:03



Lepszym rozwiązaniem IMO byłoby użycie ArraySegment <T>, który sprawdza granice dla ciebie, dzięki czemu nie musisz go duplikować wszędzie. - casperOne


Nie ma możliwości użycia sprawdzalnego kodu. Jeśli twoja metoda Parse może poradzić sobie z IEnumerable <byte>, możesz użyć wyrażenia LINQ

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

0
2018-01-03 16:06





Niektórzy ludzie, którzy odpowiedzieli

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

zrobiło to źle. To jest doskonały rozwiązanie, ale kod powinien wyglądać tak:

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

Powinieneś używać metod Pomiń i Przenieś na swojej głównej paczka, i tcpbuffer nie powinno istnieć w opublikowanym przez ciebie kodzie. Również nie musisz wtedy używać System.Buffer.BlockCopy.

JaredPar było prawie poprawne, ale zapomniał o metodzie Take

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

Ale nie mylił się tcpbuffer. Twoja ostatnia linia Twojego opublikowanego kodu powinna wyglądać następująco:

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

Ale jeśli chcesz użyć System.Buffer.BlockCopy mimo to zamiast tego Pomiń i weź, ponieważ może jest to lepsze pod względem wydajności, ponieważ Steven Robbins odpowiedział: "Ale System.Buffer.BlockCopy / System.Array.Copy są prawdopodobnie bardziej wydajne" lub Funkcja analizy nie może radzić sobie z IEnumerable<byte>lub jesteś bardziej przyzwyczajony do System.Buffer.Block w swoim zaksięgowanym pytaniu, to polecam po prostu zrób tcpbuffer nie lokalny zmienna, ale prywatny lub chroniony lub publiczny lub wewnętrzny i statyczny albo nie pole (innymi słowy powinna być zdefiniowana i stworzona na zewnątrz metoda, w której wykonywany jest opublikowany kod). W związku z tym tcpbuffer zostanie utworzony tylko pewnego razu, a jego wartości (bajty) będą ustawiane za każdym razem, gdy przekażesz kod, który opublikowałeś w wierszu System.Buffer.BlockCopy.

W ten sposób Twój kod może wyglądać tak:

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.
}

lub po prostu niezbędna część:

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 );
...
}

Jeśli tak:

private byte[] tcpbuffer;

zamiast tego musisz na swoim konstruktorze / s dodać linię:

this.tcpbuffer = new byte[20];

lub

tcpbuffer = new byte[20];

Wiesz, że nie musisz pisać to. przed tcpbufferem jest opcjonalne, ale jeśli zdefiniowałeś go jako statyczny, to ty nie może Zrób to. Zamiast tego musisz wpisać nazwę klasy, a następnie kropkę "." Lub ją opuścić (wpisz tylko nazwę pola i to wszystko).


0
2018-04-28 16:17