域名解析是将人类可读的域名(如www.baidu.com)转换为机器可识别的IP地址(如220.181.38.148)的过程。在网络编程中,这是建立连接的关键第一步。Linux提供了两种主要API实现这一功能:
// 传统方法
struct hostent* gethostbyname(const char* name);
// 现代方法
int getaddrinfo(const char* node, const char* service,
const struct addrinfo* hints,
struct addrinfo** res);
struct hostent* pHostent = gethostbyname("www.baidu.com");
if (pHostent) {
// 获取第一个IP地址
in_addr_t ip = *((unsigned long*)pHostent->h_addr_list[0]);
printf("IP: %s\n", inet_ntoa(*(struct in_addr*)&ip));
}
struct hostent {
char* h_name; // 官方主机名
char** h_aliases; // 别名列表
int h_addrtype; // 地址类型(AF_INET/AF_INET6)
int h_length; // 地址长度(4 for IPv4, 16 for IPv6)
char** h_addr_list; // IP地址列表
};
#define h_addr h_addr_list[0] // 第一个IP地址
// 错误处理示例
if (pHostent == NULL) {
herror("gethostbyname failed");
switch (h_errno) {
case HOST_NOT_FOUND: // 主机未找到
case TRY_AGAIN: // 临时错误,可重试
case NO_RECOVERY: // 不可恢复错误
case NO_DATA: // 有效域名但无IP
}
}
int getaddrinfo(const char* node, // 域名或IP字符串
const char* service, // 端口号或服务名("http"/"80")
const struct addrinfo* hints, // 过滤条件
struct addrinfo** res); // 结果集
struct addrinfo {
int ai_flags; // AI_PASSIVE, AI_CANONNAME等
int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
int ai_protocol; // 0 或 IPPROTO_TCP等
socklen_t ai_addrlen; // 地址长度
struct sockaddr* ai_addr; // 套接字地址
char* ai_canonname; // 规范名称
struct addrinfo* ai_next; // 下一结果(链表)
};
struct addrinfo hints = {0};
hints.ai_family = AF_UNSPEC; // 同时支持IPv4/IPv6
hints.ai_socktype = SOCK_STREAM;
struct addrinfo* result;
int status = getaddrinfo("www.baidu.com", "80", &hints, &result);
if (status != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
return;
}
// 遍历所有结果
for (struct addrinfo* p = result; p != NULL; p = p->ai_next) {
void* addr;
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);
}
char ipstr[INET6_ADDRSTRLEN];
inet_ntop(p->ai_family, addr, ipstr, sizeof ipstr);
printf("IP: %s\n", ipstr);
}
freeaddrinfo(result); // 必须释放内存
特性 | gethostbyname | getaddrinfo |
---|---|---|
协议支持 | 仅IPv4 | IPv4/IPv6双栈 |
线程安全 | 否 | 是 |
错误处理 | h_errno特殊机制 | 标准返回值 |
结果格式 | 单一结构体 | 链表结构 |
端口支持 | 需要额外处理 | 直接集成 |
阻塞性质 | 同步阻塞 | 可配置异步 |
现代推荐 | 已废弃 | 首选方案 |
重要提示:在Linux新版本中,gethostbyname已被标记为废弃,getaddrinfo是官方推荐替代方案
// 设置DNS解析超时(全局)
res_init();
_res.retrans = 5; // 超时秒数
_res.retry = 2; // 重试次数
// 使用libevent实现非阻塞解析
struct evdns_base* dnsbase = evdns_base_new(event_base, 1);
evdns_base_resolve_ipv4(dnsbase, "www.example.com",
0, dns_callback, NULL);
// 优先使用IPv6地址
for (res = result; res != NULL; res = res->ai_next) {
if (res->ai_family == AF_INET6) {
use_address(res);
break;
}
}
Redis的src/net.c
展示了生产级域名解析实现:
/* 尝试IPv4解析 */
hints.ai_family = AF_INET;
if ((rv = getaddrinfo(host, portstr, &hints, &servinfo)) != 0) {
/* 尝试IPv6解析 */
hints.ai_family = AF_INET6;
if ((rv = getaddrinfo(host, portstr, &hints, &servinfo)) != 0) {
__redisSetError(c, REDIS_ERR_OTHER, gai_strerror(rv));
return REDIS_ERR;
}
}
实现亮点:
DNS over HTTPS(DoH):
// 使用curl实现DoH
CURL* curl = curl_easy_init();
curl_easy_setopt(curl, CURLOPT_DOH_URL, "https://dns.google/dns-query");
多CDN智能解析:
dig www.taobao.com
;; ANSWER SECTION:
www.taobao.com. 600 IN CNAME www.taobao.com.danuoyi.tbcache.com
零配置网络(mDNS):
// Avahi库实现本地服务发现
avahi_service_resolver_new()
新项目选择:
getaddrinfo
AI_ADDRCONFIG
自动适配本机协议freeaddrinfo
严格管理内存传统项目迁移:
// 兼容层实现
struct hostent* gethostbyname(const char* name) {
static thread_local struct hostent ent;
struct addrinfo* res;
getaddrinfo(name, NULL, NULL, &res);
// 填充hostent结构...
return &ent;
}
高性能场景优化:
libevent
/libuv
异步解析黄金法则:在2023年及以后的新项目中,
getaddrinfo
应是域名解析的唯一选择。其协议中立性、线程安全性和扩展性为现代网络编程奠定了坚实基础。
C++服务端开发精髓