IP地址结构体与字符串转换函数详解
在Linux C网络编程中,IP地址的二进制结构体(如struct in_addr
)与字符串形式(如"192.168.1.1"
)之间的转换经常涉及到,与IP地址格式相关的函数包括inet_aton
、inet_addr
、inet_ntoa
、inet_pton
、inet_ntop
、getaddrinfo
和getnameinfo
等。
1. inet_aton函数(已过时)
仅支持IPv4,不推荐在新代码中使用
inet_aton
是Linux系统中的一个函数,用于将点分十进制格式的IPv4地址转换为网络字节序的二进制形式。
- 头文件 :
#include <arpa/inet.h>
- 函数原型 :
int inet_aton(const char *cp, struct in_addr *inp);
- 参数:
cp
:指向点分十进制格式的IPv4地址字符串的指针。inp
:指向struct in_addr
的指针,用于存储转换后的二进制地址。- 返回值:如果转换成功,返回非零值;如果转换失败(例如,提供的字符串不是有效的IPv4地址),返回零。
2. inet_addr函数
inet_addr
函数将一个点分十进制的IP转换成一个网络字节序整数
- 头文件 :
#include <arpa/inet.h>
- 函数原型 :
in_addr_t inet_addr(const char* strptr);
- 参数 :
strptr
是指向点分十进制IP地址字符串的指针。 - 返回值 :若字符串有效,则将其转换为32位二进制网络字节序的IPv4地址,否则返回
INADDR_NONE
。 - 缺点:无法处理255.255.255.255(返回INADDR_NONE)
3. inet_ntoa函数
inet_ntoa
函数用于将网络字节序的IPv4地址转换成点分十进制字符串表示。
- 头文件 :
#include <arpa/inet.h>
- 函数原型 :
char* inet_ntoa(struct in_addr in);
- 参数 :
in
是一个in_addr
结构体,其中包含要转换的网络字节序IPv4地址。 - 返回值:返回一个指向静态分配的字符串的指针,该字符串包含点分十进制的IP地址。由于返回的是指向静态数据的指针,因此该函数不可重入,且每次调用都会覆盖之前的结果。
4. inet_pton和inet_ntop函数
inet_pton
和inet_ntop
这两个函数能够处理IPv4和IPv6地址的转换。
- 头文件:
- Linux下:
#include <arpa/inet.h>
- 函数原型:
inet_pton
:int inet_pton(int af, const char *src, void *dst);
inet_ntop
:const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
- 参数:
af
:地址族,常用的是AF_INET
(IPv4)和AF_INET6
(IPv6)。src
:输入的字符串表示的IP地址(inet_pton
)或输入的二进制表示的IP地址(inet_ntop
)。dst
:输出的二进制表示的IP地址(inet_pton
)或输出的字符串表示的IP地址的缓冲区(inet_ntop
)。cnt
:inet_ntop
中缓冲区的大小,避免溢出,(推荐INET_ADDRSTRLEN或INET6_ADDRSTRLEN)。- 返回值:
inet_pton
:成功返回1,参数无效返回0,错误返回-1。inet_ntop
:成功返回字符串的首地址,错误返回NULL。
5. getaddrinfo函数
getaddrinfo
函数用于域名解析,将主机名和服务名映射到套接字地址结构。
- 头文件 :
#include <netdb.h>
- 函数原型 :
int getaddrinfo(const char *node, const char *service,const struct addrinfo *hints,struct addrinfo **res);
- 参数:
node
: 主机名或IP地址字符串。service
: 服务名或端口号(如"http"或"80")。hints
: 过滤结果的提示结构。res
: 输出参数,返回地址链表。- 返回值 :成功返回0,错误返回错误代码(需用
gai_strerror
解析)。 - 特点 :需用
freeaddrinfo
释放内存
6. getnameinfo函数
getnameinfo
函数用于将互联网协议地址(IPv4或IPv6)及其端口号转换为人类可读的主机名和服务名称。
- 头文件 :
#include <netdb.h>
- 函数原型 :
int getnameinfo(const struct sockaddr *sa, socklen_t salen, char *host, size_t hostlen, char *serv, size_t servlen, int flags);
- 参数:
sa
: 输入的套接字地址结构。host
: 输出主机名缓冲区。serv
: 输出服务名缓冲区。flags
: 控制标志(如NI_NUMERICHOST
强制返回数字地址)- 返回值:如果成功,返回0;如果出错,返回一个错误代码。
比较
函数 | 支持IPv6 | 线程安全 | 功能范围 | 推荐场景 |
---|---|---|---|---|
inet_aton | ❌ | ✅ | IPv4字符串→结构体 | 旧代码维护 |
inet_ntoa | ❌ | ❌ | IPv4结构体→字符串 | 避免使用 |
inet_pton | ✅ | ✅ | 字符串→二进制结构体 | 新代码,需IPv6支持 |
inet_ntop | ✅ | ✅ | 二进制结构体→字符串 | 新代码,需IPv6支持 |
getaddrinfo | ✅ | ✅ | 主机名→地址结构体链表 | 需要DNS解析或多协议支持 |
getnameinfo | ✅ | ✅ | 地址结构体→主机名/服务 | 反向解析或获取服务名 |
示例
使用inet_pton和inet_ntop
c
#include <stdio.h>
#include <arpa/inet.h>
int main() {
// IPv4转换示例
struct in_addr ipv4_addr;
inet_pton(AF_INET, "192.168.1.1", &ipv4_addr);
char ipv4_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &ipv4_addr, ipv4_str, INET_ADDRSTRLEN);
printf("IPv4: %s\n", ipv4_str);
// IPv6转换示例
struct in6_addr ipv6_addr;
inet_pton(AF_INET6, "2001:db8::1", &ipv6_addr);
char ipv6_str[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &ipv6_addr, ipv6_str, INET6_ADDRSTRLEN);
printf("IPv6: %s\n", ipv6_str);
return 0;
}
使用getaddrinfo
c
#include <stdio.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main() {
struct addrinfo hints, *res, *p;
memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // 支持IPv4/IPv6
hints.ai_socktype = SOCK_STREAM;
int status = getaddrinfo("example.com", "http", &hints, &res);
if (status != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
return 1;
}
// 遍历地址链表
for (p = res; p != NULL; p = p->ai_next) {
void *addr;
char ipstr[INET6_ADDRSTRLEN];
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
}
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf("IP: %s\n", ipstr);
}
freeaddrinfo(res); // 必须释放内存
return 0;
}