Narzędzia użytkownika

Narzędzia witryny


sk2:sockets_netdbs

Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

Both sides previous revision Poprzednia wersja
Nowa wersja
Poprzednia wersja
sk2:sockets_netdbs [2015/10/22 14:02]
jkonczak [getaddrinfo] poprawianie błędu znalezionego przez studentów
sk2:sockets_netdbs [2023/10/17 14:03] (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(); ​    
- 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 ę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, ​tym IPv6, nie ustawi portu +#include <error.h>  ​// niestandardowa funkcja ułatwiająca obsługę błędów (dostępna ​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=it->​ai_next){ +    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_nextbreak+     
-+    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 = resolvedit; 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'' ​   ​
sk2/sockets_netdbs.1445515369.txt.gz · ostatnio zmienione: 2015/10/22 14:02 przez jkonczak