przejście do zawartości
Jan Kończak
Narzędzia użytkownika
Zaloguj
Narzędzia witryny
Narzędzia
Pokaż stronę
Poprzednie wersje
Odnośniki
Ostatnie zmiany
Menadżer multimediów
Indeks
Zaloguj
Ostatnie zmiany
Menadżer multimediów
Indeks
Jesteś tutaj:
start
»
sk2
»
sockets_netdbs
sk2:sockets_netdbs
Ta strona jest tylko do odczytu. Możesz wyświetlić źródła tej strony ale nie możesz ich zmienić.
====== Zmiana adresu domenowego na IP ====== ===== Tradycyjna funkcja gethostbyname ===== 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: <code cpp ghbn.cpp> #include <unistd.h> #include <thread> #include <arpa/inet.h> #include <netdb.h> #include <cstdio> int main() { std::thread t1([]{ sleep(1); gethostbyname("spam.org"); }); 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> ~~Zadanie.#~~ Przetestuj powyższy kod. Zastanów się jak uniknąć problemu, który pokazuje powyższy kod. ===== Rozszerzenia GNU ===== <small> Kompilator GCC udostępnia wiele rozszerzeń "łatających" braki standardów [[https://gcc.gnu.org/onlinedocs/gcc/C-Extensions.html]].\\ 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> hostent he, *resptr; int status, retval; char buffer[4096]; retval = gethostbyname_r("cat.put.poznan.pl", &he, buffer, sizeof(buffer), &resptr, &status); if(retval) error(1,0,"gethostbyname_r error: %s", hstrerror(status)); if(!resptr) error(1,0,"empty result"); print(resptr);</code> </small> ===== getaddrinfo ===== Standard POSIX wprowadza funkcję ''getaddrinfo'', która pozwala na bezpieczne 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 * 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> 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: <code cpp gai1.cpp> #include <netdb.h> #include <cstdio> #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 (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> int main(){ addrinfo * resolved; // Podpowiedzi, port i nazwa hosta są opcjonalne. int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved); if(res) {fprintf(stderr, "Getaddrinfo failed: %s\n", gai_strerror(res)); return 1;} 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) {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> ~~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>
sk2/sockets_netdbs.txt
· ostatnio zmienione: 2024/10/18 09:12 przez
jkonczak
Narzędzia strony
Pokaż stronę
Poprzednie wersje
Odnośniki
Złóż / rozłóż wszystko
Do góry