2004-12-12 (修改)
IPv4 中使用 gethostbyname() 函数完成主机名到地址解析,但是该 API 不允许调用者指定所需地址类型的任何信息,返回的结构只包含了用于存储 IPv4 地址的空间。为了解决该问题, IPv6 中引入了 getaddrinfo() 的新 API ,它是协议无关的,既可用于 IPv4 也可用于 IPv6 。调用该函数会获得一个 addrinfo 结构的列表,调用的返回值是 addrinfo 的结构(列表)指针。
IPv4 中使用 gethostbyname() 函数完成主机名到地址解析,但是该 API 不允许调用者指定所需地址类型的任何信息,返回的结构只包含了用于存储 IPv4 地址的空间。为了解决该问题, IPv6 中引入了 getaddrinfo() 的新 API ,它是协议无关的,既可用于 IPv4 也可用于 IPv6 。调用该函数会获得一个 addrinfo 结构的列表,调用的返回值是 addrinfo 的结构(列表)指针。
本文结合在
WinowsXP
和
Windows2003 Server
上使用该函数的经验,对
getaddrinfo
函数和
addrinfo
数据结构进行介绍,并对其参数的设置加以讨论,主要包括
nodename
和
servname
的取值对返回值的影响,
hints
成员变量的设置对返回值的影响等。
可能有不完全或不准确的地方,欢迎大家讨论并指出。
1.getaddrinfo函数原型
函数
|
参数说明
|
int getaddrinfo(
const char* nodename
const char* servname,
const struct addrinfo* hints,//
struct addrinfo** res
);
|
nodename:
节点名可以是主机名,也可以是数字地址。(
IPV4
的
10
进点分,或是
IPV6
的
16
进制)
servname:
包含十进制数的端口号或服务名如(
ftp,http
)
hints:
是一个空指针或指向一个
addrinfo
结构的指针,由调用者填写关于它所想返回的信息类型的线索。
res:
存放返回
addrinfo
结构链表的指针
|
Getaddrinfo
提供独立于协议的名称解析。
函数的前两个参数分别是节点名和服务名。节点名可以是主机名,也可以是地址串
(IPv4
的点分十进制数表示或
IPv6
的十六进制数字串
)
。服务名可以是十进制的端口号,也可以是已定义的服务名称,如
ftp
、
http
等。注意:其中节点名和服务名都是可选项,即节点名或服务名可以为
NULL
,此时调用的结果将取缺省设置,后面将详细讨论。
函数的第三个参数
hints
是
addrinfo
结构的指针,由调用者填写关于它所想返回的信息类型的线索。函数的返回值是一个指向
addrinfo
结构的链表指针
res
。
2.addrinfo结构
结构
|
固定的参数
|
typedef struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char* ai_canonname;
struct sockaddr* ai_addr;
struct addrinfo* ai_next;
}
|
ai_addrlen must be zero or a null pointer
ai_canonname must be zero or a null pointer
ai_addr must be zero or a null pointer
ai_next must be zero or a null pointer
|
可以改动的参数
| |
ai_flags:AI_PASSIVE,AI_CANONNAME,AI_NUMERICHOST
ai_family:
AF_INET,AF_INET6
ai_socktype:
SOCK_STREAM,SOCK_DGRAM
ai_protocol:
IPPROTO_IP, IPPROTO_IPV4, IPPROTO_IPV6 etc.
|
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
不作改动。
其中
ai_flags
、
ai_family
、
ai_socktype
说明如下:
参数
|
取值
|
值
|
说明
|
ai_family
|
AF_INET
|
2
|
IPv4
|
AF_INET6
|
23
|
IPv6
| |
AF_UNSPEC
|
0
|
协议无关
| |
ai_protocol
|
IPPROTO_IP
|
0
|
IP
协议
|
IPPROTO_IPV4
|
4
|
IPv4
| |
IPPROTO_IPV6
|
41
|
IPv6
| |
IPPROTO_UDP
|
17
|
UDP
| |
IPPROTO_TCP
|
6
|
TCP
| |
ai_socktype
|
SOCK_STREAM
|
1
|
流
|
SOCK_DGRAM
|
2
|
数据报
| |
ai_flags
|
AI_PASSIVE
|
1
|
被动的,用于
bind
,通常用于
server socket
|
AI_CANONNAME
|
2
|
| |
AI_NUMERICHOST
|
4
|
地址为数字串
|
对于
ai_flags
值的说明:
AI_NUMERICHOST
|
AI_CANONNAME
|
AI_PASSIVE
|
0/1
|
0/1
|
0/1
|
如上表所示,
ai_flagsde
值范围为
0~7
,取决于程序如何设置
3
个标志位,比如设置
ai_flags
为
“AI_PASSIVE|AI_CANONNAME”
,
ai_flags
值就为
3
。三个参数的含义分别为:
(1)AI_PASSIVE
当此标志置位时,表示调用者将在
bind()
函数调用中使用返回的地址结构。当此标志不置位时,表示将在
connect()
函数调用中使用。
当节点名位
NULL
,且此标志置位,则返回的地址将是通配地址。
如果节点名
NULL
,且此标志不置位,则返回的地址将是回环地址。
(2)AI_CANNONAME
当此标志置位时,在函数所返回的第一个
addrinfo
结构中的
ai_cannoname
成员中,应该包含一个以空字符结尾的字符串,字符串的内容是节点名的正规名。
(3)AI_NUMERICHOST
当此标志置位时,此标志表示调用中的节点名必须是一个数字地址字符串。
4.实际使用的几种常用设置
一般情况下,
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] (在XP下,即使servname不为空,返回的地址仍然是[::1],即XP下只要nodename是本机名,返回的地址就是[::1],无法返回别的 地址,不知道是不是XP实现时的问题,因为[::1]只能用作本机测试,绑定在其上的socket是无法被别的机器寻址的)
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
。
5.几句废话
在
Windows
环境调试
IPv6
的程序个人感觉还是使用
WinXP
(
SP2
)和
Win2003
基本上没有太大的区别,使用
Win2003
更规范一些。
用
VC
编写和调试
IPv6
的程序一定要安装
Windows
较新的
SDK
,我安装的是
MS_Platform_SDK_Feb_2003
,否则库函数和头文件可能都会有问题。
[
参考
]
[1]MSDN Library – January 2
004
ms-help://MS.MSDNQTR.2004JAN.1033/winsock/winsock/getaddrinfo_2.htm
ms-help://MS.MSDNQTR.2004JAN.1033/winsock/winsock/addrinfo_2.htm
[2]
《理解IPv6》("Understanding IPv6”),清华大学出版社