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