域名系統
DNS是對IP地址和域名進行互相轉換的系統,其核心是DNS服務器。提供網絡服務的服務端也是通過IP地址來區分的,但由於IP地址難於記憶,因此通過容易記憶並表述的域名來取代IP地址
在瀏覽器地址欄輸入www.baidu.com,或如圖1-1用ping命令獲取其IP地址,便可訪問百度主頁,那么通用域名訪問和通過IP訪問這二者有何區別?
圖1-1 用ping命令獲取百度地址
實際上,域名是賦予服務端的虛擬地址,而非實際地址。因此需要將虛擬地址轉化為實際地址。那么,如何將域名轉化為IP地址呢?DNS服務器承擔此重任,可以向DNS服務器請求轉換地址。所有計算機中都記錄着默認DNS服務器地址,就是通過默認DNS服務器得到相應域名的IP地址信息。在瀏覽器地址欄中輸入域名后,瀏覽器通過默認DNS服務器獲取該域名對應的IP地址信息,之后才真正接入該網站
計算機內置的默認DNS服務器並不知道網絡上所有域名的IP地址信息,若該DNS服務器無法解析,則會詢問其他DNS服務器並提供給用戶,如圖1-2
圖1-2 DNS和請求獲取IP地址信息
圖1-2展示了默認DNS服務器無法解析主機詢問的域名對應IP地址時的應答過程,可以看出,默認DNS服務器收到自己無法解析的請求時,向上級DNS服務器詢問,通過這種方式逐級向上傳遞信息,到達頂級DNS服務器時——根DNS服務器,它知道該向哪個DNS服務器詢問,向下級DNS傳遞解析請求,得到IP地址后原路返回,最終將解析的IP地址傳遞到發起請求的主機,DNS就是這樣層次化管理的一種分布式數據庫系統
利用域名獲取IP地址
#include <netdb.h> struct hostent *gethostbyname(const char *hostname);//成功時返回hostent結構體指針,失敗時返回NULL指針
只要向這個函數傳遞域名字符串,就會返回域名所對應的IP地址。只是返回時,地址信息裝入hostent結構體,此結構體定義如下:
struct hostent { char *h_name; //正式主機名 char **h_aliases; //主機別名 int h_addrtype; //主機IP地址類型:IPV4-AF_INET int h_length; //主機IP地址字節長度,對於IPv4是四字節,即32位 char **h_addr_list; //主機的IP地址列表 };
從上述結構體可以看出,當調用gethostbyname函數時不止返回IP信息,同時還帶着其他信息,下面簡要介紹下上述結構體中的各個成員:
- h_name:該變量存有官方域名,官方域名代表某一主頁,但實際上,一些著名公司的域名並未使用官方域名注冊
- h_aliases:可以通過多個域名訪問同一主頁,同一IP可以綁定多個域名,因此,除官方域名外還可指定其他域名
- h_addrtype:gethostbyname函數不僅支持IPv4,還支持IPv6。因此可以通過此變量獲取保存在h_addr_list的IP地址的地址族信息。若是IPv4,則此變量存有AF_INET
- h_length:保存IP地址長度。若是IPv4地址,因為是4個字節,則保存4;若是IPv6,因為是16個字節,故保存16
- h_addr_list:該變量以整數形式保存域名的IP地址。另外,用於訪問量較大的網站可能分配多個IP給同一域名,利用多個服務器進行負載均衡,同樣可以通過此變量獲取IP地址信息
調用gethostbyname函數后返回的hostent結構體變量結構如圖1-3所示:
圖1-3 hostent結構體變量
下面我們看一下gethostbyname函數的應用,並說明hostent結構體變量的特性
gethostbyname.c
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <arpa/inet.h> #include <netdb.h> void error_handling(char *message); int main(int argc, char *argv[]) { int i; struct hostent *host; if (argc != 2) { printf("Usage:%s<addr>\n", argv[0]); exit(1); } host = gethostbyname(argv[1]); if (!host) error_handling("gethost... error"); printf("Official name:%s\n", host->h_name); for (i = 0; host->h_aliases[i]; i++) printf("Aliases %d:%s\n", i + 1, host->h_aliases[i]); printf("Address type:%s\n", (host->h_addrtype == AF_INET) ? "AF_INET" : "AF_INET6"); for (i = 0; host->h_addr_list[i]; i++) printf("IP addr %d:%s\n", i + 1, inet_ntoa(*(struct in_addr *)host->h_addr_list[i])); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
- 第17行:通過main函數傳遞的字符串作為參數傳給gethostbyname函數
- 第20行:輸出官方域名
- 第21、22行:輸出除官方域名以外的域名
- 第24、25行:輸出IP地址信息
編譯gethostbyname.c並運行
# gcc gethostbyname.c -o gethostbyname # ./gethostbyname www.taobao.com Official name:www.taobao.com.danuoyi.tbcache.com Aliases 1:www.taobao.com Address type:AF_INET IP addr 1:61.154.126.109 IP addr 2:218.67.61.254
大家可在編譯gethostbyname.c后自行選擇一個域名進行測試,現在我們看一下第24和25行,如果我們只看hostent結構體的定義,結構體成員h_addr_list指向了一個字符串指針數組(由多個字符串地址構成的數組)。但字符串指針數組中的元素實際指向的是in_addr結構體地址而非字符串,如圖1-4所示:
圖1-4 h_addr_list結構體成員
圖1-4給出了h_addr_list結構體的參照關系,正因如此,上述代碼中才有了第25行的類型轉換,並調用inet_ntoa函數
這里可能大家會有疑問,為什么是char *而不是in_addr *?hostent結構體的成員h_addr_list指向的數組類型並不是in_addr結構體的指針數組,而是采用了char指針。是因為hostent結構體並非只為IPv4准備,因此考慮到通用性,聲明為char指針類型的數組。那么,聲明為void指針類型的數組是否更合理?確實,如果指針對象不明確時,聲明為void指針類型更合理,但我們所學習的套接字函數是早在void指針標准化之前定義的,在當時無法確定指針類型時都采用char指針
利用IP地址獲取域名
之前介紹的gethostbyname函數利用域名獲取包括IP地址在內的域相關信息,而gethostbyaddr函數利用IP地址獲取域相關信息
#include <sys/socket.h> struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);//成功時返回hostent結構體變量地址值,失敗時返回NULL指針
- addr:含有IP地址信息的in_addr結構體指針,為了同時傳遞IPv4地址之外的其他信息,該變量的類型聲明為char指針
- len:向第一個參數傳遞的地址信息的字節數,IPv4時為4,IPv6時為16
- family:傳遞地址族信息,IPv4時為AF_INET,IPv6時為AF_INET6
現在我們來看一下gethostbyaddr函數的使用方法
gethostbyaddr.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <netdb.h> void error_handling(char *message); int main(int argc, char *argv[]) { int i; struct hostent *host; struct sockaddr_in addr; if (argc != 2) { printf("Usage : %s <IP>\n", argv[0]); exit(1); } memset(&addr, 0, sizeof(addr)); addr.sin_addr.s_addr = inet_addr(argv[1]); host = gethostbyaddr((char *)&addr.sin_addr, 4, AF_INET); if (!host) error_handling("gethost... error"); printf("Official name: %s \n", host->h_name); for (i = 0; host->h_aliases[i]; i++) printf("Aliases %d: %s \n", i + 1, host->h_aliases[i]); printf("Address type: %s \n", (host->h_addrtype == AF_INET) ? "AF_INET" : "AF_INET6"); for (i = 0; host->h_addr_list[i]; i++) printf("IP addr %d: %s \n", i + 1, inet_ntoa(*(struct in_addr *)host->h_addr_list[i])); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
除第22行的gethostbyaddr函數調用外,與gethostbyname.c並無區別,因為函數調用的結果是通過hostent結構體變量地址值傳遞的
編譯gethostbyaddr.c並運行
# gcc gethostbyaddr.c -o gethostbyaddr # ./gethostbyaddr 218.67.61.254 Official name: 254.61.67.218.broad.sm.fj.dynamic.163data.com.cn Address type: AF_INET IP addr 1: 218.67.61.254
之前我們通過gethostbyname.c獲得了某寶的IP地址,現在我們嘗試用之前獲得的IP地址反過來獲取域名信息。從運行結果可以看到,記錄於DNS的官方主頁地址具有特殊格式