Różnice między wybraną wersją a wersją aktualną.
| Both sides previous revision Poprzednia wersja Nowa wersja | Poprzednia wersja | ||
|
sk2:sockets_netdbs [2016/10/18 09:39] jkonczak |
sk2:sockets_netdbs [2025/10/22 15:40] (aktualna) jkonczak |
||
|---|---|---|---|
| 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.#~~ 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 <cstdio> |
| - | // // znajdzie dowolne adresy, w tym IPv6, nie ustawi portu | + | #include <arpa/inet.h> |
| - | // 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> | ||
| + | <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 | ||
| + | * użyje funkcji ''getaddrinfo'' z podpowiedzią żądającą protokołu TCP | ||
| + | * 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'' | ||
| + | <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> | ||