Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja Ostatnia wersja Both sides next revision | ||
sk2:sockets_netdbs [2015/10/05 12:05] jkonczak |
sk2:sockets_netdbs [2023/10/17 13:59] jkonczak [getaddrinfo] |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
- | ====== Zmiana adresu domenowego na IP i z powrotem ====== | + | ====== Zmiana adresu domenowego na IP ====== |
- | ===== Tradycyjna funkcja ''gethostbyname'' ===== | + | ===== Tradycyjna funkcja gethostbyname ===== |
Najstarsze funkcje do zmiany to ''gethostbyname'' i ''gethostbyaddr''. | Najstarsze funkcje do zmiany to ''gethostbyname'' i ''gethostbyaddr''. | ||
Funkcje są przestarzałe i powszechnie używane. Problemy z ich działaniem obrazuje poniższy kod: | Funkcje są przestarzałe i powszechnie używane. Problemy z ich działaniem obrazuje poniższy kod: | ||
<code cpp ghbn.cpp> | <code cpp ghbn.cpp> | ||
- | #include <iostream> | ||
- | #include <netdb.h> | ||
- | #include <arpa/inet.h> | ||
- | #include <thread> | ||
- | #include <atomic> | ||
#include <unistd.h> | #include <unistd.h> | ||
+ | #include <thread> | ||
+ | #include <arpa/inet.h> | ||
+ | #include <netdb.h> | ||
+ | #include <cstdio> | ||
- | using namespace std; | + | int main() { |
- | + | ||
- | void print(hostent* ret){ | + | std::thread t1([]{ |
- | cout << ret->h_name << endl; | + | sleep(1); |
- | for(auto it = ret->h_addr_list; *it; ++it){ | + | gethostbyname("spam.org"); |
- | cout << " " << inet_ntoa(*((in_addr*)*it)) << endl; | + | }); |
- | } | + | |
+ | std::thread t2([]{ | ||
+ | hostent* ret = gethostbyname("cat.put.poznan.pl"); | ||
+ | sleep(2); | ||
+ | printf("%s: %s\n", ret->h_name, inet_ntoa(**(in_addr**)ret->h_addr_list)); | ||
+ | }); | ||
+ | |||
+ | t1.join(); | ||
+ | t2.join(); | ||
+ | return 0; | ||
} | } | ||
+ | </code> | ||
- | int main(int argc, char **argv) { | + | //Zadanie 1.// Przetestuj powyższy kod. Zastanów się jak uniknąć problemu, który pokazuje powyższy kod. |
- | atomic<bool> wait1 {true}, wait2 {true}; | + | |
- | + | ===== Rozszerzenia GNU ===== | |
- | std::thread | + | |
- | t1([&]{ | + | <html><small></html> |
- | while(wait1.load()); | + | Kompilator GCC udostępnia wiele rozszerzeń "łatających" braki standardów [[https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html]].\\ |
- | gethostbyname("spam.org"); | + | 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. |
- | wait2.store(false); | + | |
- | sleep(1); | + | <code cpp> |
- | }), | + | hostent he, *resptr; |
- | t2([&]{ | + | int status, retval; |
- | hostent* ret = gethostbyname("fc.put.poznan.pl"); | + | char buffer[4096]; |
- | wait1.store(false); | + | retval = gethostbyname_r("cat.put.poznan.pl", &he, buffer, sizeof(buffer), &resptr, &status); |
- | while(wait2.load()); | + | if(retval) |
- | print(ret); | + | error(1,0,"gethostbyname_r error: %s", hstrerror(status)); |
- | }); | + | if(!resptr) |
- | + | error(1,0,"empty result"); | |
- | t1.join(); | + | print(resptr);</code> |
- | t2.join(); | + | <html></small></html> |
- | return 0; | + | |
- | } | + | ===== 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> | ||
+ | Składnia: <code cpp> | ||
+ | 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 * port_or_service, // numer portu (np. "80") lub nazwa usługi (np. "http", ustawi port na 80) | ||
+ | const struct addrinfo *hints, // pozwala wybrać typ adresu, wyłączyć używanie DNS, etc. | ||
+ | struct addrinfo **res); // pole na wynik (jeśli puste - nic nie znaleziono) | ||
</code> | </code> | ||
+ | |||
+ | Definicja struktury ''addrinfo'':<code cpp> | ||
+ | struct addrinfo { | ||
+ | int ai_flags; | ||
+ | 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_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 | ||
+ | char *ai_canonname; | ||
+ | struct addrinfo *ai_next; // struktura addrinfo tworzy jednokierunkową linked listę, ai_next wskazuje na jej następny element | ||
+ | };</code><html></small></html> | ||
+ | |||
+ | Pełen opis funkcji ''getaddrinfo'' i struktury ''addrinfo'' znajdziesz na stronie manuala do getaddrinfo (''man getaddrinfo'') <html><small></html> 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]]])<html></small></html>. | ||
+ | \\ | ||
+ | Uwaga: ''getaddrinfo'' w razie sukcesu zwraca ''0'', a w przypadku błędu zwraca jego kod. | ||
+ | |||
+ | Przykłady użycia: | ||
+ | <code cpp gai1.cpp> | ||
+ | #include <netdb.h> | ||
+ | #include <cstdio> | ||
+ | #include <error.h> // niestandardowa funkcja ułatwiająca obsługę błędów (dostępna w glibc) | ||
+ | #include <arpa/inet.h> | ||
+ | |||
+ | int main(){ | ||
+ | // Ustawienie "podpowiedzi" - sterowanie jakie wyniki chcemy otrzymać: | ||
+ | addrinfo hints {}; // uwaga: puste 'list initialization' zeruje całą strukturę | ||
+ | hints.ai_family = AF_INET; // tylko IPv4 (AF_INET) | ||
+ | hints.ai_protocol = IPPROTO_UDP; // protokół UDP | ||
+ | | ||
+ | // Zmienna w której będzie umieszczona lokalizacja wyniku w pamięci | ||
+ | addrinfo * resolved; | ||
+ | | ||
+ | // Z sieci bezprzewodowej PP 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); | ||
+ | | ||
+ | if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res)); | ||
+ | if(!resolved) error(1,0,"Empty result\n"); | ||
+ | | ||
+ | for(addrinfo * it = resolved; it; it=it->ai_next){ | ||
+ | sockaddr_in* addr = (sockaddr_in*) it->ai_addr; // <- rzutowanie bezpieczne, | ||
+ | printf(" %s\n",inet_ntoa(addr->sin_addr)); // bo w podpowiedziach | ||
+ | } // zażądaliśmy adresów IPv4 | ||
+ | | ||
+ | freeaddrinfo(resolved); | ||
+ | | ||
+ | return 0; | ||
+ | } | ||
+ | </code><code cpp gai2.cpp>#include <netdb.h> | ||
+ | #include <cstdio> | ||
+ | #include <error.h> | ||
+ | |||
+ | int main(){ | ||
+ | addrinfo * resolved; | ||
+ | // Podpowiedzi, port i nazwa hosta są opcjonalne. | ||
+ | int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved); | ||
+ | | ||
+ | if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res)); | ||
+ | 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 ':' | ||
+ | // można użyć stałą NI_MAXHOST określającą maksymalną długość nazwy domenowej | ||
+ | for(addrinfo * it = resolved; it; it=it->ai_next){ | ||
+ | // funkcja getnameinfo tłumaczy dowolny sockaddr na tekst | ||
+ | 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)); | ||
+ | else printf("%40s (socktype: %d, proto: %d)\n", ip, it->ai_socktype, it->ai_protocol); | ||
+ | } | ||
+ | | ||
+ | freeaddrinfo(resolved); | ||
+ | | ||
+ | return 0; | ||
+ | }</code> | ||
+ | |||
+ | //Zadanie 2.// Stwórz klienta TCP, który: | ||
+ | * odczyta z listy argumentów adres i numer portu | ||
+ | * użyje funkcji ''getaddrinfo'' z podpowiedzią żądającą protokołu TCP | ||
+ | * utworzy socket 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'' |