这万恶的结构体啊!!!


一、hostent 结构体概述

1.1 定义与作用

hostent 结构体用于描述主机的信息,包括主机名、别名、地址类型、地址长度和地址列表等。它通常在使用域名解析函数(如 gethostbyname()gethostbyaddr())时返回,帮助程序获取主机的网络地址信息。

1.2 结构体定义

在头文件 <netdb.h> 中,hostent 结构体定义如下:

1
2
3
4
5
6
7
struct hostent {
    char  *h_name;        /* Official name of the host */
    char **h_aliases;     /* Null-terminated array of alternative names */
    int    h_addrtype;    /* Address type (e.g., AF_INET) */
    int    h_length;      /* Length of the address in bytes */
    char **h_addr_list;   /* Null-terminated array of addresses */
};

为了兼容,通常会定义一个宏:

1
#define h_addr h_addr_list[0]  /* For backward compatibility */

二、hostent 结构体的成员详解

2.1 h_name:主机的正式名称

  • 类型char *
  • 含义:主机的官方全称,通常是一个完全限定域名(FQDN,Fully Qualified Domain Name),如 "www.example.com"

2.2 h_aliases:主机的别名列表

  • 类型char **(以 NULL 结尾的字符串数组)
  • 含义:主机的其他名称或别名列表,可以通过这些别名访问同一主机。

2.3 h_addrtype:地址类型

  • 类型int
  • 含义:指定地址的类型,常见的值有:
    • AF_INET:IPv4 地址
    • AF_INET6:IPv6 地址

2.4 h_length:地址长度

  • 类型int
  • 含义:地址的长度,以字节为单位。
    • 对于 IPv4,h_length 等于 4(因为 IPv4 地址占 4 个字节)。
    • 对于 IPv6,h_length 等于 16(因为 IPv6 地址占 16 个字节)。

2.5 h_addr_list:主机地址列表

  • 类型char **(以 NULL 结尾的字节数组指针数组)
  • 含义:主机的网络地址列表,每个地址都是一个 h_length 字节长的二进制地址。
  • 注意:地址以网络字节序存储。

2.6 h_addr:第一个地址(兼容宏)

  • 宏定义#define h_addr h_addr_list[0]
  • 含义:为了兼容早期代码,h_addr 提供了获取第一个地址的快捷方式。

三、主机名解析函数与 hostent

3.1 gethostbyname()

3.1.1 函数原型

1
2
3
#include <netdb.h>

struct hostent *gethostbyname(const char *name);

3.1.2 参数与返回值

  • 参数:主机名字符串,如 "www.example.com"
  • 返回值:指向 hostent 结构体的指针,包含解析后的主机信息;如果失败,返回 NULL

3.1.3 用法示例

1
2
3
4
struct hostent *host = gethostbyname("www.example.com");
if (host == NULL) {
    // 处理错误
}

3.2 gethostbyaddr()

3.2.1 函数原型

1
2
3
#include <netdb.h>

struct hostent *gethostbyaddr(const void *addr, socklen_t len, int type);

3.2.2 参数与返回值

  • addr:指向网络字节序的地址数据。
  • len:地址长度(h_length)。
  • type:地址类型(h_addrtype)。
  • 返回值:指向 hostent 结构体的指针;失败返回 NULL

3.2.3 用法示例

1
2
3
4
5
6
struct in_addr ipv4_addr;
inet_pton(AF_INET, "93.184.216.34", &ipv4_addr);
struct hostent *host = gethostbyaddr(&ipv4_addr, sizeof(ipv4_addr), AF_INET);
if (host == NULL) {
    // 处理错误
}

3.3 注意事项

  • 线程安全性gethostbyname()gethostbyaddr() 并非线程安全,可能返回指向静态内存的指针,多个线程调用可能导致数据混乱。
  • 替代函数:建议使用线程安全的函数,如 gethostbyname_r()gethostbyaddr_r(),或者使用现代的 getaddrinfo() 函数。

四、hostent 结构体的使用示例

4.1 获取主机 IP 地址列表

 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
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>

void print_host_info(const char *hostname) {
    struct hostent *host = gethostbyname(hostname);
    if (host == NULL) {
        fprintf(stderr, "Failed to resolve hostname: %s\n", hostname);
        return;
    }

    printf("Official name: %s\n", host->h_name);

    // 打印别名列表
    char **alias = host->h_aliases;
    printf("Aliases:\n");
    while (*alias != NULL) {
        printf("  %s\n", *alias);
        alias++;
    }

    // 打印 IP 地址列表
    char **addr_list = host->h_addr_list;
    char ip_str[INET6_ADDRSTRLEN];

    printf("Addresses:\n");
    while (*addr_list != NULL) {
        if (host->h_addrtype == AF_INET) {
            struct in_addr *addr = (struct in_addr *)*addr_list;
            inet_ntop(AF_INET, addr, ip_str, sizeof(ip_str));
        } else if (host->h_addrtype == AF_INET6) {
            struct in6_addr *addr6 = (struct in6_addr *)*addr_list;
            inet_ntop(AF_INET6, addr6, ip_str, sizeof(ip_str));
        } else {
            strncpy(ip_str, "Unknown AF", sizeof(ip_str));
        }
        printf("  %s\n", ip_str);
        addr_list++;
    }
}

int main() {
    print_host_info("www.example.com");
    return 0;
}

4.2 解析 IP 地址对应的主机名

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <netdb.h>
#include <arpa/inet.h>

void print_reverse_dns(const char *ip_address) {
    struct in_addr ipv4_addr;
    inet_pton(AF_INET, ip_address, &ipv4_addr);

    struct hostent *host = gethostbyaddr(&ipv4_addr, sizeof(ipv4_addr), AF_INET);
    if (host == NULL) {
        fprintf(stderr, "Failed to perform reverse DNS lookup for: %s\n", ip_address);
        return;
    }

    printf("Host name for IP %s: %s\n", ip_address, host->h_name);
}

int main() {
    print_reverse_dns("93.184.216.34");
    return 0;
}

五、实践中的注意事项

5.1 线程安全问题

  • 风险gethostbyname() 返回的数据存储在静态区域,多线程同时调用可能导致数据混淆。
  • 解决方案
    • 使用线程安全的函数,如 gethostbyname_r()
    • 使用现代的 getaddrinfo(),它是线程安全的,功能更强大。

5.2 IPv6 支持

  • 问题gethostbyname() 对 IPv6 支持有限,可能无法解析 IPv6 地址。
  • 解决方案:使用 getaddrinfo(),它支持 IPv4 和 IPv6,并提供统一的接口。

5.3 地址转换

  • 网络字节序h_addr_list 中的地址是二进制形式,需要转换成人类可读的字符串。
  • 函数:使用 inet_ntop()(推荐)或 inet_ntoa()(IPv4 专用)进行地址转换。

5.4 错误处理

  • 返回值检查:当函数返回 NULL 时,应检查全局变量 h_errno 以确定错误原因。
  • 错误信息:使用 hstrerror(h_errno) 获取错误描述。
1
2
3
if (host == NULL) {
    fprintf(stderr, "Error: %s\n", hstrerror(h_errno));
}

六、替代方案:getaddrinfo()addrinfo 结构体

6.1 getaddrinfo() 的优势

  • 线程安全:适用于多线程环境。
  • 支持 IPv4 和 IPv6:统一处理不同协议族的地址解析。
  • 灵活的查询选项:通过 addrinfo 结构体可以指定更多的查询参数。

6.2 addrinfo 结构体定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
struct addrinfo {
    int              ai_flags;      /* Input flags */
    int              ai_family;     /* Address family */
    int              ai_socktype;   /* Socket type */
    int              ai_protocol;   /* Protocol */
    socklen_t        ai_addrlen;    /* Length of ai_addr */
    struct sockaddr *ai_addr;       /* Binary address */
    char            *ai_canonname;  /* Canonical name */
    struct addrinfo *ai_next;       /* Next structure in linked list */
};

6.3 使用示例

 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <arpa/inet.h>

void print_addresses(const char *hostname) {
    struct addrinfo hints, *res, *p;
    char ip_str[INET6_ADDRSTRLEN];

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_UNSPEC;    // AF_INET 或 AF_INET6
    hints.ai_socktype = SOCK_STREAM;

    int status = getaddrinfo(hostname, NULL, &hints, &res);
    if (status != 0) {
        fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
        return;
    }

    printf("IP addresses for %s:\n\n", hostname);

    for (p = res; p != NULL; p = p->ai_next) {
        void *addr;
        const char *ip_version;

        if (p->ai_family == AF_INET) {  // IPv4
            struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
            addr = &(ipv4->sin_addr);
            ip_version = "IPv4";
        } else if (p->ai_family == AF_INET6) {  // IPv6
            struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
            addr = &(ipv6->sin6_addr);
            ip_version = "IPv6";
        } else {
            continue;
        }

        inet_ntop(p->ai_family, addr, ip_str, sizeof(ip_str));
        printf("  %s: %s\n", ip_version, ip_str);
    }

    freeaddrinfo(res);
}

int main() {
    print_addresses("www.example.com");
    return 0;
}

七、拓展资料:关键概念解释

7.1 DNS(域名系统)

  • 定义:DNS 是域名系统(Domain Name System)的缩写,它将人类可读的主机名转换为机器可读的 IP 地址。
  • 作用:方便用户通过域名访问网络资源,而无需记忆复杂的 IP 地址。
  • 解析过程:当程序调用 gethostbyname() 等函数时,操作系统会查询本地缓存、Hosts 文件或通过网络向 DNS 服务器请求解析。

7.2 网络字节序与主机字节序

  • 字节序:指多字节数据在内存中的存储顺序。
    • 大端序(Big Endian):高字节存储在低地址。
    • 小端序(Little Endian):低字节存储在低地址。
  • 网络字节序:TCP/IP 协议规定使用大端序,称为网络字节序。
  • 转换函数htonl()htons()ntohl()ntohs()

7.3 inet_ntop()inet_pton()

  • 作用
    • inet_ntop():将网络字节序的二进制 IP 地址转换为点分十进制字符串表示。
    • inet_pton():将点分十进制字符串 IP 地址转换为网络字节序的二进制格式。
  • 优势:支持 IPv4 和 IPv6,线程安全,推荐使用。