Python中的多线程经常用于IO密集型任务,如网络请求,其中DNS查询是常见的一种场景。由于全局解释器锁(GIL)的存在,Python的多线程并不适合计算密集型任务,但对于IO密集型任务,如DNS查询,多线程可以显著提高性能。那么如果遇到下面的问题,可以通过这样的解决方法解决。
1、问题背景
原有 Python DNS 搜索代码在扫描大范围 IP 地址时速度较慢,需要进行优化以提高性能。同时,使用多线程会导致写入文件时出现问题,需要找到一种方法来解决这个问题。
2、解决方案
- 优化 DNS 查询过程:
- 优化 DNS 查询包的生成和发送过程,减少不必要的操作。
- 调整超时时间以减少等待时间。
- 优化多线程处理:
- 使用线程池来管理线程,提高线程利用率。
- 使用锁来控制对文件写入的访问,避免多线程写入冲突。
- 使用异步 I/O:
- 将文件写入操作改为异步 I/O,以提高 I/O 性能。
- 代码示例:
import socket
import struct
import threading
import os
import sys
import time
import asyncio
# 基本 DNS 头部结构,用于 1 个查询
def build_dns_query(host):
packet = struct.pack("!HHHHHH", 0x0001, 0x0100, 1, 0, 0, 0)
for name in host:
query = struct.pack("!b" + str(len(name)) + "s", len(name), name)
packet = packet + query
packet = packet + struct.pack("!bHH", 0, 1, 1)
return packet
# 测试查询,用于 www.google.com
TEST_QUERY = build_dns_query(["www", "google", "com"])
DNS_PORT = 53
TIMEOUT = 2
# 扫描服务器的 DNS
async def scan_dns(addr, timeout):
reader, writer = await asyncio.open_connection(addr, DNS_PORT)
# 发送 DNS 查询请求
send_count = writer.write(TEST_QUERY)
if send_count <= 0:
return False
# 等待响应
try:
data = await reader.read(1024)
except asyncio.TimeoutError:
return False
return True
# 将 IP 地址解析为整型元组
def extract_ip(ip):
partip = ip.split(".")
if len(partip) != 4:
print("Invalid IP address:", ip)
try:
ip_tuple = (int(partip[0]), int(partip[1]), int(partip[2]), int(partip[3]))
except ValueError:
print("Invalid IP address:", ip)
return ip_tuple
# 主函数
async def main(start_ip, end_ip):
# 存储找到的 DNS 服务器
found_dns = []
# 扫描所有 IP 地址
for i0 in range(start_ip[0], end_ip[0] + 1):
for i1 in range(start_ip[1], end_ip[1] + 1):
for i2 in range(start_ip[2], end_ip[2] + 1):
for i3 in range(start_ip[3], end_ip[3] + 1):
# 构建 IP 地址
ip_addr = f"{i0}.{i1}.{i2}.{i3}"
print(f"Scanning {ip_addr}...", end=" ")
# 扫描地址
ret = await scan_dns(ip_addr, TIMEOUT)
if ret:
found_dns.append(ip_addr)
print("Found!")
await write_file(ip_addr)
else:
print("")
print("Found DNS servers:", found_dns)
# 写入文件
async def write_file(ip_addr):
file = open("dns_servers.txt", "a")
file.write(ip_addr + "\n")
file.close()
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python dnsfind.py <start_ip> <end_ip>")
exit()
# 转换 IP 地址到整型元组
start_ip = extract_ip(sys.argv[1])
end_ip = extract_ip(sys.argv[2])
# 执行主函数
asyncio.run(main(start_ip, end_ip))
根据你的应用和机器的具体情况调整线程池的大小。对于高并发的DNS查询,使用异步IO(如asyncio
库)可能比多线程更有效率。例如dnspython
提供的异步解析功能,可能比使用socket.gethostbyname
更高效。实现这些优化策略后,你应该能够显著提高Python程序中DNS查询的性能。如果有更好的建议欢迎评论区留言讨论。