这万恶的结构体啊!!!
一、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,线程安全,推荐使用。