DNS(IP)与服务名(端口)解析
DNS(IP)与服务名(端口)解析
1. getaddrinfo()
功能
将主机名和服务名转换为套接字地址结构列表,完成协议无关的地址解析。支持IPv4/IPv6双栈处理,自动过滤不支持的协议。
函数声明
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
参数解析
host:主机名或IP地址字符串
- 若为域名(如 "example.com"):触发 DNS 查询。域名字符串长度不小于netdb.h中定义的NI_MAXHOST(需启用_BSD_SOURCE)。
- 若为IPv4/IPv6 地址(如 "192.168.1.1" 或 "::1"):直接解析为地址结构。IP地址字符串长度不小于<netinet/in.h>中定义的INET_ADDRSTRLEN或INET6_ADDRSTRLEN。
- 若为NULL:若在 hint 中指定 AI_PASSIVE,表明服务端程序需要绑定到本地所有可用网络接口如(0.0.0.0 或 ::);若未指定,仅将服务名(如 "http")转换为端口号(如 80),不涉及主机名解析。
service:服务名或端口号字符串
- 可接受服务名(如"http")或十进制端口字符串(如"80")。服务字符串不小于netdb.h中定义的NI_MAXSERV(需启用_BSD_SOURCE)。十进制端口字符串无长度要求。
- NULL表示不指定端口
hints:过滤结果的模板结构体
struct addrinfo { int ai_flags; // 控制选项 int ai_family; // 地址族 int ai_socktype; // 套接字类型 int ai_protocol; // 协议类型 socklen_t ai_addrlen; // 地址结构长度 struct sockaddr *ai_addr; // 套接字地址 char *ai_canonname; // 规范主机名 struct addrinfo *ai_next; // 链表指针 };
ai_flags 组合值:
AI_PASSIVE /* 不设置,就代表你是客户端,期望获取指定域名的服务器的套接字地址进行套接字连接。 若host为空而service非空,则表明客户端希望getaddrinfo返回给其本地回环ip以通过bind绑定; 若host非空而service为空,则表明客户端希望getaddrinfo返回给其一个0号端口,而0号端口会导致bind绑定一个空 闲临时端口以供发送 若设置,就代表你是服务器,期望获取能够被客户端访问到的套接字地址, 若host为空而service非空,则表明服务器希望getaddrinfo返回给其通配ip以监听所有网卡的service请求; 若host非空而service为空,则表明服务器希望getaddrinfo返回给其一个0号端口,而0号端口会导致bind绑定一个空 闲临时端口以供接收*/ AI_CANONNAME // 返回规范主机名 AI_NUMERICHOST // 禁止域名解析成IP地址,以减少函数不必要的检查(即我们必须传入一个IP地址字符串而非域名或NULL) AI_NUMERICSERV // 禁止服务解析成端口,以减少函数不必要的检查(即我们必须传入一个端口号而非服务名) AI_V4MAPPED // IPv6返回IPv4映射地址 AI_ALL // 同时返回IPv4和IPv6映射
注意:某些系统(如 Linux)可能不允许 AI_CANONNAME
和 AI_PASSIVE
同时使用,因为 AI_PASSIVE
通常用于服务器监听,而 AI_CANONNAME
主要用于客户端查询主机名。
ai_family 取值:
AF_UNSPEC // 允许任意地址族 AF_INET // IPv4 AF_INET6 // IPv6
ai_socktype 取值:
SOCK_STREAM // 流式套接字 SOCK_DGRAM // 数据报套接字 0 // 任意类型
- res:返回的地址链表头指针
返回值
- 0:成功
非零错误码:失败,需用gai_strerror()转换
EAI_BADFLAGS // 无效的ai_flags EAI_NONAME // 无法解析名称 EAI_AGAIN // 临时故障 EAI_FAIL // 不可恢复错误 EAI_MEMORY // 内存分配失败 EAI_SYSTEM // 系统错误(检查errno)
2. freeaddrinfo()
功能
释放getaddrinfo()动态分配的地址链表,避免内存泄漏。
函数声明
#include <netdb.h>
void freeaddrinfo(struct addrinfo *res);
参数解析
- res:getaddrinfo返回的地址链表头指针
返回值
无返回值
3. gai_strerror()
功能
将getaddrinfo()/getnameinfo()的错误码转换为可读字符串。
函数声明
#include <netdb.h>
const char *gai_strerror(int errcode);
参数解析
- errcode:getaddrinfo()或getnameinfo()返回的错误码
返回值
- 对应错误描述字符串(静态存储区,无需释放)
4. getnameinfo()
功能
将套接字地址转换为对应的主机名和服务名,实现逆向解析。
函数声明
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa, socklen_t salen,
char *host, socklen_t hostlen,
char *serv, socklen_t servlen,
int flags);
参数解析
sa:输入套接字地址结构指针
- 支持struct sockaddr_in/sockaddr_in6等类型
salen:地址结构实际长度
- sizeof(struct sockaddr_in) 等
host:输出主机名缓冲区
- 长度应至少为
NI_MAXHOST
(1025)
- 长度应至少为
hostlen:主机名缓冲区长度
- 使用宏定义:
NI_MAXHOST
- 使用宏定义:
serv:输出服务名缓冲区
- 长度应至少为
NI_MAXSERV
(32)
- 长度应至少为
servlen:服务名缓冲区长度
- 使用宏定义:
NI_MAXSERV
- 使用宏定义:
flags:控制标志组合
NI_NOFQDN // 仅返回主机名部分 NI_NUMERICHOST // 返回数字地址 NI_NAMEREQD // 无法解析时报错 NI_NUMERICSERV // 返回端口号而非服务名 NI_DGRAM // 指定数据报服务(用于端口映射)
返回值
- 0:成功
- 非零错误码:同getaddrinfo()
宏定义总结
// 名称大小
#define NI_MAXHOST 1025 //域名
#define NI_MAXSERV 32 //服务名
// IP地址presentation字符串最大长度
#define INET_ADDRSTRLEN 16//IPv4
#define INET6_ADDRSTRLEN 46 //IPv6
// 地址族
#define AF_UNSPEC 0
#define AF_INET 2
#define AF_INET6 10
// 套接字类型
#define SOCK_STREAM 1
#define SOCK_DGRAM 2
以上函数配合使用可实现健壮的跨平台网络编程,正确处理了内存管理和错误处理,是构建现代网络应用的基础设施。