在网络编程中,特别是在使用套接字编程时,地址解析和管理是一个关键问题。为了简化这个过程,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
|