Różnice między wybraną wersją a wersją aktualną.
Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
sk2:sockets_netdbs [2017/10/23 15:06] jkonczak |
sk2:sockets_netdbs [2024/10/18 09:12] (aktualna) jkonczak [getaddrinfo] |
||
---|---|---|---|
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(); |
- | // cout << "About to invoke gethostbyname(\"spam.org\")" << endl; | + | return 0; |
- | gethostbyname("spam.org"); | + | |
- | // cout << "gethostbyname(\"spam.org\") finished" << endl << endl; | + | |
- | wait2.store(false); | + | |
- | sleep(1); | + | |
- | }), | + | |
- | t2([&]{ | + | |
- | // cout << "About to invoke gethostbyname(\"fc.put.poznan.pl\")" << endl; | + | |
- | hostent* ret = gethostbyname("fc.put.poznan.pl"); | + | |
- | // cout << "gethostbyname(\"fc.put.poznan.pl\") finished" << endl << endl; | + | |
- | wait1.store(false); | + | |
- | while(wait2.load()); | + | |
- | // cout << "Printing the result of gethostbyname(\"fc.put.poznan.pl\")" << endl; | + | |
- | 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. | + | ~~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]].\\ | ||
- | 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> | + | </small> |
- | <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''. | ||
- | Składnia: <code cpp> | + | Standard POSIX wprowadza funkcję ''getaddrinfo'', która pozwala na bezpieczne |
- | int getaddrinfo( // zwraca 0 (ok) lub -1 (błąd) | + | 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) | ||
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: | + | |
+ | Definicja struktury ''addrinfo'': | ||
+ | <html><div style=margin-top:-1.2em></div></html> | ||
<code cpp> | <code cpp> | ||
+ | struct addrinfo { | ||
+ | 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_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; // 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 | ||
+ | };</code> | ||
+ | |||
+ | 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: | ||
+ | <code cpp gai1.cpp> | ||
#include <netdb.h> | #include <netdb.h> | ||
- | #include <error.h> // (funkcja error) | + | #include <cstdio> |
- | #include <errno.h> // (zmienna errno) | + | #include <arpa/inet.h> |
- | (...) | + | |
- | // // znajdzie dowolne adresy, w tym IPv6, nie ustawi portu | + | |
- | // addrinfo * aio; | + | |
- | // 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) {fprintf(stderr, "Getaddrinfo failed: %s\n", gai_strerror(res)); return 1;} | ||
+ | |||
+ | 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> | ||
- | 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=it->ai_next){ | + | if(res) {fprintf(stderr, "Getaddrinfo failed: %s\n", gai_strerror(res)); return 1;} |
- | cout << " " << inet_ntoa(((sockaddr_in*)it->ai_addr)->sin_addr) << endl; | + | |
- | if(!it->ai_next) break; | + | 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 |
- | freeaddrinfo(aio); | + | 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) {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); | ||
+ | } | ||
+ | |||
+ | freeaddrinfo(resolved); | ||
+ | |||
+ | 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 pustymi podpowiedziami | + | * 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> |