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