异步域名解析 c-ares 使用
源码编译
- 使用 AutoTools 编译(仅限 Git 仓库源码,非 Release 版本)。
- 使用 Cmake 编译。
AutoTools 编译
对于 Unix 平台,可以使用 AutoTools 工具生成 c-ares 编译脚本。
Autotools是一个用于自动化软件构建的工具集,包括 Autoconf、Automake 和 Libtool。
- autoconfig 用于将 configure.ac中 的宏展开,生成 configure 脚本。
- automake 用于将 Makefile.am 中定义的结构生成 Makefile.in。
1 | 通过 HomeBrew 安装 AutoTools |
编译步骤
- 执行 ./buildconf (Unix) 或者 buildconf.bat (Windows) 生成编译脚本文件。
- 执行 ./configure,生成 makefile。
- 执行 make 命令,编译源码。
- 执行 make install。
1 | iOS |
Cmake 编译
c-ares 支持 cmake v3 以上编译系统构建。支持 Windows, Linux, FreeBSD, MacOS, AIX and Solaris 等常见平台。
编译步骤
- 执行 cmake,生成对应平台的编译系统 。
- 执行 make 命令,编译源码。
- 执行 make install。
1 | iOS (需要额外下载 ios.toolchain.cmake) |
c-ares 可选配置:
- CARES_STATIC,编译静态库,默认 OFF。
- CARES_SHARED,编译动态库,默认 ON。
- CARES_INSTALL,创建安装目标,默认 ON。
- CARES_STATIC_PIC,编译 PIC (position independent)静态库,默认 OFF。
- CARES_BUILD_TESTS,编译运行测试代码,默认 OFF。
- CARES_BUILD_TOOLS,编译命令行工具代码,默认 ON。
- CARES_SYMBOL_HIDING, 在动态库中隐藏私有符号”Hide,默认OFF。
IDE 手动编译
编译步骤
- 生成 ares_build.h 和 ares_config.h。(需通过 cmake 或者 configure)
- 将 include 和 src/lib 下的源码和头文件加入到 IDE 工程中,并添加预编译宏 “HAVE_CONFIG_H=1”。(若编译动态库,还需要加上 CARES_BUILDING_LIBRARY 宏以确保接口导出)
异步解析
- c-ares 初始化。
- [可选] Android等平台额外初始化。
- 创建 c-ares 通道,并设置相关可选参数。
- 添加域名解析任务。
- 驱动域名解析执行(消息收发、回调处理)。
- 销毁 c-ares 通道。
- 清理 c-ares。
- win 平台,在初始化 c-ares 之前,需要调用 WSAStartup,在清理 c-ares 之后,需要调用 WSACleanup,来启用 win 网络。
- Android 平台,在 JNI_OnLoad 时,需要缓存 jvm 用于 JNI 调用,并通过 ares_library_init_jvm 设置给 c-ares。 然后在 onCreate 或其他合适时机,将 ConnectivityManager 传递到 Native 层并缓存(也可以传递 application context,然后在 native 层通过 JNI 获得 ConnectivityManager 实例)。当 c-ares 初始化完后,需要调用 ares_library_init_android 进行 Android 平台的额外设置,将 ConnectivityManager 设置给 c-ares 用于获取默认 DNS Servers。因为清理 c-ares 时,内部会自动释放 ConnectivityManager 引用,故每次c-ares 初始化后,都要重新设置 ConnectivityManager。(Android 8 或者 target 26 以上必须调用,否则不设置 DNS servers 的情况下,无法正常解析)。
1 | void my_dns_callback(void* arg, int status, int timeouts, struct ares_addrinfo* ai) { |
可以使用 select、poll、epoll、kqueue等实现不同平台的驱动:
1 |
|
Android 获取 connectivity manager
1 | static JavaVM *gJVM = NULL; |
注意事项
iOS 弹窗申请本地网络权限
若未设置 DNS Server,则 c-ares 会尝试查询 resolv.conf,失败时,会默认使用 LOOPBACK 作为 DNS 服务器,对于 iOS 平台,与 LOOPBACK 通信会触发本地网络权限。
iOS libresolv 检查失败
若通过 cmake 来生成 ares_config.h,则可能导致 CARES_FUNCTION_IN_LIBRARY (res_servicename resolv HAVE_RES_SERVICENAME_IN_LIBRESOLV) 检测失败,从而未打开 CARES_USE_LIBRESOLV 和 HAVE_LIBRESOLV 宏定义。
c-ares 中 CmakeLists.txt 中判断逻辑如下:
1 | # 当使用 ios.toolchain.cmake 配置 iOS 环境时,CARES_FUNCTION_IN_LIBRARY 方法会产生误判,调用 CHECK_FUNCTION_EXISTS (res_servicename) 直接返回 True,从而不再调用 CHECK_LIBRARY_EXISTS(resolv res_servicename)判断 libresolv 的情况。 |
CHECK_XXX_EXISTS 会调用 try_compile(),构造测试工程编译链接相关库和符号,以此判断库/符号是否存在。try_compile 有两种模式,通过 CMAKE_TRY_COMPILE_TARGET_TYPE 来设置编译目标为 EXECUTABLE 还是 STATIC_LIBRARY。当使用 STATIC_LIBRARY 时,测试工程仅编译,不会触发链接,因此无法正常判断符号存在性,一般情况下使用 EXECUTABLE。
旧版本的 cmake(如 3.18.x)在 CMAKE_TRY_COMPILE_TARGET_TYPE 设置编译目标为 EXECUTABLE 时,生成的 Xcode 工程没有屏蔽签名选项,导致因为缺少 bundleid 等信息无法编译,影响了结果判断。高版本的 cmake (如 3.22.1)会在 Build Settings 中 User-Defined 中增加 CODE_SIGNING_ALLOWED = NO 关闭签名。
使用同一个 channel 发起多个 dns 请求,仅有一个正常回调,其他的在无回调,或在 channel 销毁时返回 ARES_EDESTRUCTION
在 loop 驱动时,每一帧,需要判断当前 socket 的数量,当 socket 数量不为 0 时,无论 poll/epoll/kqueue 的结果是否成功,都要驱动 ares_process_fd。
参考资料
https://github.com/c-ares/c-ares
https://cmake.org/cmake/help/latest/manual/cmake-toolchains.7.html#cross-compiling-toolchain
https://github.com/leetal/ios-cmake