在网络编程中,特别是在使用套接字编程时,地址解析和管理是一个关键问题。为了简化这个过程,POSIX 标准定义了 addrinfo
结构体和相关函数。
一、addrinfo
结构体简介#
addrinfo
结构体用于存储地址信息,getaddrinfo
函数通过解析主机名和服务名生成这个结构体的链表。以下是 addrinfo
结构体的定义:
1
2
3
4
5
6
7
8
9
10
struct addrinfo {
int ai_flags ; // 输入标志
int ai_family ; // 地址簇(如 AF_INET、AF_INET6)
int ai_socktype ; // 套接字类型(如 SOCK_STREAM、SOCK_DGRAM)
int ai_protocol ; // 协议(如 IPPROTO_TCP、IPPROTO_UDP)
socklen_t ai_addrlen ; // 套接字地址的长度
struct sockaddr * ai_addr ; // 套接字地址
char * ai_canonname ; // 规范名
struct addrinfo * ai_next ; // 指向下一个 addrinfo 结构的指针
};
复制
二、成员变量详细解析#
(1)ai_flags
:指定 getaddrinfo
函数的行为,可以是以下标志的组合:
AI_PASSIVE
: 如果设置了该标志,表示返回的套接字地址用于绑定(绑定到 INADDR_ANY
或 in6addr_any
)。
AI_CANONNAME
: 返回主机的规范名称,通过 ai_canonname
成员指向。
AI_NUMERICHOST
: 以数字字符串形式返回主机地址,而不是名称。
AI_NUMERICSERV
: 以数字字符串形式返回服务地址,而不是名称。
AI_V4MAPPED
: 如果没有找到 IPv6 地址,则返回 IPv4 映射的 IPv6 地址。
AI_ALL
: 返回 IPv4 和 IPv6 地址。
AI_ADDRCONFIG
: 仅在系统中配置的地址类型上返回地址。
1
2
3
struct addrinfo hints ;
memset ( & hints , 0 , sizeof hints );
hints . ai_flags = AI_PASSIVE | AI_CANONNAME ;
复制
(2)ai_family
:指定地址簇,可以是以下值:
AF_INET
: IPv4 协议
AF_INET6
: IPv6 协议
AF_UNSPEC
: 未指定(既可以是 IPv4 也可以是 IPv6)
1
hints . ai_family = AF_INET ; // 仅返回 IPv4 地址
复制
(3)ai_socktype
:指定套接字类型,可以是以下值:
SOCK_STREAM
: 流套接字(如 TCP)
SOCK_DGRAM
: 数据报套接字(如 UDP)
SOCK_RAW
: 原始套接字
其他类型根据系统而定
1
hints . ai_socktype = SOCK_STREAM ; // 返回流套接字地址
复制
(4)ai_protocol
:指定协议,可以是以下值:
IPPROTO_TCP
: TCP 协议
IPPROTO_UDP
: UDP 协议
其他协议根据系统而定
1
hints . ai_protocol = IPPROTO_TCP ; // 返回 TCP 协议地址
复制
(5)ai_addrlen
:指定套接字地址的长度。
这个成员变量一般由 getaddrinfo
函数设置,无需手动设置。
(6)ai_addr
:指向套接字地址的指针。
同样的,这个成员变量一般由 getaddrinfo
函数设置,无需手动设置。
(7)ai_canonname
:指向主机的规范名称的指针。
仅在设置 AI_CANONNAME
标志时返回。
(8)ai_next
:指向下一个 addrinfo
结构的指针,形成一个链表。
也是啦,这个一般由 getaddrinfo
函数设置,无需手动设置。
三、示例程序#
以下是一个示例程序,展示如何使用 getaddrinfo
解析域名,并通过 addrinfo
结构体访问地址信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <iostream>
#include <cstring>
#include <netdb.h>
#include <arpa/inet.h>
#include <unistd.h>
void resolveDomain ( const std :: string & domain , const std :: string & service ) {
struct addrinfo hints , * res , * p ;
char ipstr [ INET6_ADDRSTRLEN ];
int status ;
memset ( & hints , 0 , sizeof hints );
hints . ai_family = AF_UNSPEC ; // 支持IPv4和IPv6
hints . ai_socktype = SOCK_STREAM ; // TCP
hints . ai_flags = AI_PASSIVE | AI_CANONNAME ; // 设置标志
if (( status = getaddrinfo ( domain . c_str (), service . c_str (), & hints , & res )) != 0 ) {
std :: cerr << "getaddrinfo: " << gai_strerror ( status ) << std :: endl ;
return ;
}
std :: cout << "IP addresses for " << domain << " on service " << service << ":" << std :: endl ;
for ( p = res ; p != nullptr ; p = p -> ai_next ) {
void * addr ;
std :: string ipver ;
// 获取地址
if ( p -> ai_family == AF_INET ) { // IPv4
struct sockaddr_in * ipv4 = ( struct sockaddr_in * ) p -> ai_addr ;
addr = & ( ipv4 -> sin_addr );
ipver = "IPv4" ;
} else { // IPv6
struct sockaddr_in6 * ipv6 = ( struct sockaddr_in6 * ) p -> ai_addr ;
addr = & ( ipv6 -> sin6_addr );
ipver = "IPv6" ;
}
// 转换IP为字符串
inet_ntop ( p -> ai_family , addr , ipstr , sizeof ipstr );
std :: cout << " " << ipver << ": " << ipstr << std :: endl ;
if ( hints . ai_flags & AI_CANONNAME && p -> ai_canonname ) {
std :: cout << " Canonical name: " << p -> ai_canonname << std :: endl ;
}
}
freeaddrinfo ( res );
}
int main ( int argc , char * argv []) {
if ( argc != 3 ) {
std :: cerr << "Usage: " << argv [ 0 ] << " <domain> <port>" << std :: endl ;
return 1 ;
}
std :: string domain = argv [ 1 ];
std :: string service = argv [ 2 ];
resolveDomain ( domain , service );
return 0 ;
}
复制
3.1 运行示例#
编译:
1
g++ -o resolveDomain resolveDomain.cpp
复制
运行:
1
./resolveDomain example.com 80
复制
示例输出
1
2
3
4
5
IP addresses for example.com on service 80:
IPv4: 93.184.216.34
Canonical name: example.com
IPv6: 2606:2800:220:1:248:1893:25c8:1946
Canonical name: example.com
复制