gethostbyname, gethostbyaddr是不可重入函數;已經被getaddrinfo, getnameinfo替代。
可重入函數主要用於多任務環境中,一個可重入的函數簡單來說就是可以被中斷的函數,也就是說,可以在這個函數執行的任何時刻中斷它,轉入OS調度下去執行另外一段。不可重入,意味着不可被並行調度,否則會產生不可預料的結果,這些函數提內一般使用了靜態(static)的數據結構,使用了malloc()或者free()函數,使用了標准I/O函數等等。
getaddrinfo 將主機和服務轉換到socket地址,融合了函數getipnodebyname, getipnodebyaddr, getservbyname, getservbyport的功能,是可重入的。
getnameinfo 功能與getaddrinfo相反,它將socket地址轉換到主機和服務,融合了函數gethostbyaddr、getservbyport的功能,也是可重入的。
函數原型:
int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res);
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags);
addrinfo 結構體定義如下:
struct addrinfo { int ai_flags; int ai_family; int ai_socktype; int ai_protocol; size_t ai_addrlen; struct sockaddr *ai_addr; char *ai_canonname; struct addrinfo *ai_next; };
函數參數說明:
node或service參數最多可能有一個為NULL。
node 要么為點分式地址(IPv4-點分十進制, IPv6-十六進制) 要么為主機名。如果hints.ai_flags包含了AI_NUMERICHOST標志,則node必須為數字地址。
service為端口號或者端口名。如果不為空,為端口名,則必須可以解析,通過/etc/services。
如果hints.ai_flags指定了AI_CANONNAME標志,則返回的結構體列表中第一個addrinfo結構體的ai_canonname域指向了主機的正式名字
如果hints.ai_flags指定了AI_PASSIVE標志,而且node為NULL,則返回的地址包含INADDR_ANY或IN6ADDR_ANY_INIT適合用來bind將要accept連接的socket。
如果hints.ai_flags指定了AI_PASSIVE標志,而且node不為NULL,則忽略AI_PASSIVE。
如果hints.ai_flags未指定AI_PASSIVE標志,則返回的地址適合用來connect,sendto,sendmsg。
如果hints.ai_flags未指定AI_PASSIVE標志,而且node為NULL, 則返回回環地址,用於本地服務。
根據node和service查找到對應的addr info。如果此信息被connect調用,則表示這是被監聽對象;而監聽者可以通過node里面設置。node如果是fe80這種IP V6地址,必須接%eth來指定接口,則監聽者就是這個local IP;如果是raw socket,則監聽端口就是hints里面的 ai_protocol。 getaddrinfo() supports the address%scope-id notation for specifying the IPv6 scope-ID.
IPv4中使用gethostbyname()函數完成主機名到地址解析,這個函數僅僅支持IPv4,
且不允許調用者指定所需地址類型的任何信息,返回的結構只包含了用於存儲IPv4地址的空間。
IPv6中引入了新的API getaddrinfo(),它是協議無關的,既可用於IPv4也可用於IPv6。
getaddrinfo() 函數能夠處理名字到地址以及服務到端口這兩種轉換,返回的是一個 struct addrinfo 的結構體(列表)指針而不是一個地址清單。
這些 struct addrinfo 結構體隨后可由套接口函數直接使用。如此以來,getaddrinfo()函數把協議相關性安全隱藏在這個庫函數內部。
應用程序只要處理由getaddrinfo()函數填寫的套接口地址結構。
Parameter
1) nodename
主機名("www.baidu.com")或者是數字化的地址字符串(IPv4的點分十進制串("192.168.1.100")或者IPv6的16進制串("2000::1:2345:6789:abcd")),
如果 ai_flags 中設置了AI_NUMERICHOST 標志,那么該參數只能是數字化的地址字符串,不能是域名,
該標志的作用就是阻止進行域名解析。
nodename 和 servname 可以設置為NULL,但是同時只能有一個為NUL。
2) servname
服務名可以是十進制的端口號("8080")字符串,也可以是已定義的服務名稱,如"ftp"、"http"等,詳細請查看/etc/services 文件,
最后翻譯成對應服務的端口號。如果此參數設置為NULL,那么返回的socket地址中的端口號不會被設置。
如果 ai_flags 設置了AI_NUMERICSERV 標志並且該參數未設置為NULL,那么該參數必須是一個指向10進制的端口號字符串,
不能設定成服務名,該標志就是用來阻止服務名解析。
3) hints
該參數指向用戶設定的 struct addrinfo 結構體,只能設定該結構體中 ai_family、ai_socktype、ai_protocol 和 ai_flags 四個域,
其他域必須設置為0 或者 NULL, 通常是申請 結構體變量后使用memset()初始化再設定指定的四個域。
該參數可以設置為NULL,等價於 ai_socktype = 0, ai_protocol = 0,ai_family = AF_UNSPEC,
ai_flags = 0 (在GNU Linux中ai_flag = AI_V4MAPPED | AI_ADDRCONFIG,可以看作是GNU的改進)。
① ai_family
指定返回地址的協議簇,取值范圍:AF_INET(IPv4)、AF_INET6(IPv6)、AF_UNSPEC(IPv4 and IPv6)
② ai_socktype
具體類型請查看struct addrinfo 中的 enum __socket_type 類型,用於設定返回地址的socket類型,
常用的有SOCK_STREAM、SOCK_DGRAM、SOCK_RAW, 設置為0表示所有類型都可以。
③ ai_protocol
具體取值范圍請查看 Ip Protocol ,常用的有 IPPROTO_TCP、IPPROTO_UDP 等,設置為0表示所有協議。
④ ai_flags
附加選項,多個選項可以使用或操作進行結合,具體取值范圍請查看struct addrinfo , 常用的標志如下:
- AI_PASSIVE
如果設置了 AI_PASSIVE 標志,並且 nodename 是 NULL, 那么返回的socket地址可以用於的bind()函數,
返回的地址是通配符地址(wildcard address, IPv4時是INADDR_ANY,IPv6時是IN6ADDR_ANY_INIT),
這樣應用程序(典型是server)就可以使用這個通配符地址用來接收任何請求主機地址的連接,
如果 nodename 不是NULL,那么 AI_PASSIVE 標志被忽略;
如果未設置AI_PASSIVE標志,返回的socket地址可以用於connect(), sendto(), 或者 sendmsg()函數。
如果 nodename 是NULL,那么網絡地址會被設置為lookback接口地址(IPv4時是INADDR_LOOPBACK,IPv6時是IN6ADDR_LOOPBACK_INIT),
這種情況下,應用是想與運行在同一個主機上另一個應用通信。
- AI_CANONNAME
請求canonical(主機的official name)名字。如果設置了該標志,那么 res 返回的第一個 struct addrinfo 中的 ai_canonname 域會存儲official name的指針。
- AI_NUMERICHOST
阻止域名解析,具體見 nodename 中的說明。
- AI_NUMERICSERV
阻止服務名解析,具體見 servname 中的說明。
- AI_V4MAPPED
當 ai_family 指定為AF_INT6(IPv6)時,如果沒有找到IPv6地址,那么會返回IPv4-mapped IPv6 地址,
也就是說如果沒有找到AAAA record(用來將域名解析到IPv6地址的DNS記錄),那么就查詢A record(IPv4),
將找到的IPv4地址映射到IPv6地址, IPv4-mapped IPv6 地址其實是IPv6內嵌IPv4的一種方式,
地址的形式為"0::FFFF:a.b.c.d",例如"::ffff:192.168.89.9"(混合格式)這個地址仍然是一個IPv6地址,
只是"0000:0000:0000:0000:0000:ffff:c0a8:5909"(16機制格式)的另外一種寫法罷了。
當 ai_family 不是AF_INT6(IPv6)時,該標志被忽略。
- AI_ALL
查詢IPv4和IPv6地址
- AI_ADDRCONFIG
只有當主機配置了IPv4地址才進行查詢IPv4地址;只有當主機配置了IPv6地址才進行查詢IPv6地址.
4) res
該參數獲取一個指向存儲結果的 struct addrinfo 結構體列表,使用完成后調用 freeaddrinfo() 釋放存儲結果空間。
Return Value
如果 getaddrinfo() 函數執行成功,返回值為 0 , 其他情況返回值表示錯誤種別。使用函數gai_strerror() 可以獲取可讀性的錯誤信息,用法用strerror()相同,
錯誤種別如下:
- EAI_ADDRFAMILY
指定的主機上沒有請求的address family對應的網絡地址.
- EAI_AGAIN
DNS(name server)返回臨時性錯誤. 可以稍后重試.
- EAI_BADFLAGS
hints.ai_flags 包含了無效的標志; 或者 hints.ai_flags 包含了 AI_CANONNAME 標志但是 name 是 NULL.
- EAI_FAIL
DNS(name server)返回永久性錯誤
- EAI_FAMILY
不支持的 address family(hints.ai_family).
- EAI_MEMORY
內存耗盡.
- EAI_NODATA
指定的網絡主機存在,但是其未定義任何網絡地址.
- EAI_NONAME
nodename 或者 servname 未知;或者兩者都設置為NULL;
或者設置了 AI_NUMERICSERV 標志但是 servname 不是一個數字化的端口名字符串。
- EAI_SERVICE
請求的socket類型不支持請求的服務類型.例如服務類型是 "shell" (基於流的socket服務),
但是 hints.ai_protocol 是 IPPROTO_UDP 或者hints.ai_socktype 是 SOCK_DGRAM;
或者 servname 不是NULL 但是 hints.ai_socktype 是 SOCK_RAW (原始套接字不支持服務的概念).
- EAI_SOCKTYPE
不支持請求的socket類型. 例如, hints.ai_socktype 和 hints.ai_protocol 沖突 (例如分別是SOCK_DGRAM、IPPROTO_TCP).
- EAI_SYSTEM
系統調用錯誤,檢查 errno.
示例:
#include <stdlib.h> #include <stdio.h> #include <string.h> #include <netdb.h> #include <sys/socket.h> int ip_to_hostname(const char* ip) { int ret = 0; if (!ip) { printf("invalid params\n"); return -1; } struct addrinfo hints; struct addrinfo *res, *res_p; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME | AI_NUMERICHOST; hints.ai_protocol = 0; ret = getaddrinfo(ip, NULL, &hints, &res); if (ret != 0) { printf("getaddrinfo: %s\n", gai_strerror(ret)); return -1; } for (res_p = res; res_p != NULL; res_p = res_p->ai_next) { char host[1024] = {0}; ret = getnameinfo(res_p->ai_addr, res_p->ai_addrlen, host, sizeof(host), NULL, 0, NI_NAMEREQD); if(ret != 0) printf("getnameinfo: %s\n", gai_strerror(ret)); else printf("hostname: %s\n", host); } freeaddrinfo(res); return ret; } int hostname_to_ip(const char* hostname) { int ret = 0; if (!hostname) { printf("invalid params\n"); return -1; } struct addrinfo hints; struct addrinfo *res, *res_p; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME; hints.ai_protocol = 0; ret = getaddrinfo(hostname, NULL, &hints, &res); if (ret != 0) { printf("getaddrinfo: %s\n", gai_strerror(ret)); return -1; } for (res_p = res; res_p != NULL; res_p = res_p->ai_next) { char host[1024] = {0}; ret = getnameinfo(res_p->ai_addr, res_p->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST); if(ret != 0) printf("getnameinfo: %s\n", gai_strerror(ret)); else printf("ip: %s\n", host); } freeaddrinfo(res); return ret; } int main(void) { int err = ip_to_hostname("127.0.1.1"); printf("err1: %d\n\n", err); err = hostname_to_ip("baidu.com"); printf("err2: %d\n", err); return 0; }