Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
sk2:sockets_netdbs [2015/10/05 15:05] jkonczak [getaddrinfo] |
sk2:sockets_netdbs [2023/10/17 14:03] (aktualna) jkonczak |
||
---|---|---|---|
Linia 1: | Linia 1: | ||
- | ====== Zmiana adresu domenowego na IP i z powrotem ====== | + | ====== Zmiana adresu domenowego na IP ====== |
===== Tradycyjna funkcja gethostbyname ===== | ===== Tradycyjna funkcja gethostbyname ===== | ||
Linia 6: | Linia 6: | ||
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"); | |
- | int main(int argc, char **argv) { | + | sleep(2); |
- | atomic<bool> wait1 {true}, wait2 {true}; | + | printf("%s: %s\n", ret->h_name, inet_ntoa(**(in_addr**)ret->h_addr_list)); |
- | + | }); | |
- | std::thread | + | |
- | t1([&]{ | + | t1.join(); |
- | while(wait1.load()); | + | t2.join(); |
- | gethostbyname("spam.org"); | + | return 0; |
- | wait2.store(false); | + | |
- | sleep(1); | + | |
- | }), | + | |
- | t2([&]{ | + | |
- | hostent* ret = gethostbyname("fc.put.poznan.pl"); | + | |
- | wait1.store(false); | + | |
- | while(wait2.load()); | + | |
- | print(ret); | + | |
- | }); | + | |
- | + | ||
- | t1.join(); | + | |
- | t2.join(); | + | |
- | return 0; | + | |
} | } | ||
</code> | </code> | ||
+ | |||
+ | //Zadanie 1.// Przetestuj powyższy kod. Zastanów się jak uniknąć problemu, który pokazuje powyższy kod. | ||
===== Rozszerzenia GNU ===== | ===== Rozszerzenia GNU ===== | ||
Linia 49: | Linia 37: | ||
<html><small></html> | <html><small></html> | ||
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]].\\ | ||
- | Między innymi wprowadza funkcje ''gethostbyname_r'' (funkcje ''*_r'' są reentrant, tj. można je bezpiecznie stosować w aplikacjach wielowątkowych [[https://en.wikipedia.org/wiki/Reentrancy_%28computing%29]]) | + | 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> | ||
hostent he, *resptr; | hostent he, *resptr; | ||
- | 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)); |
- | if(!resptr) | + | if(!resptr) |
- | error(1,0,"empty result"); | + | error(1,0,"empty result"); |
- | print(resptr); | + | print(resptr);</code> |
- | </code> | + | |
<html></small></html> | <html></small></html> | ||
===== 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''. | + | 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> | Składnia: <code cpp> | ||
- | int getaddrinfo( // zwraca 0 (ok) lub -1 (błąd) | + | 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 | ||
- | const char * port_or_service, // numer portu (np. "80" lub nazwa usługi (np. "http", ustawi port na 80) | + | 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. | 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) | struct addrinfo **res); // pole na wynik (jeśli puste - nic nie znaleziono) | ||
</code> | </code> | ||
- | Przykład użycia: | + | |
- | <code cpp> | + | 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 <netdb.h> | ||
- | (...) | + | #include <cstdio> |
- | // // znajdzie dowolne adresy, w tym IPv6, nie ustawi portu | + | #include <error.h> // niestandardowa funkcja ułatwiająca obsługę błędów (dostępna w glibc) |
- | // addrinfo * aio; | + | #include <arpa/inet.h> |
- | // int res = getaddrinfo("fc.put.poznan.pl", 0, 0, &aio); | + | |
- | // znajdzie tylko adresy IPv4, ustawi port 13 | + | int main(){ |
- | addrinfo * aio, aih {.ai_flags=0, .ai_family=AF_INET, .ai_socktype=SOCK_STREAM}; | + | // Ustawienie "podpowiedzi" - sterowanie jakie wyniki chcemy otrzymać: |
- | int res = getaddrinfo("fc.put.poznan.pl", "13", &aih, &aio); | + | 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 (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); | ||
+ | |||
+ | 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> | ||
- | if(res) | + | int main(){ |
- | error(1, errno, "getaddrinfo failed"); | + | addrinfo * resolved; |
- | if(!aio) | + | // Podpowiedzi, port i nazwa hosta są opcjonalne. |
- | error(1, 0, "empty result"); | + | int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved); |
- | cout << "fc.put.poznan.pl" << endl; | + | |
- | for(addrinfo * it = aio;;it++){ | + | if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res)); |
- | cout << " " << inet_ntoa(((sockaddr_in*)it->ai_addr)->sin_addr) << endl; | + | if(!resolved) error(1,0,"Empty result\n"); |
- | if(!it->ai_next) break; | + | |
- | } | + | char ip[40]; // maks. długość IP(v6) jako tekst: 8 bloków po 4 znaki oddzielone ':' |
- | freeaddrinfo(aio); | + | // można użyć stałą NI_MAXHOST określającą maksymalną długość nazwy domenowej |
- | </code> | + | 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'' |