用C语言获取域名对应的IP地址,核心就是调用系统提供的DNS解析函数,最常用的是getaddrinfo(),它能同时处理IPv4和IPv6,并返回可用于socket连接的结构体。
为什么推荐getaddrinfo()而不是老牌的gethostbyname()
很多初学者在网上搜索“C语言域名解析IP”时,会看到大量教程还在教gethostbyname(),行业共识认为,gethostbyname()是上世纪的标准,它只支持IPv4,而且不可重入,在多线程环境下容易出问题,虽然它在Linux和Windows上依然能用,但对于2026年的新项目,直接用getaddrinfo()才是正确选择。
getaddrinfo()的优势非常明显:
- 同时支持IPv4(AF_INET)和IPv6(AF_INET6),无需写两套逻辑
- 可重入,线程安全,适合在高并发服务中使用
- 返回的addrinfo链表可以直接传给socket()和connect(),省去手动填充结构体的麻烦
- 错误提示更详细,通过gai_strerror()可以拿到人类可读的报错信息
一个完整的域名转IP示例
下面是一个可以直接编译运行的C程序,它查询指定域名的IP地址并打印出来:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc, char argv[]) {
if (argc != 2) {
fprintf(stderr, "用法: %s <域名>\n", argv[0]);
return 1;
}
struct addrinfo hints, res, p;
char ipstr[INET6_ADDRSTRLEN];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // 不限定协议族,IPv4/IPv6都行
hints.ai_socktype = SOCK_STREAM;
int status = getaddrinfo(argv[1], NULL, &hints, &res);
if (status != 0) {
fprintf(stderr, "解析失败: %s\n", gai_strerror(status));
return 2;
}
printf("域名 %s 对应的IP地址:\n", argv[1]);
for (p = res; p != NULL; p = p->ai_next) {
void addr;
if (p->ai_family == AF_INET) {
struct sockaddr_in ipv4 = (struct sockaddr_in )p->ai_addr;

addr = &(ipv4->sin_addr);
} else {
struct sockaddr_in6 ipv6 = (struct sockaddr_in6 )p->ai_addr;
addr = &(ipv6->sin6_addr);
}
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));
printf(" %s\n", ipstr);
}
freeaddrinfo(res);
return 0;
}
编译命令:
gcc -o resolve resolve.c ./resolve www.baidu.com
运行后你会看到百度域名解析出来的多个IP地址,这个程序在Linux和macOS上都能直接编译运行,Windows下则需要先调用WSAStartup()初始化Winsock。
linux下C语言域名转IP的详细实现步骤
如果你在Linux服务器上做开发,上面这段代码已经够用了,但实际项目中,我们通常不只是打印IP,而是要把IP存起来或者直接建立连接,下面拆解每一步的关键细节。
第一步:填充hints结构体
hints用于控制解析行为,两个最常见的坑是:
- 不memset就使用:hints里残留垃圾值,导致getaddrinfo行为不可预测
- ai_family乱填:填AF_INET只查IPv4,填AF_INET6只查IPv6,填AF_UNSPEC则让系统自动选择,多数情况下用AF_UNSPEC最省心
第二步:解析结果链表怎么遍历
getaddrinfo()返回的res是一个链表,一个域名可能对应多个IP(比如负载均衡场景),遍历时注意以下几点:
- 每个节点代表一个可用的地址信息
- ai_addr成员指向sockaddr结构体,可以直接传给connect()
- 如果觉得某个IP连接失败,可以继续尝试链表中的下一个,这比手动写重试逻辑方便得多
第三步:内存释放
调用freeaddrinfo(res)释放整个链表,忘记释放会导致内存泄漏,在长期运行的服务进程里后果严重。
第四步:如何判断解析是否成功
返回值是0表示成功,非0表示失败,用gai_strerror()打印错误信息,常见的错误码包括:
- EAI_NONAME:域名不存在
- EAI_AGAIN:临时失败,可能是DNS服务器没有响应
- EAI_MEMORY:内存不足

用C语言获取域名IP地址的跨平台指南
不同操作系统对getaddrinfo的支持略有差异,如果你要写跨平台代码,需要注意以下对比:
| 系统 | 头文件 | 初始化要求 | 注意事项 |
|---|---|---|---|
| Linux | netdb.h, sys/socket.h | 无需初始化 | 几乎完美支持 |
| macOS | 同上 | 无需初始化 | 与Linux基本一致 |
| Windows | winsock2.h, ws2tcpip.h | 必须调用WSAStartup() | 链接ws2_32.lib |
Windows平台的额外步骤
在Windows上,除了包含头文件,还必须在调用getaddrinfo()之前执行:
WSADATA wsaData; WSAStartup(MAKEWORD(2, 2), &wsaData);
程序退出前记得调用WSACleanup(),这里的细节是很多初学者在把Linux代码迁移到Windows时报错的主要原因。
C语言解析域名失败时怎么办
实际开发中,DNS解析并不总是顺利的,下面是一些常见的排查思路。
检查系统DNS配置
在Linux上执行cat /etc/resolv.conf,确认nameserver指向的DNS服务器是否可用,如果配置了错误的DNS地址,getaddrinfo()会超时,表现为程序卡住几秒后返回EAI_AGAIN。
区分“域名不存在”和“网络不通”
EAI_NONAME表示DNS服务器返回了“查无此域名”,而EAI_AGAIN多表示网络层面没收到响应,前者检查域名拼写,后者检查到DNS服务器的连通性。
本地hosts文件的影响
getaddrinfo()会先读取系统的hosts文件(Linux是/etc/hosts,Windows是C:\Windows\System32\drivers\etc\hosts),再查询DNS服务器,如果你改了hosts,解析结果可能跟公网DNS不一致,在调试时可以用getent hosts <域名>命令对照查看系统实际解析结果。
获取域名对应IP的实际应用场景
端口扫描工具
很多安全工具需要把域名批量转换为IP,然后对IP段做端口扫描,用getaddrinfo()逐个解析,性能瓶颈在DNS查询本身,而不是函数调用,如果想提速,可以考虑引入DNS缓存,或者用异步解析库。

HTTP客户端连接
在编写HTTP客户端时,通常不直接显式调用getaddrinfo(),而是用libcurl等库,它们内部已经封装好了解析逻辑,但如果你要自己实现socket连接,getaddrinfo()返回的addrinfo可以直接作为socket()和connect()的参数:
int sockfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol); connect(sockfd, p->ai_addr, p->ai_addrlen);
这样写的好处是不需要手动构造sockaddr_in,代码更健壮,因为ai_addr已经包含了正确的端口和地址族信息。
获取本机IP
有时你需要知道本机在某个网络接口上的IP地址,这时可以用getifaddrs()而不是getaddrinfo(),因为getaddrinfo()默认解析的是本机的主机名映射,不一定反映真实网卡地址,这种场景容易混淆,新手常在这个问题上走弯路。
常见问题解答
如何用C语言获取域名的第一个IP地址?
在遍历getaddrinfo()返回的链表时,取第一个节点即可,但要注意,第一个节点并不保证是“最快”或“最优”的,它只是系统排序后的结果,如果需要稳定的IP,建议将解析结果缓存,避免每次连接都重复查询。
gethostbyname和getaddrinfo的C语言实现区别是什么?
gethostbyname是旧版API,只能返回IPv4地址,且内部使用静态缓冲区,多次调用会覆盖之前的结果,getaddrinfo是新版替代方案,支持IPv6且线程安全,建议新代码一律使用getaddrinfo,老项目维护时再考虑兼容gethostbyname。
域名解析IP地址超时如何设置?
getaddrinfo()本身没有提供超时参数,它依赖系统解析器的配置,在Linux上可以修改/etc/resolv.conf中的options timeout:n和attempts:n来控制超时时间和重试次数,如果需要在应用层控制超时,可以把解析放到独立的线程中,用条件变量或future来限制等待时间。