getaddrinfo()函數詳解

1. 概述

IPv4中使用gethostbyname()函數完成主機名到地址解析,這個函數僅僅支持IPv4,且不允許調用者指定所需地址類型的任何信息,返回的結構只包含了用於存儲IPv4地址的空間。IPv6中引入了getaddrinfo()的新API,它是協議無關的,既可用於IPv4也可用於IPv6。getaddrinfo函數能夠處理名字到地址以及服務到端口這兩種轉換,返回的是一個addrinfo的結構(列表)指針而不是一個地址清單。這些addrinfo結構隨後可由套接口函數直接使用。如此以來,getaddrinfo函數把協議相關性安全隱藏在這個庫函數內部。應用程序只要處理由getaddrinfo函數填寫的套接口地址結構。該函數在 POSIX規範中定義了。


2. 函數說明

包含頭文件
#include<netdb.h>

函數原型
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );

參數說明
hostname:一個主機名或者地址串(IPv4的點分十進制串或者IPv6的16進制串)
service:服務名可以是十進制的端口號,也可以是已定義的服務名稱,如ftp、http等
hints:可以是一個空指針,也可以是一個指向某個addrinfo結構體的指針,調用者在這個結構中填入關於期望返回的信息類型的暗示。舉例來說:如果指定的服務既支持TCP也支持UDP,那麼調用者可以把hints結構中的ai_socktype成員設置成SOCK_DGRAM使得返回的僅僅是適用於數據報套接口的信息。
result:本函數通過result指針參數返回一個指向addrinfo結構體鏈表的指針。
返回值:0——成功,非0——出錯


3. 參數設置

在getaddrinfo函數之前通常需要對以下6個參數進行以下設置:nodename、servname、hints的ai_flags、ai_family、ai_socktype、ai_protocol。
在6項參數中,對函數影響最大的是nodename,sername和hints.ai_flag,而ai_family只是有地址爲v4地址或v6地址的區別。ai_protocol一般是爲0不作改動。

getaddrinfo在實際使用中的幾種常用參數設置
一般情況下,client/server編程中,server端調用bind(如果面向連接的還需要listen),client則不用掉bind函數,解析地址後直接connect(面向連接)或直接發送數據(無連接)。因此,比較常見的情況有
(1)    通常服務器端在調用getaddrinfo之前,ai_flags設置AI_PASSIVE,用於bind;主機名nodename通常會設置爲NULL,返回通配地址[::]。
(2)    客戶端調用getaddrinfo時,ai_flags一般不設置AI_PASSIVE,但是主機名nodename和服務名servname(更願意稱之爲端口)則應該不爲空。
(3)    當然,即使不設置AI_PASSIVE,取出的地址也並非不可以被bind,很多程序中ai_flags直接設置爲0,即3個標誌位都不設置,這種情況下只要hostname和servname設置的沒有問題就可以正確bind。

上述情況只是簡單的client/server中的使用,但實際在使用getaddrinfo和參考國外開源代碼的時候,曾遇到一些將servname(即端口)設爲NULL的情況(當然,此時nodename必不爲NULL,否則調用getaddrinfo會報錯)。
以下分情況進行了測試:
(1)    如果nodename是字符串型的IPv6地址,bind的時候會分配臨時端口;
(2)    如果nodename是本機名,servname爲NULL,則根據操作系統的不同略有不同,本文僅在WinXP和Win2003上作了測試。
        a)    WinXP系統(SP2)返回loopback地址[::1]
        b)    Win2003則將本機的所有IPv6地址列表加以返回。因爲通常一臺IPv6主機都有可能不止一個IPv6地址,比如fe80::1(本機 loopback地址)、fe80::***的Link-Local地址、3ffe:***的全局地址等等。這種情況下調用getaddrinfo會將這些地址全部返回,調用者應該注意如何使用這些地址。另外要注意的是,對於fe80::的地址在綁定的時候必須標明接口地址,即使用 fe80::20d:60ff:fe78:51c2%4或fe80::1%1這樣的地址格式,通過getaddrinfo直接取出fe80地址好像無法直接bind。
        
        
4. 使用細節

如果本函數返回成功,那麼由result參數指向的變量已被填入一個指針,它指向的是由其中的ai_next成員串聯起來的addrinfo結構鏈表。可以導致返回多個addrinfo結構的情形有以下2個:
    1.    如果與hostname參數關聯的地址有多個,那麼適用於所請求地址簇的每個地址都返回一個對應的結構。
    2.    如果service參數指定的服務支持多個套接口類型,那麼每個套接口類型都可能返回一個對應的結構,具體取決於hints結構的ai_socktype成員。

我們必須先分配一個hints結構,把它清零後填寫需要的字段,再調用getaddrinfo,然後遍歷一個鏈表逐個嘗試每個返回地址。
        
getaddrinfo解決了把主機名和服務名轉換成套接口地址結構的問題。

其中,如果getaddrinfo出錯,那麼返回一個非0的錯誤值。
#include<netdb.h>
const char *gai_strerror( int error );
該函數以getaddrinfo返回的非0錯誤值的名字和含義爲他的唯一參數,返回一個指向對應的出錯信息串的指針。

由getaddrinfo返回的所有存儲空間都是動態獲取的,這些存儲空間必須通過調用freeaddrinfo返回給系統。
#include< netdb.h >
void freeaddrinfo( struct addrinfo *ai );
ai參數應指向由getaddrinfo返回的第一個addrinfo結構。這個連表中的所有結構以及它們指向的任何動態存儲空間都被釋放掉。


5. 例子

#include <stdio.h>
#include 
<stdlib.h>
#include 
<sys/socket.h>
#include 
<netinet/in.h>
#include 
<netdb.h>
#include 
<string.h>
int main(int argc, char **argv)
{
if (argc != 2) {
fprintf(stderr, 
"Usage: %s hostname\n",
argv[
1]);
exit(
1);   
}

struct addrinfo *answer, hint, *curr;
char ipstr[16];   
bzero(
&hint, sizeof(hint));
hint.ai_family 
= AF_INET;
hint.ai_socktype 
= SOCK_STREAM;

int ret = getaddrinfo(argv[1], NULL, &hint, &answer);
if (ret != 0) {
fprintf(stderr,
"getaddrinfo: &s\n",
gai_strerror(ret));
exit(
1);
}

for (curr = answer; curr != NULL; curr = curr->ai_next) {
inet_ntop(AF_INET,
&(((struct sockaddr_in *)(curr->ai_addr))->sin_addr),
ipstr, 
16);
printf(
"%s\n", ipstr);
}

freeaddrinfo(answer);
exit(
0);
}
發佈了15 篇原創文章 · 獲贊 1 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章