Android NDK中resolv.h的res_query无法执行DNS查询问题咨询
res_query的问题分析与解决方案 首先明确:Android的Bionic libc确实包含res_query接口,但它的行为和Linux glibc存在关键差异,这正是你遇到查询失败(h_errno=2)的核心原因。下面详细拆解问题并给出解决方法:
为什么res_query会返回-1?
你看到的h_errno=2对应HOST_NOT_FOUND,但这并不是因为example.com不存在,而是Bionic的resolv实现不会自动读取Android系统的DNS服务器配置。
Linux glibc的res_query依赖/etc/resolv.conf获取DNS服务器,调用res_init()时会自动加载该文件。但Android系统没有传统的/etc/resolv.conf(部分设备可能有临时生成的,但不保证存在或有效),Bionic的res_init()不会主动从系统获取DNS地址,导致res_query没有可用的DNS服务器,最终返回失败。
解决方法:两种可行路径
路径1:手动配置DNS服务器给resolv库
你可以通过读取Android系统的DNS属性,手动设置给resolv结构体,让res_query知道该用哪个DNS服务器。
步骤如下:
- 读取系统属性
net.dns1、net.dns2等获取当前DNS服务器地址(Android 10+可能区分移动网络和WiFi,比如net.wlan0.dns1对应WiFi环境)。 - 将DNS地址转换为
sockaddr_in结构,赋值给_res.nsaddr_list。 - 直接使用配置后的结构体发起查询(无需额外调用
res_init())。
示例代码片段:
#include <jni.h> #include <android/log.h> #include <sys/types.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/nameser.h> #include <resolv.h> #include <sys/system_properties.h> #include <arpa/inet.h> #include <cstring> extern "C" JNIEXPORT jstring JNICALL Java_com_example_cpptest_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { // 读取系统DNS服务器 char dns_server[PROP_VALUE_MAX]; __system_property_get("net.dns1", dns_server); if (strlen(dns_server) == 0) { __android_log_print(ANDROID_LOG_DEBUG, "CppTest", "Failed to get DNS server"); return env->NewStringUTF("DNS: No server found."); } // 配置resolv全局结构体 memset(&_res, 0, sizeof(_res)); _res.nsaddr_list[0].sin_family = AF_INET; _res.nsaddr_list[0].sin_port = htons(53); inet_pton(AF_INET, dns_server, &_res.nsaddr_list[0].sin_addr); _res.nscount = 1; unsigned char buf[128]; const int result = res_query("example.com", C_IN, T_A, buf, 128); if (result < 0) { herror(NULL); __android_log_print(ANDROID_LOG_DEBUG, "CppTest", "DNS Error %d", h_errno); return env->NewStringUTF("DNS: Query failed."); } else return env->NewStringUTF("DNS: Query successful."); }
注:
__system_property_get属于libc,无需额外链接库即可使用。
路径2:使用Android推荐的POSIX网络接口
Android NDK更推荐使用getaddrinfo()或gethostbyname()这类标准POSIX函数,它们会自动适配Android的系统DNS配置,无需手动处理。这些函数内部会调用Android系统的DNS解析服务,兼容性和稳定性更好。
示例代码(用getaddrinfo替代res_query):
#include <jni.h> #include <android/log.h> #include <sys/types.h> #include <sys/socket.h> #include <netdb.h> extern "C" JNIEXPORT jstring JNICALL Java_com_example_cpptest_MainActivity_stringFromJNI( JNIEnv* env, jobject /* this */) { struct addrinfo hints, *res; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_INET; // 指定IPv4 hints.ai_socktype = SOCK_STREAM; int result = getaddrinfo("example.com", NULL, &hints, &res); if (result != 0) { __android_log_print(ANDROID_LOG_DEBUG, "CppTest", "getaddrinfo error: %s", gai_strerror(result)); return env->NewStringUTF("DNS: Query failed."); } // 解析成功,可按需处理res中的地址信息 freeaddrinfo(res); return env->NewStringUTF("DNS: Query successful."); }
为什么命令行程序也失败?
即使你把代码编译成命令行程序推到设备上,Bionic的resolv库依然不会自动读取系统DNS——因为Android的命令行环境同样没有有效的resolv.conf,所以问题和APP中一致。而Linux下正常是因为glibc会自动加载/etc/resolv.conf中的DNS配置。
总结
resolv.h的接口在Android NDK中是可以使用的,但必须手动配置DNS服务器地址,否则无法正常工作;- 更推荐使用
getaddrinfo()/gethostbyname()这类标准接口,它们会自动适配Android系统的DNS设置,无需额外配置,兼容性和稳定性更好。
内容的提问来源于stack exchange,提问作者Woalk




