歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> 關於Linux >> Linux socket編程:addrinfo結構體與getaddrinfo函數

Linux socket編程:addrinfo結構體與getaddrinfo函數

日期:2017/3/3 16:23:59   编辑:關於Linux

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結構。這個連表中的所有結構以及它們指向的任何動態存儲空間都被釋放掉。

addrinfo結構體的定義如下:

struct addrinfo {  
     int ai_flags; /* customize behavior */
     int ai_family; /* address family */
     int ai_socktype; /* socket type */
     int ai_protocol; /* protocol */
     socklen_t ai_addrlen; /* length in bytes of address */
     struct sockaddr *ai_addr; /* address */
     char *ai_canonname; /* canonical name of host */
     struct addrinfo *ai_next; /* next in list */
     .  
     .  
     .  
   };

ai_family指定了地址族,可取值如下:

AF_INET 2 IPv4

AF_INET6 23 IPv6

AF_UNSPEC 0 協議無關

ai_socktype指定我套接字的類型

SOCK_STREAM 1 流

SOCK_DGRAM 2 數據報

在AF_INET通信域中套接字類型SOCK_STREAM的默認協議是TCP(傳輸控制協議)

在AF_INET通信域中套接字類型SOCK_DGRAM的默認協議是UDP(用戶數據報協議)

ai_protocol指定協議類型。可取的值取決於ai_address和ai_socktype的值

ai_flags指定了如何來處理地址和名字,可取值如下:

getaddrinfo函數 定義及需要的頭文件如下:

#include <sys/socket.h>  
#include <netdb.h>  
      
int getaddrinfo(const char *restrict host,  
                const char *restrict service,  
                const struct addrinfo *restrict hint,  
                struct addrinfo **restrict res);  
      
      
      
      
Returns: 0 if OK, nonzero error code on error  
      
      
void freeaddrinfo(struct addrinfo *ai);

getaddrinfo函數允許將一個主機名字和服務名字映射到一個地址。

實例:

#include<stdio.h>  
#include<stdlib.h>  
#include <netdb.h>  
#include <arpa/inet.h>  
      
void
print_family(struct addrinfo *aip)  
{  
    printf(" family ");  
    switch (aip->ai_family) {  
    case AF_INET:  
        printf("inet");  
        break;  
    case AF_INET6:  
        printf("inet6");  
        break;  
    case AF_UNIX:  
        printf("unix");  
        break;  
    case AF_UNSPEC:  
        printf("unspecified");  
        break;  
    default:  
        printf("unknown");  
    }  
}  
      
void
print_type(struct addrinfo *aip)  
{  
    printf(" type ");  
    switch (aip->ai_socktype) {  
    case SOCK_STREAM:  
        printf("stream");  
        break;  
    case SOCK_DGRAM:  
        printf("datagram");  
        break;  
    case SOCK_SEQPACKET:  
        printf("seqpacket");  
        break;  
    case SOCK_RAW:  
        printf("raw");  
        break;  
    default:  
        printf("unknown (%d)", aip->ai_socktype);  
    }  
}  
      
void
print_protocol(struct addrinfo *aip)  
{  
    printf(" protocol ");  
    switch (aip->ai_protocol) {  
    case 0:  
        printf("default");  
        break;  
    case IPPROTO_TCP:  
        printf("TCP");  
        break;  
    case IPPROTO_UDP:  
        printf("UDP");  
        break;  
    case IPPROTO_RAW:  
        printf("raw");  
        break;  
    default:  
        printf("unknown (%d)", aip->ai_protocol);  
    }  
}  
      
void
print_flags(struct addrinfo *aip)  
{  
    printf("flags");  
    if (aip->ai_flags == 0) {  
        printf(" 0");  
    } else {  
        if (aip->ai_flags & AI_PASSIVE)  
            printf(" passive");  
        if (aip->ai_flags & AI_CANONNAME)  
            printf(" canon");  
        if (aip->ai_flags & AI_NUMERICHOST)  
            printf(" numhost");  
#if defined(AI_NUMERICSERV)  
        if (aip->ai_flags & AI_NUMERICSERV)  
            printf(" numserv");  
#endif  
#if defined(AI_V4MAPPED)  
        if (aip->ai_flags & AI_V4MAPPED)  
            printf(" v4mapped");  
#endif  
#if defined(AI_ALL)  
        if (aip->ai_flags & AI_ALL)  
            printf(" all");  
#endif  
    }  
}  
      
int
main(int argc, char *argv[])  
{  
    struct addrinfo        *ailist, *aip;  
    struct addrinfo        hint;  
    struct sockaddr_in    *sinp;  
    const char             *addr;  
    int                 err;  
    char                 abuf[INET_ADDRSTRLEN];  
      
    if (argc != 3)  
        printf("usage: %s nodename service", argv[0]);  
    hint.ai_flags = AI_CANONNAME;  
    hint.ai_family = 0;  
    hint.ai_socktype = 0;  
    hint.ai_protocol = 0;  
    hint.ai_addrlen = 0;  
    hint.ai_canonname = NULL;  
    hint.ai_addr = NULL;  
    hint.ai_next = NULL;  
    if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)  
        printf("getaddrinfo error: %s", gai_strerror(err));  
    for (aip = ailist; aip != NULL; aip = aip->ai_next) {  
        print_flags(aip);  
        print_family(aip);  
        print_type(aip);  
        print_protocol(aip);  
        printf("\n\thost %s", aip->ai_canonname?aip->ai_canonname:"-");  
        if (aip->ai_family == AF_INET) {  
            sinp = (struct sockaddr_in *)aip->ai_addr;  
            addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf,INET_ADDRSTRLEN);  
            printf(" address %s", addr?addr:"unknown");  
            printf(" port %d", ntohs(sinp->sin_port));  
        }  
        printf("\n");  
    }  
    exit(0);  
}

代碼說明:sinp = (struct sockaddr_in *)aip->ai_addr;會將struct sockaddr 變量強制轉化為struct sockaddr_in 類型

inet_ntop函數用於在二進制格式與點分十進制格式表示(a.b.c.d)之間進行轉換

#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);  
}
Copyright © Linux教程網 All Rights Reserved