Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?
Autor: Brian 'Beej' Hall <beej@piratehaven.org>
Tłumaczenie: Bartosz Zapałowski <bartek@klepisko.eu.org>
Biblioteki C++

Struktury i przetwarzanie danych

[lekcja] Rozdział 3. Bezpieczna konwersja transmitowanych danych i adresy IP.
No, w końcu tu jesteśmy. Pora, żebym powiedział trochę o programowaniu. W tej sekcji odryję przed Tobą różne typy danych używanych przez interfejsy gniazd, ponieważ niektóre z nim są bardzo trudne do rozszyfrowania.

Na początek coś łatwego: deskryptor gniazda. Deskryptor gniazda jest następującego typu:
C/C++
int
Po prostu normalny int.

Teraz zaczną się pojawiać dziwne rzeczy, więc przeczytaj to i zgódź się ze mną. Pamiętaj: są dwa sposoby reprezentacji bajtu: na początku najbardziej znaczący bit (czasami zwany oktetem) lub najmniej znaczący bit na początku. Ta pierwsza reprezentacja jest nazywana sieciową kolejnością bajtów, a druga kolejnością bajtów hosta. Niektóre maszyny przechowują liczby używają sieciowej kolejności bajtów, niektóre nie. Kiedy mówię, że coś ma być w sieciowej kolejności bajtów, wtedy musisz wywołać specjalną funkcję (np. htons()), aby zamienić liczbę z kolejności bajtów hosta. Jeśli nie powiem, że liczba musi być w sieciowej kolejności bajtów, wtedy musisz zostawić ją w kolejności bajtów hosta.

(Dla ciekawych, "Network Byte Order" jest również znana jako "Big-Endian Byte Order".)

Moja Pierwsza StrukturaTM -- struct sockaddr. Ta struktura przechowuje informacje o adresie gniazda dla różnych typów gniazd:
C/C++
struct sockaddr {
    unsigned short sa_family; // rodzina adresów, AF_xxx
    char sa_data[ 14 ]; // 14-bajtowy adres protokołowy
};
 
sa_family może być różnymi rzeczami, ale w tym dokumencie będzie tylko AF_INET. sa_data przechowuje docelowy adres i numer portu dla gniazda. Jednak nie będziesz musiał pakować ręcznie adres w sa_data.

Żeby się uporać z struct sockaddr programiści stworzyli równorzędną strukturę: struct sockaddr_in ("in" jak Internet).

C/C++
struct sockaddr_in {
    short int sin_family; // rodzina adresów
    unsigned short int sin_port; // numer portu
    struct in_addr sin_addr; // adres internetowy
    unsigned char sin_zero[ 8 ]; // dla zachowania rozmiaru struct sockaddr
};
 
Ta struktura ułatwia dostęp do elementów adresu gniazda. Zauważ, że sin_zero(które jest dołączone do struktury dla wyrównania wielkości struktury z struct sockaddr) powinno być ustawione na zero (wszystkie elementy) przez funkcję memset(). Również ważną informacją jest to, że wskaźnik na struct sockaddr_in może być zrzutowany na struct sockaddr i vice-versa. Więc mimo, że socket() chce struct sockaddr*, możesz używać struct sockaddr_in i rzutować to w ostatniem chwili! Zauważ też, że sin_family odpowiada sa_family w struct sockaddr i powinno być ustawione na "AF_INET". W końcu sin_port i sin_addr muszą być w sieciowej kolejności bajtów!

"Ale," możesz się zastanawiać, "jak cała struktura, struct in_addr sin_addr, może być w Network Byte Order?". To pytanie wymaga uważnenego przyjrzenia się strukturze struct in_addr:
C/C++
// adres internetowy (struktura z przyczyn historycznych)
struct in_addr {
    unsigned long s_addr; // to ma rozmiar 32 bitów, lub 4 bajtów
};
 
Kiedyś to było unią (union), ale teraz już się tego nie spotyka. Dobre posunięcie. Więc jeśli zadeklarowałeś ina jako struct sockaddr_in, wtedy na.sin_addr.s_addr odpowiada 4 bajtowemu adresowi IP (w sieciowej kolejności bajtów). Zauważ, że nawet gdy twój system nadal używa okropnej unii (union) dla struct in_addr nadal możesz się odwoływać do 4 bajtowego adresu IP w identyczny sposób jaki czyniliśmy wcześniej (a to z podowu #defineów).

Zmiana reprezentacji bajtów

W końcu trafiliśmy do następnej sekcji. Do tej pory było dużo gadania o konwersji z Network na Host Byte Order -- teraz przyszedł czas na akcję!

Jołkej. Są dwa typy, które możesz konwertować: short (dwa bajty) i long (cztery bajty). Poniżesze funkcje działają równie dobrze dla wariacji unsigned. Powiedzmy, że chcesz skonwertować short z kolejności bajtów hosta na sieciową kolejność bajtów. Zacznij z "h" dla "Host", następnie "to", w końcu "n" dla "Network" i "s" dla "short": h-to-n-s, lub htons() (czyt. "Host to Network Short").

Jest to prawie za łatwe...

Możesz używać każdej kombinacji "n", "h", "s" i "l" , jakiej tylko chcesz (oprócz tych głupich). Na przykład, nie ma funkcji stolh() ("Short to Long Host"), ale jest:

  • htons() -- "Host to Network Short"
  • htonl() -- "Host to Network Long"
  • ntohs() -- "Network to Host Short"
  • ntohl() -- "Network to Host Long"

Now, you may think you're wising up to this. Pewnie myślisz "Co mam zrobić, jeśli muszę zmienić kolejność bajtów w char?". Wtedy możesz sobie pomyśleć "Eee, nie ważne". Możesz również pomyśleć, że skoro twoja maszyna 68000 już używa Network Byte Order, nie musisz wywoływać htonl() na adresach IP. Miałbyś rację. ALE jeśli spróbujesz otworzyć port na maszynie, która używa odwrotnej kolejności niż Network Byte Order, twój program nie zadziała poprawnie. Pisz przenośnie! To jest świat Unix! (tak bardzo jak Bill Gates chciałby, żeby było odwrotne). Pamiętaj: Spraw, by twoje bajty były w Network Byte Order zanim umieścisz je w sieci.

Ostatni punkt: dlaczego sin_addr oraz sin_port muszą być w Network Byte Order w strukturze struct sockaddr_in, podczas gdy sin_family nie? Odpowiedź: sin_addr oraz sin_port są kapsułkowane w pakiecie w warstwach odpowiednio IP i UDP. Stąd muszą być w Network Byte Order. Jakkolwiek, pole sin_family jest używane tylko przez jądro do określenia jaki typ adresu przechowuje struktura, więc musi być w Host Byte Order. Również, ponieważ sin_family nie jest wysyłane do sieci, może być w Host Byte Order.

Adresy IP oraz jak sobie z nimi radzić

Na szczęście jest kilka funkcji, która pozwalają na manipulowanie adresami IP. Nie musisz ich rozpracowywać i składować w long za pomocą operatora <<.

Na początek powiedzmy, że mamy strukturę struct sockaddr_in ina oraz adres IP "10.12.110.57", który chcesz przechowywać w niej. Funkcją, której chcesz użyć jest inet_addr() (od tłumacza: ta funkcja jest przestarzała, użyj inet_pton()), która zamienia adres IP na notację numerowo-kropkową i składuje go w unsigned long. Przypisanie adresu IP wygląda tak:

C/C++
ina.sin_addr.s_addr = inet_addr( "10.12.110.57" );
 

Zauważ, że inet_addr() zwraca adres w Network Byte Order -- nie musisz wywoływać htonl(). Fajnie!

Powyższy kawałek kodu nie jest zbyt dobry, ponieważ nie ma tam sprawdzania błędów. Zauważ, że inet_addr() zwraca -1 w przypadku błędu. Pamiętasz liczby binarne? (unsigned)-1 dziwnym trafem odpowiada adresowi IP 255.255.255.255! To jest adres rozgłoszeniowy! Niedobrze. Pamiętaj by sprawdzać błędy poprawnie.

Właściwie, to jest lepszy interface, którego możesz użyć zamiast inet_addr(): jest to inet_aton() ("aton" oznacza "ascii to network" (zamień ascii na adres sieciowy)):

C/C++
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int inet_aton( const char * cp, struct in_addr * inp );

Poniżej jest przykładowe użycia wstawiania danych do struct sockaddr_in (ten przykład będzie dla ciebie bardziej sensowny, gdy dojdziesz do sekcji o bind() oraz connect()).

C/C++
struct sockaddr_in my_addr;

my_addr.sin_family = AF_INET; // host byte order
my_addr.sin_port = htons( MYPORT ); // short, network byte order
inet_aton( "10.12.110.57", &( my_addr.sin_addr ) );
memset( &( my_addr.sin_zero ), '\0', 8 ); // wyzeruj resztę struktury

inet_aton(), w przeciwieństwie do praktycznie każdej funkcji operującej na gniazdach, zwraca wartość niezerową w przypadku powodzenia oraz zero w przypadku błędu. (Jeśli ktoś wie dlaczego, proszę niech mi powie.) Adres IP jest umieszczany w zmiennej inp.

Niestety, nie wszystkie platformy dostarczają inet_aton() dlatego, mimo, że jest ona preferowana, starsza i częściej używana funkcje inet_addr() jest używana w tym przewodniku.

Tak więc, teraz umiesz zamieniać adres IP zapisane jako tekst w jego binarny odpowiednik. Co powiesz na proces odwrotny? Co, jeśli miałbyś struct in_addr i chciałbyś wyświetlić to w formacie liczb rozdzielonych kropkami? W tym przypadku użyłbyś funkcji inet_ntoa() ("ntoa" oznacza "network to ascii" - zamień adres sieciowy na ciąg znaków ascii) (od tłumacza: zamiast inet_ntoa() polecam inet_ntop()) tak jak tu:

C/C++
printf( "%s", inet_ntoa( ina.sin_addr ) );

Powyższe wyświetli adres IP. Zauważ, że inet_ntoa() przyjmuje struct in_addr jako swój argument, a nie long. Zauważ również, że funkcja ta zwraca wskaźnik do typu char. Ten sposób działania funkcji sprawia, że wynik jej działania jest umieszczany w statycznie alokowanym buforze w obrębie funkcji inet_ntoa() , tak więc za każdym wywołaniem funkcji inet_ntoa() nadpisze ona ostani adres IP, o który pytałeś. Przykład:

C/C++
char * a1, * a2;
//...
a1 = inet_ntoa( ina1.sin_addr ); // tu jest 192.168.4.14
a2 = inet_ntoa( ina2.sin_addr ); // tu jest 10.12.110.57
printf( "address 1: %s\n", a1 );
printf( "address 2: %s\n", a2 );
 
Powyższe da wynik:

    address 1: 10.12.110.57
    address 2: 10.12.110.57

Jeśli musisz zachować adres, użyj strcpy(), by wynik skopiować do własnego bufora.

To tyle na ten temat póki co. Dalej nauczysz się jak zamieniać tekst, taki jak "whitehouse.gov" na jego odpowiedni adres IP (patrz DNS, poniżej.)
Poprzedni dokument Następny dokument
Co to jest gniazdo? Wywołania systemowe