取得主機名稱或服務的資訊,並將結果載入 struct sockaddr。
函式原型
Copy #include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo ( const char * nodename , const char * servname ,
const struct addrinfo * hints , struct addrinfo ** res);
void freeaddrinfo ( struct addrinfo * ai);
const char * gai_strerror ( int ecode);
struct addrinfo {
int ai_flags; // AI_PASSIVE, AI_CANONNAME, ...
int ai_family; // AF_xxx
int ai_socktype; // SOCK_xxx
int ai_protocol; // 0 (auto) or IPPROTO_TCP, IPPROTO_UDP
socklen_t ai_addrlen; // ai_addr 的長度
char * ai_canonname; // canonical name for nodename
struct sockaddr * ai_addr; // 二進制格式位址
struct addrinfo * ai_next; // linked list 中的下個資料結構
};
說明
getaddrinfo() 是很優秀的函式,可以傳回特別的主機名稱資訊(比如它的 IP address)以及為你載入 struct sockaddr,要注意一些細節(像是 IPv4 或 IPv6)。它會取代舊有的 gethostbyname() 及 getservbyname() 函式。下列的說明包含許多資訊,可能看起來有點難,但實際上卻很簡單。先看看範例是值得的。
你感興趣的 host name 在 node name 參數中,address 可以是個 host name,像 "www.example.com" 或者是 IPv4 或 IPv6 位址(以字串傳遞)。如果你使用 AI_PASSIVE flag,那這個參數也可以是 NULL(參考下面)。
servname 參數基本上就是 port number,它可以是個 port number(以字串傳遞,如 "80"),或者是個 service name,像是 "http"、"tftp"、"smtp"、或 "pop" 等。常見的 service name 可以在 IANA Port List [42] 或你的 /etc/services 中找到。
最後的 input 參數是 hints,這就是你要定義 getaddinfo() 函式要做什麼事的地方,使用以前先用 memset() 將整個資料結構清為零,在用它之前,我們先看一下需要設定的欄位。
ai_flags 可以設定成各種東西,但是這邊有件重要的事情。(可以用 OR 位元運算[|]指定多個 flags)完整的 flags 清單請參考你的 man 使用手冊。
AI_CANONNAME 會讓 ai_canonname 填上主機的 canonical (real) name,AI_PASSIVE 讓 IP address 填上 INADDR_ANY (IPv4)或 in6addr_any(IPv6);這讓之後在呼叫 bind() 時,可以自動用目前 host 的 address 來填上 struct sockaddr 的 IP address。這在設定 server 且你不想要寫死 address 時非常好用。
如果你使用 AI_PASSIVE flag,那麼你可以在 nodename 中傳遞 NULL(因為 bind() 在之後會幫你填上)
[42] http://www.iana.org/assignments/port-numbers
繼續談輸入的參數,你應該會想要將 ai_family 設定為 AF_UNSPEC,這樣可以讓 getaddrinfo() 知道 IPv4 與 IPv6 addresses 都需要查詢。你也能自己以 AF_INET 或 AF_INET6 自訂要使用 IPv4 或 IPv6。
再來,socktype 欄位應該要設定為 SOCK_STREAM 或 SOCK_DGRAM,取決與你需要哪種類型的 socket。
最後,你可以將 ai_protocol 保留為 0,可以自動選擇你的 protocol type。
在你取得全部的東西之後,你終於可以呼叫 getaddrinfo() 了!
當然,這個地方開始有趣了,res 會指向一個 struct addrinfo 的鏈結串列,而你可以透過這個串列取得全部的 addresses(符合你在 hints 中指定的 address 類型)。
現在可能會取得一些因為某些理由無法正常運作的 addresses,所以 Linux man 使用手冊提供的方法是不斷用迴圈讀取串列,然後試著呼叫 socket()、connect()(如果以 AI_PASSIVE flag 設定 server,則是 bind()),直到成功為止。
最後,當你處理完鏈結串列之後,你需要呼叫 freeaddrinfo() 來釋放記憶體(否則會發生 memory leak,這會讓有些人不安。)
傳回值
成功時傳回零,或錯誤時傳回非零值。若傳回非零值,你可以代入 gai_strerror() 函式取得一個文字版的錯誤訊息。
範例
Copy // client 連線到 server 的程式碼
// 透過 stream socket 連線到 www.example.com 的 port 80 (http)
// 不是 IPv4 就是 IPv6
int sockfd;
struct addrinfo hints , * servinfo , * p;
int rv;
memset ( & hints , 0 , sizeof hints);
hints.ai_family = AF_UNSPEC; // 設定 AF_INET6 表示強迫使用 IPv6
hints.ai_socktype = SOCK_STREAM;
if ((rv = getaddrinfo ( "www.example.com" , "http" , & hints , & servinfo)) != 0 ) {
fprintf(stderr , "getaddrinfo: %s \n" , gai_strerror(rv)) ;
exit( 1 ) ;
}
// 不斷執行迴圈,直到我們可以連線成功
for (p = servinfo; p != NULL ; p = p -> ai_next) {
if ((sockfd = socket( p -> ai_family , p -> ai_socktype , p -> ai_protocol) ) == - 1 ) {
perror( "socket" ) ;
continue ;
}
if ( connect(sockfd , p -> ai_addr , p -> ai_addrlen) == - 1 ) {
close(sockfd) ;
perror( "connect" ) ;
continue ;
}
break ; // if we get here, we must have connected successfully
}
if (p == NULL ) {
// 迴圈已經執行到 list 的最後,都無法連線
fprintf(stderr , "failed to connect\n" ) ;
exit( 2 ) ;
}
freeaddrinfo (servinfo); // 釋放 servinfo 記憶體空間
// code for a server waiting for connections
// namely a stream socket on port 3490, on this host's IP
// either IPv4 or IPv6.
int sockfd;
struct addrinfo hints , * servinfo , * p;
int rv;
memset ( & hints , 0 , sizeof hints);
hints.ai_family = AF_UNSPEC; // 使用 AF_INET6 表示一定要用 IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // 使用我的 IP address
if ((rv = getaddrinfo ( NULL , "3490" , & hints , & servinfo)) != 0 ) {
fprintf(stderr , "getaddrinfo: %s \n" , gai_strerror(rv)) ;
exit( 1 ) ;
}
// 不斷執行迴圈,直到我們可以成功綁定
for (p = servinfo; p != NULL ; p = p -> ai_next) {
if ((sockfd = socket( p -> ai_family , p -> ai_socktype ,
p -> ai_protocol) ) == - 1 ) {
perror( "socket" ) ;
continue ;
}
if ( bind(sockfd , p -> ai_addr , p -> ai_addrlen) == - 1 ) {
close(sockfd) ;
perror( "bind" ) ;
continue ;
}
break ; // 若執行到這行,表示我們一定已經成功連線
}
if (p == NULL ) {
// 整個迴圈執行完畢,到了 linked-list 結尾都無法成功綁定(bind)
fprintf(stderr , "failed to bind socket\n" ) ;
exit( 2 ) ;
}
freeaddrinfo (servinfo); // 使用完畢時釋放這個資料結構的空間
參考
gethostbyname(), getnameinfo()