Różnice między wybraną wersją a wersją aktualną.
| Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
|
sk2:sockets_netdbs [2018/10/22 16:43] jkonczak [getaddrinfo] |
sk2:sockets_netdbs [2025/10/22 15:40] (aktualna) jkonczak |
||
|---|---|---|---|
| Linia 10: | Linia 10: | ||
| #include <arpa/inet.h> | #include <arpa/inet.h> | ||
| #include <netdb.h> | #include <netdb.h> | ||
| + | #include <cstdio> | ||
| int main() { | int main() { | ||
| | | ||
| - | std::thread t1([&]{ | + | std::thread t1([]{ |
| sleep(1); | sleep(1); | ||
| gethostbyname("spam.org"); | gethostbyname("spam.org"); | ||
| }); | }); | ||
| | | ||
| - | std::thread t2([&]{ | + | std::thread t2([]{ |
| - | hostent* ret = gethostbyname("fc.put.poznan.pl"); | + | hostent* ret = gethostbyname("cat.put.poznan.pl"); |
| sleep(2); | sleep(2); | ||
| printf("%s: %s\n", ret->h_name, inet_ntoa(**(in_addr**)ret->h_addr_list)); | printf("%s: %s\n", ret->h_name, inet_ntoa(**(in_addr**)ret->h_addr_list)); | ||
| 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]].\\ | ||
| - | 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''. | ||
| - | <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 |
| - | int getaddrinfo( // zwraca 0 (ok) lub -1 (błąd) | + | 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><html></small></html>Opis struktury ''addrinfo'' znajdziesz na stronie manuala do getaddringo (''man getaddrinfo''). | + | </code> |
| + | |||
| + | Definicja struktury ''addrinfo'': | ||
| + | <html><div style=margin-top:-1.2em></div></html> | ||
| + | <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: | Przykłady użycia: | ||
| Linia 68: | Linia 126: | ||
| #include <netdb.h> | #include <netdb.h> | ||
| #include <cstdio> | #include <cstdio> | ||
| - | #include <error.h> | ||
| #include <arpa/inet.h> | #include <arpa/inet.h> | ||
| int main(){ | int main(){ | ||
| // Ustawienie "podpowiedzi" - sterowanie jakie wyniki chcemy otrzymać: | // Ustawienie "podpowiedzi" - sterowanie jakie wyniki chcemy otrzymać: | ||
| - | addrinfo hints {}; | + | addrinfo hints {}; // uwaga: puste 'list initialization' zeruje całą strukturę |
| hints.ai_family = AF_INET; // tylko IPv4 (AF_INET) | hints.ai_family = AF_INET; // tylko IPv4 (AF_INET) | ||
| - | hints.ai_protocol = IPPROTO_TCP; // protokół TCP | + | hints.ai_protocol = IPPROTO_UDP; // protokół UDP |
| | | ||
| // Zmienna w której będzie umieszczona lokalizacja wyniku w pamięci | // Zmienna w której będzie umieszczona lokalizacja wyniku w pamięci | ||
| 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 94: | 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 103: | 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 ':' | ||
| + | // można użyć stałą NI_MAXHOST określającą maksymalną długość nazwy domenowej | ||
| for(addrinfo * it = resolved; it; it=it->ai_next){ | for(addrinfo * it = resolved; it; it=it->ai_next){ | ||
| // 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 117: | Linia 177: | ||
| | | ||
| return 0; | return 0; | ||
| - | }</code> | + | } |
| + | </code> | ||
| - | //Zadanie 2.// Stwórz klienta TCP, który: | + | <html> |
| + | <script> | ||
| + | function genGaiCall(){ | ||
| + | var hints = ""; | ||
| + | if(document.getElementById("ai_family").value) | ||
| + | hints += "hints.ai_family = " + document.getElementById("ai_family").value + ";\n"; | ||
| + | if(document.getElementById("ai_socktype").value) | ||
| + | hints += "hints.ai_socktype = " + document.getElementById("ai_socktype").value + ";\n"; | ||
| + | if(document.getElementById("ai_protocol").value) | ||
| + | hints += "hints.ai_protocol = " + document.getElementById("ai_protocol").value + ";\n"; | ||
| + | var flags = ""; | ||
| + | if(document.getElementById("AI_PASSIVE").checked) | ||
| + | flags += "AI_PASSIVE | "; | ||
| + | if(!document.getElementById("AI_NUMERICHOST").checked) | ||
| + | flags += "AI_NUMERICHOST | "; | ||
| + | if(!document.getElementById("AI_NUMERICSERV").checked) | ||
| + | flags += "AI_NUMERICSERV | "; | ||
| + | if(flags) | ||
| + | hints += "hints.ai_flags = " + flags.slice(0, -3) + ";\n"; | ||
| + | |||
| + | var text = ""; | ||
| + | if(hints) | ||
| + | text = "struct addrinfo hints = {};\n" + hints; | ||
| + | text += "struct addrinfo *res;\n"; | ||
| + | text += "int status;\n"; | ||
| + | text += "if (0 != (status = getaddrinfo(address, port, " + (hints?"&hints":"NULL") + ", &res))) {\n"; | ||
| + | text += " fprintf(stderr, \"Query failed: %s\\n\", gai_strerror(status));\n"; | ||
| + | text += " …\n"; | ||
| + | text += "}\n"; | ||
| + | text += "// przykładowe użycie tylko pierwszego ze zwróconych adresów:\n"; | ||
| + | text += "int sockFd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);\n"; | ||
| + | if(document.getElementById("AI_PASSIVE").checked) | ||
| + | text += "bind(sockFd, res->ai_addr, res->ai_addrlen);\n"; | ||
| + | else | ||
| + | text += "connect(sockFd, res->ai_addr, res->ai_addrlen);\n"; | ||
| + | text += "freeaddrinfo(res);"; | ||
| + | document.getElementById("gaiCall").innerHTML = text; | ||
| + | } | ||
| + | </script> | ||
| + | <div style="padding: 0.5em; border: 2px lightgray solid; display: inline-block;"> | ||
| + | Interaktywny przykład jak używać <code>getaddrinfo</code>: | ||
| + | <small> | ||
| + | <div style="display:table"> | ||
| + | <div style="display:table-row"> | ||
| + | <div style="display:table-cell"> | ||
| + | <form> | ||
| + | Protokół warstwy sieci: | ||
| + | <select onChange="genGaiCall();" id="ai_family"> | ||
| + | <option value="">dowolny</option> | ||
| + | <option value="AF_INET">IPv4</option> | ||
| + | <option value="AF_INET6">IPv6</option> | ||
| + | </select> | ||
| + | <br> | ||
| + | Rodzaj gniazda: | ||
| + | <select onChange="genGaiCall();" id="ai_socktype"> | ||
| + | <option value="">dowolny</option> | ||
| + | <option value="SOCK_STREAM">strumieniowe</option> | ||
| + | <option value="SOCK_DGRAM">datagramowe</option> | ||
| + | </select> | ||
| + | <br> | ||
| + | Protokół warstwy transportowej: | ||
| + | <select onChange="genGaiCall();" id="ai_protocol"> | ||
| + | <option value="">dowolny</option> | ||
| + | <option value="IPPROTO_TCP">TCP</option> | ||
| + | <option value="IPPROTO_UDP">UDP</option> | ||
| + | </select> | ||
| + | <br> | ||
| + | Opcje: | ||
| + | <ul style="margin-top: 0em"> | ||
| + | <li><input onChange="genGaiCall();" type="checkbox" id="AI_PASSIVE"><label class="li" for="AI_PASSIVE"> chcę użyć adresu w funkcji <code>bind</code></label></li> | ||
| + | <li><input onChange="genGaiCall();" type="checkbox" checked id="AI_NUMERICHOST"><label class="li" for="AI_NUMERICHOST"> chcę użyć DNS do nazw domenowych</label></li> | ||
| + | <li><input onChange="genGaiCall();" type="checkbox" checked id="AI_NUMERICSERV"><label class="li" for="AI_NUMERICSERV"> zamiast numeru portu akceptuj też nazwę</label></li> | ||
| + | </ul> | ||
| + | </form> | ||
| + | </div> | ||
| + | <div style="display:table-cell"> | ||
| + | <pre id="gaiCall" style="margin-left:1em;margin-bottom:0;user-select:none;"> | ||
| + | </pre> | ||
| + | <script>genGaiCall();</script> | ||
| + | </div> | ||
| + | </div> | ||
| + | </div> | ||
| + | </small> | ||
| + | </div> | ||
| + | </html> | ||
| + | |||
| + | ~~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> | ||