
后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载gevent.ares是 gevent 中一个保留至今的向后兼容别名模块它把底层基于 c-ares 库的异步 DNS 解析能力channel及完整解析器体系暴露给旧代码使用。本文将以该模块为线索带你梳理 gevent 可插拔 DNS 解析架构、c-ares 解析器的实现原理、全部配置项与已知行为差异读完即可独立评估并在项目中启用、调优基于 c-ares 的解析方案。一、gevent.ares一段只有十几行代码的历史遗留docs/api/gevent.ares.rst对该模块的官方定位只有一句话Backwards compatibility alias forgevent.resolver.caresgevent.resolver.cares的向后兼容别名并通过automodule指令将模块的全部公开成员渲染进文档。而模块本体 src/gevent/ares.py 的实现也极其精简全部逻辑如下Backwards compatibility alias for :mod:gevent.resolver.cares. .. deprecated:: 1.3 Use :mod:gevent.resolver.cares from gevent.resolver.cares import * # 通配导入 import gevent.resolver.cares as _cares __all__ _cares.__all__ del _cares这里可以提炼出三个关键事实它不做任何解析工作只是把gevent.resolver.cares的公开符号原样转发由于gevent.resolver.caresCython 模块 src/gevent/resolver/cares.pyx的__all__ [channel]gevent.ares实际暴露的也是channel——即 c-ares 异步通道的封装类。自 1.3 起被标记为 deprecated文档与代码均提示新代码应使用gevent.resolver.ares承载Resolver类的模块。仓库里还存在另一个形态相似的别名模块 src/gevent/resolver_ares.py它在导入时会发出DeprecationWarninggevent.resolver_ares is deprecated... Use gevent.resolver.ares instead.。也就是说围绕 c-ares 解析器一共有四个模块名需要区分模块角色gevent.ares兼容别名转发gevent.resolver.cares的channelgevent.resolver_ares兼容别名已废弃警告转发gevent.resolver.aresgevent.resolver.caresCython 实现层暴露channel、InvalidIPgevent.resolver.ares解析器实现层暴露Resolver类推荐使用的规范位置二、背景gevent 的可插拔 DNS 解析体系gevent.ares之所以存在是因为 gevent 从 docs/dns.rst 开始就设计了一套可插拔pluggable主机名解析系统。该系统在gevent.monkey打补丁时会被自动启用也可以手动通过gevent.hub.Hub.resolver属性或gevent.socket模块中对应的方法使用。一个解析器需要实现socket模块中的 5 个标准函数socket.gethostbynamesocket.gethostbyname_exsocket.getaddrinfosocket.gethostbyaddrsocket.getnameinfogevent 内置了四种解析器实现应用也可提供自定义实现gevent.resolver.thread.Resolver默认实现把查询委托给系统解析器所在的线程池gevent.resolver.ares.Resolver本文主角基于 c-ares 的原生异步实现gevent.resolver.dnspython.Resolver基于 dnspython 库gevent.resolver.blocking.Resolver阻塞式实现直接调用系统 socket 函数。默认使用哪个由配置项resolver决定。在 src/gevent/_config.py 中可以看到它的默认候选列表与短名映射default [thread, dnspython, ares, block] shortname_map { ares: gevent.resolver.ares.Resolver, thread: gevent.resolver.thread.Resolver, block: gevent.resolver.blocking.Resolver, dnspython: gevent.resolver.dnspython.Resolver, }因此把resolver设为短名ares或直接设为完整导入路径gevent.resolver.ares.Resolver即可切换到 c-ares 解析器。三、c-ares 解析器核心Resolver 类真正的实现位于 src/gevent/resolver/ares.py 的Resolver(AbstractResolver)类。它的类文档直接说明了设计取舍c-ares 在 socket 层面就是原生异步的因此能很好地融入 gevent 的事件循环不同于把查询丢进线程池的gevent.resolver.thread.Resolver代价是实现复杂度明显更高且历史上存在不完全遵循某些系统配置的报道例如 IPv4/IPv6 结果的返回顺序可能与线程解析器不一致由于不占用线程对发起大量查询的应用可能具有更好的扩展性。3.1 初始化流程Resolver.__init__见 src/gevent/resolver/ares.py做的事情可以拆解为四步def __init__(self, hubNone, use_environTrue, **kwargs): AbstractResolver.__init__(self) if hub is None: hub get_hub() self.hub hub if use_environ: for setting in config.settings.values(): if isinstance(setting, AresSettingMixin): value setting.get() if value is not None: kwargs.setdefault(setting.kwarg_name, value) self.cares self.cares_class(hub.loop, **kwargs) self.pid os.getpid() self.params kwargs self.fork_watcher hub.loop.fork(refFalse) self.fork_watcher.start(self._on_fork)解析并持有 hub若use_environTrue遍历gevent._config.config.settings把所有AresSettingMixin子类的已配置值如ares_servers、resolver_timeout等收集为channel的构造关键字参数用hub.loop与这些参数构造channelcares_class默认即channel注册一个forkwatcher用于进程 fork 后重建 c-ares 通道。3.2 生命周期与 fork 安全close()先调用父类AbstractResolver.close()然后通过hub.loop.run_callback(self.cares.destroy)在事件循环中销毁通道并停掉 fork watcher__del__()自 22.10.1 版本起若对象销毁时未显式close()会发出ResourceWarning并尝试自动关闭_on_fork()比较os.getpid()与构造时的self.pid若进程号变化说明发生了 fork则在新进程中用保存的self.params重新创建通道避免在子进程中使用父进程遗留的 c-ares 状态。_gethostbyname_ex、_getaddrinfo等方法的while True重试循环正是为配合这一重建机制设计的当检测到self.cares已被替换时重试一次。四、channelCython 层如何把 c-ares 接进事件循环channelsrc/gevent/resolver/cares.pyx是真正与 c-ares C API 打交道的 Cython 类也是gevent.ares实际暴露的符号。4.1 构造参数与底层选项channel.__init__接受以下参数逐一映射到 c-ares 的ares_options与optmaskchannel 参数对应 ares 选项说明flagsARES_OPT_FLAGS通道级标志timeout秒转为毫秒ARES_OPT_TIMEOUTMS单次查询超时triesARES_OPT_TRIES重试次数ndotsARES_OPT_NDOTS名字中点数阈值决定先查绝对名还是先过搜索域udp_portARES_OPT_UDP_PORTUDP 查询端口tcp_portARES_OPT_TCP_PORTTCP 查询端口servers经ares_set_servers指定 nameserver 列表其中timeout在 Python 侧是秒浮点底层会乘以 1000 转为毫秒。4.2 与事件循环的协作机制c-ares 通过socket 状态回调sock_state_callback通知某个 fd 现在需要读/写。channel正是利用这一点把 c-ares 的异步事件桥接到 gevent 的loop.iowatcher 上回调中按需创建/更新/销毁loop.io(socket, events)watcher见_sock_state_callback同时维护一个周期timer用于驱动ares_process_fd()处理超时fd 就绪时调用ares_process_fd(channel, read_fd, write_fd)推进 c-ares 内部状态机destroy()调用ares_destroy()释放通道并清理所有 watcher。此外channel还实现了set_servers(servers)用ares_inet_pton校验每个地址是 IPv4 还是 IPv6非法地址抛出InvalidIP即ValueError子类再通过ares_set_servers下发gethostbyname / gethostbyaddr / getnameinfo / getaddrinfo四个异步查询入口均以回调对象方式接收结果结果统一包装为Result(value, exception)由ares_host_result承载(name, aliases, addresses)元组并附带family属性——这正是src/gevent/tests/test__ares_host_result.py所测试的对象。4.3 getaddrinfo 的两处实现细节自 20.5.0 版本起getaddrinfo改用 c-ares 1.16 的原生ares_getaddrinfo实现见 src/gevent/resolver/cares.pyx并把 Python 的AI_CANONNAME、AI_NUMERICSERV标志转换为 c-ares 的ARES_AI_*标志同时总是附带ARES_AI_NOSORT由于 c-ares 1.16不会在结果中填充 socktype/proto其官方 issue #317Resolver.__getaddrinfo 中的fill_in_type_proto逻辑会依据传入的socktype/proto提示为结果补上(SOCK_STREAM, SOL_TCP)或(SOCK_DGRAM, SOL_UDP)组合。五、五个标准函数的 ares 实现路径Resolver继承自 src/gevent/resolver/init.py 的AbstractResolver后者负责把socket风格的高层 API 归一化gethostbyname/gethostbyname_ex对localhost、ip6-localhost、127.0.0.1、::1、255.255.255.255、broadcast等特殊名字直接回落给原生socket实现_LOCAL_AND_BROADCAST_HOSTNAMES其余名字进入_gethostbyname_ex通过一次带AI_CANONNAME的getaddrinfo获取规范名与地址列表。getaddrinfoAbstractResolver.getaddrinfo在四种情况下同样直接走原生socket.getaddrinfohost 为None、类型非法、设置了AI_NUMERICHOST、名字属于本地别名或 link-local IPv6fe80::前缀。其余情况进入Resolver._getaddrinfo。gethostbyaddrResolver.__gethostbyaddr先尝试ares_gethostbyaddr若 c-ares 判定地址非法抛出InvalidIP则回退为一次getaddrinfo(AF_UNSPEC, SOCK_DGRAM)规范化地址后再试。getnameinfoResolver.__getnameinfo先做一次getaddrinfo把 sockaddr 归一化为单条结果再调用ares_getnameinfo若 c-ares 返回的service为空NULL会按 Python 3 语义抛出gaierror(EAI_NONAME)。端口越界≥ 65536在 CPython 上被规整为 0在 PyPy 上则抛OverflowError。六、错误码映射从 ARES_* 到 gaierror / herrorc-ares 的 C 错误码与 Pythonsocket的gaierror/herror并不一一对应因此 src/gevent/resolver/cares.pyx 中维护了两张映射表_ares_to_gai_system把ARES_ENONAME、ARES_ENOTFOUND、ARES_ENODATA统一映射为EAI_NONAMEARES_EBADFAMILY→EAI_ADDRFAMILYARES_ENOMEM→EAI_MEMORY等并借助gai_strerror生成与系统解析器一致的错误文案_ares_to_host_system把ARES_ENONAME、ARES_ENOTFOUND→HOST_NOT_FOUNDARES_ENODATA→NO_DATA供herror使用。自 20.5.0 版本起herror/gaierror的抛出时机与 errno 数值已与标准库解析器更加一致例如主机不存在统一表现为gaierror(EAI_NONAME)错误文案取AbstractResolver.EAI_NONAME_MSG在 macOS 与非 macOS 平台分别对应两种标准消息。七、配置与实战如何启用并调优 ares 解析器7.1 切换解析器方式一修改全局配置monkey patch 后自动生效。import gevent gevent.config.resolver ares # 短名 # 或完整导入路径 gevent.config.resolver gevent.resolver.ares.Resolver方式二直接构造并使用。from gevent import get_hub from gevent.resolver.ares import Resolver resolver Resolver(get_hub(), servers[8.8.8.8, 8.8.4.4], timeout5, ndots2) addr resolver.gethostbyname(example.com)7.2 全部相关配置项与环境变量Resolver.__init__的use_environTrue逻辑会把 src/gevent/_config.py 中所有AresSettingMixin子类的已配置值自动传给channel。这些配置项的完整清单如下均可在对应类定义处查到environment_key配置项环境变量作用备注resolver无选择解析器实现支持ares短名ares_flagsGEVENTARES_FLAGSc-ares 通道标志ares_timeoutGEVENTARES_TIMEOUT单次查询超时自 1.3a2 起废弃优先用resolver_timeoutares_triesGEVENTARES_TRIES重试次数ares_ndotsGEVENTARES_NDOTS名字点数阈值ares_udp_portGEVENTARES_UDP_PORTUDP 查询端口ares_tcp_portGEVENTARES_TCP_PORTTCP 查询端口ares_serversGEVENTARES_SERVERS指定 nameserver逗号分隔自 1.3a2 起废弃优先用resolver_nameserversresolver_nameserversGEVENT_RESOLVER_NAMESERVERS通用 nameserver 列表dnspython 与 ares 共用ares 会校验成员必须是合法地址resolver_timeoutGEVENT_RESOLVER_TIMEOUT解析器总查询时间仅 ares 与 dnspython 支持命令行示例指定 DNS 服务器并限制总超时GEVENT_RESOLVER_NAMESERVERS8.8.8.8,8.8.4.4 GEVENT_RESOLVER_TIMEOUT5 python app.py注意_config.py中的两条 deprecation 提示若同时设置ares_timeout与resolver_timeout或ares_servers与resolver_nameservers结果未定义请只使用推荐项。7.3 版本与平台注意事项当前仓库嵌入了 c-ares 源码deps/c-ares构建入口见 include/ares.h 与根目录_setupares.py自 1.5.0 起通常内嵌 c-ares 1.15.0该版本起永不解析.onion结尾的域名甚至不会发给 DNS 服务器在PyPy上该模块被视为极其实验性由于是 Cython 实现可能更慢甚至可能引发解释器崩溃PyPy 用户应谨慎评估见 src/gevent/resolver/ares.py 的 caution 说明自 22.10.1 起新增__del__警告机制忘记调用close()会收到ResourceWarning。八、与系统解析器的已知差异Resolver类文档明确列举了与系统解析器thread 解析器最终委托的系统实现的已知差异选型时务必对照gethostbyname_ex/gethostbyaddr返回的aliaslist可能不同顺序或别名本身gethostbyname_ex的ipaddrlist顺序可能不同getaddrinfo不返回SOCK_RAW结果且结果顺序可能不同.localmDNS名字的处理可能不同即使其已写入 hosts 文件broadcasthost在 2020-04-30 之前的 c-ares 中即使存在于 hosts 文件也不会解析localhost的结果可能不同系统解析器可能返回更多结果如SOCK_DGRAM项c-ares 在多网卡主机上可能报告更多 IPhosts 文件中的条目系统实现可能返回完整限定名而 c-ares 只返回主机名getnameinfo/getaddrinfo仅支持有限的标志集未知标志被忽略如AI_V4MAPPED_CFG这类系统专属标志不支持即使未设置AI_CANONNAMEgetaddrinfo也可能返回规范名getaddrinfo不支持 IPv6 符号形式的 scope ID。九、何时选择 ares 解析器结合类文档的表述可以给出如下选型建议属合理推断而非承诺适合对并发解析量要求高、希望避免 DNS 查询占满线程池、能够接受结果顺序等细节与系统解析器略有出入、且能容忍更高实现复杂度的场景需谨慎对结果顺序、错误类型、localhost返回项等要求与系统行为完全一致的应用PyPy 环境以及重度依赖 mDNS/broadcasthost等特殊名字的环境兜底验证仓库自带 test__ares_host_result.py 与 test__ares_timeout.py 等测试可结合docs/api/gevent.resolver.thread.rst、docs/api/gevent.resolver.dnspython.rst、docs/api/gevent.resolver.blocking.rst中三种备选实现横向对比后再做决定。十、相关文件索引本文主体文档docs/api/gevent.ares.rst别名模块实现src/gevent/ares.py、src/gevent/resolver_ares.pyResolver 实现src/gevent/resolver/ares.pyCython 通道实现src/gevent/resolver/cares.pyx解析器抽象基类src/gevent/resolver/init.py配置项定义src/gevent/_config.pyDNS 体系总览docs/dns.rstc-ares 解析器文档docs/api/gevent.resolver.ares.rst内嵌 c-ares 依赖deps/c-ares相关测试src/gevent/tests/test__ares_host_result.py、src/gevent/tests/test__ares_timeout.py赞分享后端【免费下载链接】geventCoroutine-based concurrency library for Python项目地址https://gitcode.com/gh_mirrors/ge/gevent点击查看免费下载相关推荐Honcho流式响应与结构化输出打造极致Agent交互体验的2大技巧Honcho流式响应与结构化输出打造极致Agent交互体验的2大技巧 Honcho 是一个用于构建有状态 Agent 的记忆库Memory library后端Tornado 中的 C-Ares 异步 DNS 解析器tornado.platform.caresresolver 原理与弃用演进Tornado 中的 C Ares 异步 DNS 解析器 tornado.platform.caresresolver 原理与弃用演进 tornado.pla后端Web框架异步编程WebSocket在电脑上跑 PS4 游戏shadPS4 教程与配置全解在电脑上跑 PS4 游戏shadPS4 教程与配置全解 shadPS4 是一款用 C 写的 PS4 模拟器可在 Windows、Linux、macOS虚拟化图形学上一篇Kepler.gl 开发者指南环境搭建、测试、编码规范与版本发布的完整贡献流程下一篇5分钟掌握AMD Ryzen隐藏性能免费开源调试工具完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考