You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Python获取设备主机名等信息,跳过或仅读取一次/etc/hosts

解决Python获取主机信息时跳过/缓存/etc/hosts的问题

针对你提到的用socket.gethostbyaddr()每次都会读取/etc/hosts的痛点,我整理了两个靠谱的方案,都用Python标准库就能实现,不需要额外装依赖:

方案一:直接跳过/etc/hosts,仅执行DNS查询

如果想完全绕开/etc/hosts直接查DNS,我们可以自己用socket实现一个简单的DNS查询逻辑,这样就不会触发系统对hosts文件的读取。以下是一个基础的A记录查询实现,还能结合反向解析获取主机名、别名等信息:

import socket
import struct

def dns_query(hostname, dns_server="8.8.8.8", port=53):
    # 构建DNS请求头部
    request_id = 1234  # 随机生成请求ID也可以
    flags = 0x0100  # 标准查询+递归请求
    header = struct.pack(">HHHHHH", request_id, flags, 1, 0, 0, 0)

    # 打包查询域名
    qname = b""
    for part in hostname.split("."):
        qname += struct.pack("B", len(part)) + part.encode()
    qname += b"\x00"  # 域名结束标记
    qtype = struct.pack(">H", 1)  # A记录类型
    qclass = struct.pack(">H", 1)  # 互联网类

    query = header + qname + qtype + qclass

    # 发送UDP请求到DNS服务器
    with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
        sock.sendto(query, (dns_server, port))
        response, _ = sock.recvfrom(512)

    # 解析响应提取IP地址(简化处理非压缩格式)
    ptr = 12  # 跳过头部12字节
    # 跳过查询部分
    while response[ptr] != 0:
        ptr += 1 + response[ptr]
    ptr += 5

    # 提取所有A记录IP
    ancount = struct.unpack(">H", response[6:8])[0]
    ips = []
    for _ in range(ancount):
        while response[ptr] != 0:
            ptr += 1 + response[ptr]
        ptr += 1
        rtype = struct.unpack(">H", response[ptr:ptr+2])[0]
        ptr += 10
        if rtype == 1:
            ip = socket.inet_ntoa(response[ptr:ptr+4])
            ips.append(ip)
        ptr += 4

    # 反向解析获取主机名等信息
    if ips:
        try:
            hostname, aliases, canonical_name = socket.gethostbyaddr(ips[0])
            return (hostname, aliases, canonical_name, ips)
        except socket.herror:
            return (hostname, [], [], ips)
    return (None, [], [], [])

# 使用示例
result = dns_query("example.com")
print(f"主机名: {result[0]}")
print(f"别名: {result[1]}")
print(f"规范名: {result[2]}")
print(f"IP地址: {result[3]}")

这个方法直接和DNS服务器通信,完全不依赖/etc/hosts。如果需要支持更多DNS类型(比如AAAA)或压缩格式,还可以扩展逻辑,但基础版本足够满足日常需求。

方案二:缓存/etc/hosts内容,自行处理解析逻辑

如果想自己控制/etc/hosts的读取(只读一次缓存),同时结合DNS查询,可以先把hosts文件内容加载到内存缓存,查询时优先查缓存,缓存没命中再走DNS:

import socket
import os

class HostsCache:
    def __init__(self):
        self.ip_to_info = {}
        self.name_to_ip = {}
        self._load_hosts()

    def _load_hosts(self):
        hosts_path = "/etc/hosts"
        if not os.path.exists(hosts_path):
            return
        with open(hosts_path, "r") as f:
            for line in f:
                line = line.strip()
                if not line or line.startswith("#"):
                    continue
                parts = line.split()
                if len(parts) < 2:
                    continue
                ip = parts[0]
                names = parts[1:]
                # 缓存IP对应的主机信息
                self.ip_to_info[ip] = {
                    "hostname": names[0],
                    "aliases": names[1:],
                    "canonical": names[0]
                }
                # 缓存域名到IP的映射
                for name in names:
                    self.name_to_ip[name] = ip

    def get_host_info(self, target):
        # 先查本地缓存
        if target in self.ip_to_info:
            info = self.ip_to_info[target]
            return (info["hostname"], info["aliases"], info["canonical"], [target])
        if target in self.name_to_ip:
            return self.get_host_info(self.name_to_ip[target])
        
        # 缓存未命中,走DNS查询
        try:
            if "." in target:
                # 正向解析域名
                hostname, aliases, ips = socket.gethostbyname_ex(target)
                # 尝试反向解析规范名
                try:
                    canonical_name, _, _ = socket.gethostbyaddr(ips[0])
                except socket.herror:
                    canonical_name = hostname
                return (hostname, aliases, canonical_name, ips)
            else:
                # 反向解析主机名/IP
                hostname, aliases, canonical_name = socket.gethostbyaddr(target)
                ips = [socket.gethostbyname(hostname)]
                return (hostname, aliases, canonical_name, ips)
        except socket.herror:
            return (None, [], [], [])

# 使用示例
hosts_cache = HostsCache()  # 初始化时仅读取一次/etc/hosts
result = hosts_cache.get_host_info("localhost")
print(f"主机名: {result[0]}")
print(f"别名: {result[1]}")
print(f"规范名: {result[2]}")
print(f"IP地址: {result[3]}")

result = hosts_cache.get_host_info("example.com")
print(f"主机名: {result[0]}")
print(f"别名: {result[1]}")
print(f"规范名: {result[2]}")
print(f"IP地址: {result[3]}")

这个方案的优势是:

  • /etc/hosts仅在初始化时读取一次,避免重复IO操作
  • 优先使用本地配置,同时兼容DNS远程解析,灵活性更高

额外小技巧:系统层面修改解析行为

如果你想让整个Python进程的socket调用都跳过/etc/hosts,可以设置RES_OPTIONS环境变量,但这个方法是全局的,且依赖系统resolv.conf支持,跨平台性一般,谨慎使用:

import os
import socket

# 让解析器跳过hosts文件
os.environ['RES_OPTIONS'] = 'nohosts'
# 重新初始化解析器(部分系统需要)
socket.getaddrinfo('localhost', 80)

# 此时调用socket.gethostbyaddr会直接查DNS
hostname, aliases, canonical_name = socket.gethostbyaddr('example.com')
print(hostname, aliases, canonical_name)

内容的提问来源于stack exchange,提问作者Jonathan Allen Grant

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 10:11:13