资讯详情

Tornado 中的 C-Ares 异步 DNS 解析器:`tornado.platform.caresresolver` 原理与弃用演进

📅 2026/9/20 23:57:39 | 华诺云谱 👁 阅读
Tornado 中的 C-Ares 异步 DNS 解析器:`tornado.platform.caresresolver` 原理与弃用演进
Tornado 中的 C-Ares 异步 DNS 解析器tornado.platform.caresresolver原理与弃用演进【免费下载链接】tornadoTornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.项目地址: https://gitcode.com/gh_mirrors/to/tornadotornado.platform.caresresolver是 Tornado 中基于 c-ares 库及其 Python 绑定pycares实现的异步 DNS 解析模块。本文以 docs/caresresolver.rst 为核心结合 tornado/platform/caresresolver.py 源码、tornado/netutil.py 中的 Resolver 体系与相关测试系统讲解CaresResolver的设计动机、底层工作机制、AF_INET/AF_UNSPEC地址族限制、接入方式以及它在 Tornado 6.2 被弃用、7.0 移除的完整演进脉络帮助读者理解无线程、非阻塞 DNS 解析方案的取舍并掌握迁移到默认线程解析器的路径。一、模块定位什么是CaresResolverCaresResolver是 Tornado 提供的一种基于 c-ares 库的异步域名解析器。c-ares 是一个用 C 语言编写的异步 DNS 解析库其 Python 绑定为pycares。该类的完整定义位于 tornado/platform/caresresolver.pyclass CaresResolver(Resolver): Name resolver based on the c-ares library. This is a non-blocking and non-threaded resolver. It may not produce the same results as the system resolver, but can be used for non-blocking resolution when threads cannot be used. ... 其核心设计目标在类文档中交代得很清楚同时也在模块文档 docs/caresresolver.rst 中重申非阻塞non-blocking解析过程不会阻塞事件循环无线程non-threaded不依赖工作线程池适合线程不可用的场景例如某些受限运行环境或对线程使用有严格限制的程序结果差异它可能不会产生与系统解析器相同的结果这是使用该类前必须接受的取舍。从源码结构看CaresResolver直接继承自tornado.netutil.Resolvertornado/netutil.py而Resolver是一个Configurable类即 Tornado 中可插拔解析器体系的抽象接口。Resolver.resolve()的约定返回值是一个(family, address)对的列表其中 address 是可直接传给socket.connect()的元组IPv4 为(host, port)IPv6 可能带额外字段。二、核心 API 与解析流程2.1initialize()建立 c-ares 通道def initialize(self) - None: self.io_loop IOLoop.current() self.channel pycares.Channel(sock_state_cbself._sock_state_cb) self.fds: dict[int, int] {}tornado/platform/caresresolver.py这里创建了一个pycares.Channelc-ares 的解析通道对象并注册sock_state_cb回调——这是 c-ares 向事件循环报告哪些 socket 处于可读/可写状态的桥梁下文第三节详述。在 Tornado 5.0 之前initialize还接受io_loop参数5.0 起该参数自 4.1 已弃用被彻底移除统一使用IOLoop.current()。2.2resolve()宿主名到地址元组的转换resolve()是一个gen.coroutine协程方法tornado/platform/caresresolver.py签名与Resolver接口一致gen.coroutine def resolve(self, host: str, port: int, family: int 0) - ...:其执行流程可以拆解为四个步骤IP 直通短路若host本身就是合法 IP 字符串经tornado.netutil.is_valid_ip校验则直接跳过 DNS 查询以[host]作为解析结果。is_valid_ip的实现位于 tornado/netutil.py它通过socket.getaddrinfo(..., AI_NUMERICHOST)同时校验 IPv4 与 IPv6并额外处理了空串、\x00截断、超长输入63 字符触发 idna UnicodeError等边界情况。异步查询对非 IP 的宿主名调用self.channel.gethostbyname(host, family, callback)发起 c-ares 异步查询并用一个Future承接回调结果fut: Future[tuple[Any, Any]] Future() self.channel.gethostbyname( host, family, lambda result, error: fut.set_result((result, error)) ) result, error yield fut这里刻意采用gethostbyname而非gethostbyname4源码注释给出了原因gethostbyname不支持把回调作为关键字参数传入因此用位置参数包裹。 3.错误归一化若 c-ares 返回错误码则抛出OSError错误信息中包含 c-ares 错误码及其可读文本raise OSError( C-Ares returned error %s: %s while resolving %s % (error, pycares.errno.strerror(error), host) )结果标准化遍历返回的地址根据地址中是否含.或:判定AF_INET/AF_INET6组装成(address_family, (address, port))元组列表同时校验请求的family与返回族是否一致不一致则抛出OSErrorRequested socket family %d but got %d。该返回格式与Resolver接口约定tornado/netutil.py完全一致可直接作为socket.connect/IOStream.connect的输入因此CaresResolver可以无缝嵌入 Tornado 的连接管线。三、底层机制c-ares 如何与 Tornado 事件循环协作CaresResolver之所以能做到无线程、非阻塞关键在于把 c-ares 自己的 socket 事件接入 Tornado 的IOLoop。这一过程由一对回调完成_sock_state_cb(fd, readable, writable)tornado/platform/caresresolver.py——c-ares 在内部 socket 状态变化时调用它state (IOLoop.READ if readable else 0) | (IOLoop.WRITE if writable else 0) if not state: self.io_loop.remove_handler(fd) del self.fds[fd] elif fd in self.fds: self.io_loop.update_handler(fd, state) self.fds[fd] state else: self.io_loop.add_handler(fd, self._handle_events, state) self.fds[fd] state逻辑为状态归零则从IOLoop移除该 fdfd 已注册则更新监听事件READ/WRITE否则新增监听。self.fds字典用于跟踪当前已注册的 fd 与其监听状态。_handle_events(fd, events)tornado/platform/caresresolver.py——IOLoop在 fd 就绪时调用它把事件喂回 c-aresread_fd pycares.ARES_SOCKET_BAD write_fd pycares.ARES_SOCKET_BAD if events IOLoop.READ: read_fd fd if events IOLoop.WRITE: write_fd fd self.channel.process_fd(read_fd, write_fd)process_fd是 c-ares 的驱动函数告诉 c-ares 哪个 fd 可读、哪个 fd 可写c-ares 随即完成收发 DNS 报文、超时处理、触发查询回调等内部工作。整个过程中没有任何线程阻塞等待DNS 报文收发全部由IOLoop的事件驱动完成——这正是非阻塞且非线程化的源码级答案。四、地址族限制为什么只推荐AF_INET原文档明确指出 c-ares 的一个关键限制c-ares fails to resolve some names whenfamilyisAF_UNSPEC, so it is only recommended for use inAF_INET(i.e. IPv4).源码中的表述更进一步tornado/platform/caresresolver.pypycareswill not return a mix ofAF_INETandAF_INET6whenfamilyisAF_UNSPEC, so it is only recommended for use inAF_INET.这意味着当请求AF_UNSPEC即IPv4/IPv6 皆可时pycares 不会同时返回两类地址的混合结果某些域名甚至会解析失败。因此仅推荐在AF_INETIPv4场景使用CaresResolvertornado.simple_httpclient默认使用AF_INET与CaresResolver契合但其他库可能默认AF_UNSPEC此时接入CaresResolver会遇到解析异常。此外还有一个可观察到的行为差异在 tornado/test/websocket_test.py 的测试注释中提到 CaresResolver may return ipv6-only results for localhost即CaresResolver对localhost可能只返回 IPv6 结果这与系统解析器返回127.0.0.1的行为不同——再次印证了结果可能与系统解析器不一致的警告也是测试用例特意跳过 macOS / Windows 的原因之一。五、接入方式如何配置并投入使用5.1 通过Resolver.configure全局替换由于Resolver是Configurable类可借助其configure类方法在全局启用CaresResolver方式与 tornado/netutil.py 文档中给出的示例一致from tornado.netutil import Resolver Resolver.configure(tornado.platform.caresresolver.CaresResolver)配置后凡是经由Resolver()获取解析器的地方如AsyncHTTPClient、TCPClient等都会使用 c-ares 进行解析。5.2 在 HTTP 客户端中显式传入tornado.simple_httpclient的构造函数接受resolver参数tornado/simple_httpclient.py未指定时内部创建默认Resolver()并自持其生命周期own_resolverTrue显式传入时则由调用方负责解析器的关闭。同时若提供了hostname_mapping解析器还会被OverrideResolver包装实现本地主机名重定向——这对测试环境非常有用。5.3 直接实例化调用也可以直接构造并调用与 maint/scripts/test_resolvers.py 中的用法一致import socket from tornado.ioloop import IOLoop from tornado.platform.caresresolver import CaresResolver async def main(): resolver CaresResolver() result await resolver.resolve(example.com, 80, socket.AF_INET) print(result) # [(socket.AF_INET, (93.184.216.34, 80)), ...] resolver.close() IOLoop.current().run_sync(main)注意resolve的family参数应显式传socket.AF_INET以规避上文所述的AF_UNSPEC问题。六、测试与验证仓库中的证据仓库对CaresResolver的验证主要落在 tornado/test/netutil_test.py。该测试类继承_ResolverTestMixin通过统一的解析用例含localhost、域名解析、bad_host错误场景验证行为但有以下值得注意的跳过条件与注释pycares未安装时跳过pycares is NoneWindows 上跳过pycares doesnt return loopback on windowsmacOS 上跳过pycares doesnt return 127.0.0.1 on darwin注释说明不测试错误场景的原因部分 DNS 劫持型 ISP如 Time Warner在返回 NXDOMAIN 状态码的同时仍返回非空结果多数解析器将其视为错误而 c-ares 会返回这些结果导致bad_host测试不可靠且 c-ares 甚至会尝试解析带空格的畸形名称。另外maint/scripts/test_resolvers.py 提供了一个手工对比脚本它会同时实例化默认Resolver()、ThreadedResolver、DefaultExecutorResolver以及若安装了 pycaresCaresResolver对localhost、www.google.com等真实域名逐一解析并打印结果可通过--familyinet|inet6|unspec切换地址族默认unspec。该脚本需要联网其文档注明将在 Tornado 7.0 移除可插拔解析器体系时一并删除。七、版本演进与弃用为什么你应改用默认解析器CaresResolver的完整生命周期可以从仓库的发布记录中还原Tornado 3.0引入tornado.platform.caresresolver.CaresResolver见 docs/releases/v3.0.0.rstTornado 5.0移除io_loop构造参数docs/releases/v5.0.0.rst同时默认解析器从BlockingResolver演进为DefaultExecutorResolverTornado 6.2CaresResolver被正式标记为弃用明确将在 Tornado 7.0 移除docs/releases/v6.2.0.rst同一版本中默认解析器已从DefaultExecutorResolver切换为DefaultLoopResolver——后者直接基于asyncio.loop.getaddrinfotornado/netutil.py已不再需要线程或 c-arespycares 版本约束CaresResolver要求pycares 4且不会升级支持 pycares 5。仓库 tox.ini 中明确写道Pycares 5 has some backwards-incompatible changes that we dont support. And since CaresResolver is deprecated, I do not expect to fix it因此测试环境将 pycares 固定为5Tornado 6.5同属无线程 DNS阵营的TwistedResolver被直接删除docs/releases/v6.5.0.rst原因之一是 RFC 8482针对 ANY 查询的 DNS 最小化响应使其对大多数域名失效。发布说明将该类的主要使命——提供无线程的非阻塞 DNS 解析——转述给tornado.platform.caresresolver作为次优选择同时再次强调它同样已弃用大多数用户应切换到使用线程的默认解析器。迁移建议如果你当前使用了CaresResolver推荐的迁移路径是移除显式配置、回归默认解析器默认DefaultLoopResolver基于asyncio事件循环的getaddrinfo无需额外安装 pycares行为与系统解析器一致兼容 IPv4/IPv6若确需控制线程资源可使用仍保留的ThreadedResolvertornado/netutil.py默认线程池 10支持num_threads配置或ExecutorResolver可自定义 executortornado/netutil.py需要本地 DNS 覆盖时用OverrideResolver包装任意解析器测试场景下非常实用tornado/netutil.py。一句话总结CaresResolver是 Tornado 在既不想用线程、又需要非阻塞 DNS这一历史约束下的精妙实现——它把 c-ares 的 socket 事件桥接到IOLoop完成了无线程的非阻塞解析但随着asyncio.getaddrinfo成为默认方案它的历史使命已经完成理解其原理的价值更多在于领会 Tornado 可插拔解析器体系的架构思想与事件驱动编程模式。【免费下载链接】tornadoTornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed.项目地址: https://gitcode.com/gh_mirrors/to/tornado创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。