Narzędzia użytkownika

Narzędzia witryny


Pasek boczny

sk2:sockets_netdbs

To jest stara wersja strony!


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:

ghbn.cpp
#include <iostream>
#include <netdb.h>
#include <arpa/inet.h>
#include <thread>
#include <atomic>
#include <unistd.h>
 
using namespace std;
 
void print(hostent* ret){
	cout << ret->h_name << endl;
	for(auto it = ret->h_addr_list; *it; ++it){
			cout << "  " << inet_ntoa(*((in_addr*)*it)) << endl;
	}
}
 
int main(int argc, char **argv) {
	atomic<bool> wait1 {true}, wait2 {true};
 
	std::thread 
	t1([&]{
		while(wait1.load());
		// cout << "About to invoke gethostbyname(\"spam.org\")" << endl;
		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;
}

Zadanie 1. Przetestuj powyższy kod. Zastanów się jak uniknąć problemu, który pokazuje powyższy kod.

Rozszerzenia GNU

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)

hostent he, *resptr;
	int status, retval;
	char buffer[4096];
	retval = gethostbyname_r("fc.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);

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:

int getaddrinfo(                  // zwraca 0 (ok) lub -1 (błąd) 
    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)

Opis struktury addrinfo znajdziesz na stronie manuala do getaddringo (man getaddrinfo).

Przykłady użycia:

gai1.cpp
#include <netdb.h>
#include <cstdio>
#include <error.h>
#include <arpa/inet.h>
 
int main(){
    // Ustawienie "podpowiedzi" - sterowanie jakie wyniki chcemy otrzymać:
    addrinfo hints {};
    hints.ai_family   = AF_INET;         // tylko IPv4 (AF_INET)
    hints.ai_protocol = IPPROTO_TCP;     // protokół TCP
 
    // Zmienna w której będzie umieszczona lokalizacja wyniku w pamięci
    addrinfo * resolved;
 
    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;
}
gai2.cpp
#include <netdb.h>
#include <cstdio>
#include <error.h>
 
int main(){
    addrinfo * resolved;
    // Podpowiedzi, port i nazwa hosta są opcjonalne.
    int res = getaddrinfo("ietf.org", nullptr, nullptr, &resolved);
 
    if(res) error(1,0,"Getaddrinfo failed: %s\n", gai_strerror(res));
    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 ':'
    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) 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;
}

Zadanie 2. Stwórz klienta TCP, który:

  • odczyta z listy argumentów adres i numer portu
  • użyje funkcji getaddrinfo z pustymi podpowiedziami
  • 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.1509310854.txt.gz · ostatnio zmienione: 2017/10/29 22:00 przez jkonczak