You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android NDK中resolv.h的res_query无法执行DNS查询问题咨询

Android NDK中使用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服务器。

步骤如下:

  1. 读取系统属性net.dns1net.dns2等获取当前DNS服务器地址(Android 10+可能区分移动网络和WiFi,比如net.wlan0.dns1对应WiFi环境)。
  2. 将DNS地址转换为sockaddr_in结构,赋值给_res.nsaddr_list
  3. 直接使用配置后的结构体发起查询(无需额外调用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

火山引擎 最新活动