Pakiety sieciowe

Problemy, które należy rozwiązać


Wymiana danych w sieci potrafi być bardziej skomplikowana niż wydaje się z z początku. Problemem jest zróżnicowanie urządzeń, które wymieniają pomiędzy sobą dane: różne systemy operacyjne, procesory powodują kilka problemów gdy chcemy w rzetelny sposób wymieniać pomiędzy nimi dane.

Pierwszym problemem jest kolejność bajtów (endianness). Jest to porządek wg którego procesor interpretuje prymitywne typy, które używają więcej niż 1 bajt(liczby całkowite i zmiennoprzecinkowe). Istnieją 2 grupy: „big-endian” gdzie bardziej znaczące bajty są przechowywane na początku oraz „little-endian” gdzie bardziej znaczące bajty są przechowywane na końcu.
Problem tutaj jest raczej dość oczywisty: jeżeli wysyłam dane pomiędzy dwoma komputerami, których endiany do siebie nie pasują to dane zostaną błędnie zinterpretowane. Np. 16-bitowa liczba ’42’ w „big-endian” wygląda tak: 00000000 00101010, „little-endian” zinterpretuje to jako liczbę ‚10752’.

Drugim problemem jest wielkość typów prymitywnych (char, short, int, long, float, double) w C++, które nie są ustawione odgórnie i ich rozmiar zależy od procesora. Np. long int może być liczbą 32-bitową na jednych procesorach, a na innych może być 64-bitową.

Trzecim problemem jest sposób działania TCP. Z racji, że dane w TCP mogą być dzielone na części to odbiorca musi odebrane dane jeszcze zrekonstruować przed zinterpretowaniem ich, w przeciwnym razie mogą powstać błędy bo nie odebrano np. całej zmiennej.

Oczywiście pewnie spotkasz się także z innymi problemami, jednak te są najbardziej podstawowe. SFML posiada narzędzia do uniknięcia tych problemów i ich omówieniem właśnie się zajmiemy.

 

Typy o stałych rozmiarach


Ponieważ typy prymitywne mają różne rozmiary na różnych komputerach to rozwiązanie tego problemu jest proste: nie należy ich używać. SFML posiada odpowiedniki tych zmiennych, które są o stałym rozmiarze: sf::Int8, sf::Uint16, sf::Int32, itp. A więc powinieneś ich używać aby w bezpieczny sposób przesyłać dane.

SFML posiada odpowiedniki do liczb całkowitych. W zasadzie w SFML powinny się też znaleźć dla liczb zmiennoprzecinkowych, ale w praktyce nie są potrzebne (przynajmniej na platformach na których działa SFML). Typy float i double posiadają zawsze ten sam rozmiar (odpowiednio 32 i 64 bity).

 

Pakiety


Dwa kolejne problemy dotyczyły endiany i dzielenia danych w TCP. Może je rozwiązać za pomocą klasy sf::Packet. Jako bonus zapewnia ona o wiele przyjemniejszy interfejs niż stare dobre tablice bajtów.

Użycie pakietów jest analogiczne do innych standardowych strumieni. Możesz „włożyć dane za pomocą operatora <<, oraz „wyjąć” za pomocą >>.

W przeciwieństwie do zapisywania danych, odczytywanie ich z pakietów może zakończyć się niepowodzeniem jeżeli próbujemy wydobyć więcej bajtów niż pakiet ich zawiera. Jeżeli nie uda się odczytać danych to status pakietów jest przestawiany na błąd. Aby sprawdzić status możesz użyć if‚a tak jakbyś użył normalny typ bool (tak samo jak w standardowych strumieniach danych).

Wysyłanie i odbieranie danych jest bardzo proste, socket’y posiadają przeładowaną funkcję send oraz receive dla klasy sf::Packet.

Pakiety rozwiązują także problemy z dzieleniem danych na mniejsze części, ponieważ gdy otrzymasz pakiet to otrzymasz go w całości, dokładnie taki sam jaki został wysłany.

 

Wykorzystanie pakietów we własnych klasach


Pakiety posiadają przeładowane operatory dla wszystkich standardowych typów, jednak co jeżeli chcę wysłać własną klasę? W tym wypadku wystarczy przeładować operatory wewnątrz swojej klasy.

Teraz już możesz wysłać/odebrać swoją klasę Character za pomocą pakietów.

 

Niestandardowe pakiety


Pakiety posiadają kilka fajnych cech dla surowych danych, jednak może się zdarzyć że będziesz chciał dodać kilka własnych (np. kompresję/dekompresję danych przy ich wysyłaniu/odbieraniu). Oczywiście możesz to zrobić przez dziedziczenie po klasie sf::Packet oraz po napisaniu metod:

  • onSend: wywoływane przed wysłaniem danych przez socket
  • onReceive: wywoływane po odebraniu danych przez socket

Ten funkcje dają ci bezpośredni dostęp do danych, które możesz przekształcać w dowolny sposób wg własnych potrzeb.

Poniżej masz przykład automatycznej kompresji i dekompresji danych w pakietach (oczywiście przykład jest niedziałający, jest to bardziej pseudo-kod).

Teraz możemy użyć naszej klasy dokładnie w ten sam sposób jak każdy inny sf::Packet z tą różnicą, że automatycznie odbędzie się kompresja i dekompresja danych.

Oryginalny artykuł