Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
sk2:sockets_netdbs [2020/10/20 12:55] jkonczak [Tradycyjna funkcja gethostbyname] |
sk2:sockets_netdbs [2024/10/18 09:12] (aktualna) jkonczak [getaddrinfo] |
||
---|---|---|---|
Linia 10: | Linia 10: | ||
#include <arpa/inet.h> | #include <arpa/inet.h> | ||
#include <netdb.h> | #include <netdb.h> | ||
+ | #include <cstdio> | ||
int main() { | int main() { | ||
Linia 30: | Linia 31: | ||
</code> | </code> | ||
- | //Zadanie 1.// Przetestuj powyższy kod. Zastanów się jak uniknąć problemu, który pokazuje powyższy kod. | + | ~~Zadanie.#~~ Przetestuj powyższy kod. Zastanów się jak uniknąć problemu, który pokazuje powyższy kod. |
===== Rozszerzenia GNU ===== | ===== Rozszerzenia GNU ===== | ||
- | <html><small></html> | + | <small> |
Kompilator GCC udostępnia wiele rozszerzeń "łatających" braki standardów [[https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html]].\\ | Kompilator GCC udostępnia wiele rozszerzeń "łatających" braki standardów [[https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html]].\\ | ||
- | Towarzysząca mu biblioteka [[https://www.gnu.org/software/libc/|glibc]] wprowadza między innymi funkcję ''gethostbyname_r'' (funkcje ''*_r'' są reentrant, tj. można je łatwiej stosować w aplikacjach wielowątkowych [[https://en.wikipedia.org/wiki/Reentrancy_%28computing%29]]). ''gethostbyname_r'' może być używana współbieżnie. | + | Towarzysząca mu biblioteka [[https://www.gnu.org/software/libc/|glibc]] wprowadza między innymi funkcję ''[[https://www.gnu.org/software/libc/manual/html_node/Host-Names.html#index-gethostbyname_005fr|gethostbyname_r]]'' (funkcje ''*_r'' są reentrant, tj. można je łatwiej stosować w aplikacjach wielowątkowych [[https://en.wikipedia.org/wiki/Reentrancy_%28computing%29]]). ''gethostbyname_r'' może być używana współbieżnie. |
<code cpp> | <code cpp> | ||
Linia 42: | Linia 43: | ||
int status, retval; | int status, retval; | ||
char buffer[4096]; | char buffer[4096]; | ||
- | retval = gethostbyname_r("fc.put.poznan.pl", &he, buffer, sizeof(buffer), &resptr, &status); | + | retval = gethostbyname_r("cat.put.poznan.pl", &he, buffer, sizeof(buffer), &resptr, &status); |
if(retval) | if(retval) | ||
error(1,0,"gethostbyname_r error: %s", hstrerror(status)); | error(1,0,"gethostbyname_r error: %s", hstrerror(status)); | ||
Linia 48: | Linia 49: | ||
error(1,0,"empty result"); | error(1,0,"empty result"); | ||
print(resptr);</code> | print(resptr);</code> | ||
- | <html></small></html> | + | </small> |
===== getaddrinfo ===== | ===== getaddrinfo ===== | ||
- | Standard POSIX wprowadza funkcje ''getaddrinfo'', która pozwala na bezpieczne tłumaczenie nazwy domenowej na adresy IP (zarówno IPv4 i IPv6). Funkcja od razu tworzy gotową strukturę ''sockaddr''. Wynik musi być zwolniony przez programistę funkcją ''freeaddrinfo''. Poza ''getaddrinfo'' standard wprowadza też m. inn. funkcję do tłumaczenia ''sockaddr*'' na tekst – ''getnameinfo''. | ||
- | <html><small></html> | + | Standard POSIX wprowadza funkcję ''getaddrinfo'', która pozwala na bezpieczne |
- | Składnia: <code cpp> | + | tłumaczenie nazwy domenowej (lub adresu IP) z tekstu na odpowiedni format dla |
+ | funkcji sieciowych (wskaźnik na strukturę ''sockaddr''). Funkcja działa zarówno | ||
+ | IPv4 i IPv6.\\ | ||
+ | ''getaddrinfo'' przygotowuje wszystkie argumenty potrzebne funkcjom takim jak | ||
+ | ''socket'', ''connect'' czy ''bind''. | ||
+ | |||
+ | Funkcja ''getaddrinfo'' sama alokuje pamięć dla wyników, stąd wyniki muszą być | ||
+ | zwolnione ręcznie funkcją ''freeaddrinfo''. | ||
+ | |||
+ | Standard POSIX wprowadza też m. inn. wygodną funkcję do tłumaczenia ''sockaddr*'' | ||
+ | na tekst – funkcję ''getnameinfo''. | ||
+ | |||
+ | <small> | ||
+ | Składnia ''getaddrinfo'': | ||
+ | <html><div style=margin-top:-1.2em></div></html> | ||
+ | <code cpp> | ||
int getaddrinfo( // zwraca 0 (ok) lub niezerowy kod błędu (można go przerobić na tekst używając gai_strerror) | int getaddrinfo( // zwraca 0 (ok) lub niezerowy kod błędu (można go przerobić na tekst używając gai_strerror) | ||
const char * ip_or_hostname, // nazwa domenowa lub adres IP | const char * ip_or_hostname, // nazwa domenowa lub adres IP | ||
Linia 62: | Linia 77: | ||
</code> | </code> | ||
- | Definicja struktury ''addrinfo'':<code cpp> | + | Definicja struktury ''addrinfo'': |
+ | <html><div style=margin-top:-1.2em></div></html> | ||
+ | <code cpp> | ||
struct addrinfo { | struct addrinfo { | ||
- | int ai_flags; | + | int ai_flags; // pole flag, używane jeśli struktura ma być podpowiedziami dla getaddrinfo – patrz manual |
+ | | ||
int ai_family; // ai_family, ai_socktype i ai_protocol mają znaczenie identyczne jak | int ai_family; // ai_family, ai_socktype i ai_protocol mają znaczenie identyczne jak | ||
int ai_socktype; // w funkcji socket(). Mogą być ustawione w podpowiedziach na wybraną | int ai_socktype; // w funkcji socket(). Mogą być ustawione w podpowiedziach na wybraną | ||
int ai_protocol; // wartość lub na 0 (wtedy oznaczają wszystkie pasujące wartości) | int ai_protocol; // wartość lub na 0 (wtedy oznaczają wszystkie pasujące wartości) | ||
- | socklen_t ai_addrlen; // ai_addr to adres o długości ai_addrlen który może być | + | |
- | struct sockaddr *ai_addr; // użyty w funkcji connect lub bind | + | socklen_t ai_addrlen; // ai_addr to adres o długości ai_addrlen który może być |
- | char *ai_canonname; | + | struct sockaddr *ai_addr; // użyty w funkcji connect lub bind |
+ | | ||
+ | char *ai_canonname; // wypełniane (tylko jeśli podano odpowiednią flagę) nazwą o którą zapytano | ||
+ | | ||
struct addrinfo *ai_next; // struktura addrinfo tworzy jednokierunkową linked listę, ai_next wskazuje na jej następny element | struct addrinfo *ai_next; // struktura addrinfo tworzy jednokierunkową linked listę, ai_next wskazuje na jej następny element | ||
- | };</code><html></small></html> | + | };</code> |
- | Pełen opis funkcji ''getaddrinfo'' i struktury ''addrinfo'' znajdziesz na stronie manuala do getaddrinfo (''man getaddrinfo''). | + | Składnia ''getnameinfo'': |
+ | <html><div style=margin-top:-1.2em></div></html> | ||
+ | <code cpp> | ||
+ | int getnameinfo( // zwraca 0 (ok) lub niezerowy kod błędu (można go przerobić na tekst używając gai_strerror) | ||
+ | const struct sockaddr *addr, // adres (IPv4 lub IPv6) do przerobienia na tekst | ||
+ | socklen_t addrlen, // ilość bajtów powyższego adresu | ||
+ | // | ||
+ | char * host, // wskaźnik na bufor do którego zostanie wpisany adres IP (lub nazwa domenowa) jako tekst | ||
+ | size_t hostLen, // ilość miejsca w powyższym buforze | ||
+ | // (dla zmiany IP na tekst powinien wystarczyć bufor o długości NI_MAXHOST) | ||
+ | char * port, // wskaźnik na bufor do którego zostanie wpisany numer (lub nazwa) portu jako tekst | ||
+ | size_t portLen, // ilość miejsca w powyższym buforze | ||
+ | // (dla zmiany portu na tekst powinien wystarczyć bufor o długości NI_MAXSERV) | ||
+ | int flags); // pole flag, pozwalające wybrać m. inn. czy IP i port mają być tłumaczone na numery czy nazwy | ||
+ | </code> | ||
+ | </small> | ||
+ | |||
+ | Pełen opis funkcji i struktur znajdziesz na stronie manuala do getaddrinfo (''man getaddrinfo'') | ||
+ | <small> lub w standardzie POSIX ([[https://pubs.opengroup.org/onlinepubs/9699919799/functions/freeaddrinfo.html|[1]]], | ||
+ | [[https://pubs.opengroup.org/onlinepubs/9699919799/functions/getnameinfo.html|[2]]], | ||
+ | [[https://pubs.opengroup.org/onlinepubs/9699919799/functions/gai_strerror.html|[3]]])</small>. | ||
+ | |||
+ | __Uwaga:__ ''getaddrinfo'' i ''getnameinfo'' w razie sukcesu zwraca ''0'', a w | ||
+ | przypadku błędu zwraca jego kod. Czytelny dla człowieka komunikat powiązany z | ||
+ | kodem błędu można uzyskać funckją ''gai_strerror''. | ||
+ | Zwróć uwagę, że to odbiega od typowej konwencji POSIX. | ||
Przykłady użycia: | Przykłady użycia: | ||
Linia 80: | Linia 126: | ||
#include <netdb.h> | #include <netdb.h> | ||
#include <cstdio> | #include <cstdio> | ||
- | #include <error.h> // niestandardowa funkcja ułatwiająca obsługę błędów (dostępna w glibc) | ||
#include <arpa/inet.h> | #include <arpa/inet.h> | ||
Linia 92: | Linia 137: | ||
addrinfo * resolved; | addrinfo * resolved; | ||
| | ||
+ | // Z sieci bezprzewodowej PP (lub innych używających serwerów nazw z podsieci 150.254.5.0/24) | ||
+ | // proszę zmienić "pool.ntp.org" na "onet.pl" - Dział Obsługi i Eksploatacji z niezrozumiałych | ||
+ | // przyczyn cenzuruje odpowiedzi z DNS | ||
int res = getaddrinfo("pool.ntp.org", "ntp", &hints, &resolved); | int res = getaddrinfo("pool.ntp.org", "ntp", &hints, &resolved); | ||
| | ||
- | if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res)); | + | if(res) {fprintf(stderr, "Getaddrinfo failed: %s\n", gai_strerror(res)); return 1;} |
- | if(!resolved) error(1,0,"Empty result\n"); | + | |
| | ||
for(addrinfo * it = resolved; it; it=it->ai_next){ | for(addrinfo * it = resolved; it; it=it->ai_next){ | ||
Linia 106: | Linia 153: | ||
return 0; | return 0; | ||
} | } | ||
- | </code><code cpp gai2.cpp>#include <netdb.h> | + | </code> |
+ | <code cpp gai2.cpp> | ||
+ | #include <netdb.h> | ||
#include <cstdio> | #include <cstdio> | ||
- | #include <error.h> | ||
int main(){ | int main(){ | ||
Linia 115: | Linia 163: | ||
int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved); | int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved); | ||
| | ||
- | if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res)); | + | if(res) {fprintf(stderr, "Getaddrinfo failed: %s\n", gai_strerror(res)); return 1;} |
- | if(!resolved) error(1,0,"Empty result\n"); | + | |
| | ||
char ip[40]; // maks. długość IP(v6) jako tekst: 8 bloków po 4 znaki oddzielone ':' | char ip[40]; // maks. długość IP(v6) jako tekst: 8 bloków po 4 znaki oddzielone ':' | ||
Linia 123: | Linia 170: | ||
// funkcja getnameinfo tłumaczy dowolny sockaddr na tekst | // funkcja getnameinfo tłumaczy dowolny sockaddr na tekst | ||
res = getnameinfo(it->ai_addr, it->ai_addrlen, ip, 40, nullptr, 0, NI_NUMERICHOST); | res = getnameinfo(it->ai_addr, it->ai_addrlen, ip, 40, nullptr, 0, NI_NUMERICHOST); | ||
- | if(res) error(1,0,"Getnameinfo failed: %s\n", gai_strerror(res)); | + | if(res) {fprintf(stderr, "Getnameinfo failed: %s\n", gai_strerror(res)); return 1;} |
else printf("%40s (socktype: %d, proto: %d)\n", ip, it->ai_socktype, it->ai_protocol); | else printf("%40s (socktype: %d, proto: %d)\n", ip, it->ai_socktype, it->ai_protocol); | ||
} | } | ||
Linia 130: | Linia 177: | ||
| | ||
return 0; | return 0; | ||
- | }</code> | + | } |
+ | </code> | ||
- | //Zadanie 2.// Stwórz klienta TCP, który: | + | ~~Zadanie.#~~ Stwórz klienta TCP, który: |
* odczyta z listy argumentów adres i numer portu | * odczyta z listy argumentów adres i numer portu | ||
* użyje funkcji ''getaddrinfo'' z podpowiedzią żądającą protokołu TCP | * użyje funkcji ''getaddrinfo'' z podpowiedzią żądającą protokołu TCP | ||
- | * utworzy socket korzystając z rodziny adresów zwróconej przez ''getaddrinfo'' | + | * utworzy gniazdo korzystając z rodziny adresów zwróconej przez ''getaddrinfo'' |
- | * połączy się funkcją connect korzystając ze struktury sockaddr i informacji o jej długości zwróconej przez ''getaddrinfo'' | + | * połączy się funkcją connect korzystając ze struktury sockaddr i informacji o jej długości zwróconej przez ''getaddrinfo'' |
+ | <html><div style=margin-top:-1.2em></div></html> | ||
+ | W tym zadaniu wszystkie argumenty funkcji ''socket'' i wszystkie poza deskryptorem | ||
+ | gniazda argumenty funkcji ''connect'' mają być brane z wyników funkcji ''getaddrinfo'' | ||
+ | |||
+ | <small> | ||
+ | ~~Zadanie.#~~ | ||
+ | Zmień kod ostatniego zadania z poprzedniego tematu (serwer UDP wysyłający każdą | ||
+ | wiadomość do wszystkich od których wcześniej coś dostał) tak, by serwer w treść | ||
+ | każdej wiadomości wstawiał informację skąd przyszła. Tłumacz adres IP na nazwę | ||
+ | domenową, ale numer portu pozostaw jako liczbę. | ||
+ | </small> |