You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Python多线程局域网端口扫描性能优化求助:10主机扫描耗时超预期

提升局域网端口扫描速度的关键优化方案

你遇到的问题其实很典型——无限制创建线程反而拖慢了扫描速度。一下子启动65万+个线程,系统根本扛不住:线程调度的上下文切换开销会爆炸,大部分线程都在等待系统资源,反而远不如控制合理并发数来得高效。下面给你几个针对性的优化方案,一步步把扫描速度提上去:

核心问题分析

原代码一次性创建并启动了10×65525=655250个线程,这远远超过了操作系统能高效处理的线程数量(一般单机能高效处理的线程数在几百到几千级别)。过多的线程会导致CPU花费大量时间在线程切换上,而非实际的端口连接操作,最终扫描效率暴跌。

优化方案

1. 使用线程池控制并发数

concurrent.futures.ThreadPoolExecutor来限制同时运行的线程数,这是最直接的优化。比如设置最大并发线程为500(可以根据你的机器性能调整,比如200-1000之间测试),既能利用多核优势,又不会耗尽系统资源。

修改后的代码示例:

import socket
from concurrent.futures import ThreadPoolExecutor

open_sockets = []

def scan_port(ip, port):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(0.5)  # 局域网内可以把超时设短,比如0.5秒
        result = sock.connect_ex((ip, port))  # connect_ex返回0表示成功,避免try-except捕获所有异常
        if result == 0:
            open_sockets.append(f"{ip}:{port} is open")
        sock.close()
        return result == 0
    except Exception as e:
        return False

if __name__ == "__main__":
    target_ips = [f"192.168.1.{i}" for i in range(1, 11)]
    target_ports = range(1, 65526)
    
    # 生成所有需要扫描的(ip, port)组合
    scan_tasks = [(ip, port) for ip in target_ips for port in target_ports]
    
    # 使用线程池,设置最大并发数
    with ThreadPoolExecutor(max_workers=500) as executor:
        # 批量提交任务
        executor.map(lambda x: scan_port(x[0], x[1]), scan_tasks)
    
    print(open_sockets)

2. 先筛选存活主机,减少无效扫描

局域网内大部分IP可能是不在线的,先探测主机是否存活,只对存活的IP进行端口扫描,能减少大量无效任务。比如添加一个存活检测函数:

def is_host_alive(ip):
    try:
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.settimeout(0.2)
        # 尝试连接常见端口(比如80、443)快速判断主机是否在线
        result = sock.connect_ex((ip, 80))
        sock.close()
        return result == 0
        # 如果有权限,也可以用ICMP ping(需要root/管理员权限)
        # return os.system(f"ping -c 1 -W 0.2 {ip} > /dev/null 2>&1") == 0
    except:
        return False

# 在生成扫描任务前先过滤存活IP
alive_ips = [ip for ip in target_ips if is_host_alive(ip)]
scan_tasks = [(ip, port) for ip in alive_ips for port in target_ports]

3. 改用异步IO(更高效的IO密集型方案)

对于端口扫描这种IO密集型任务,异步IO(比如asyncio)比多线程更高效,因为它没有线程切换的开销,只用一个线程就能处理大量并发连接。下面是用asyncio的示例:

import asyncio
import socket

open_sockets = []

async def scan_port(ip, port):
    try:
        # 使用asyncio的异步连接方法
        reader, writer = await asyncio.wait_for(
            asyncio.open_connection(ip, port),
            timeout=0.5
        )
        open_sockets.append(f"{ip}:{port} is open")
        writer.close()
        await writer.wait_closed()
        return True
    except (asyncio.TimeoutError, ConnectionRefusedError, socket.gaierror):
        return False
    except Exception as e:
        return False

async def main():
    target_ips = [f"192.168.1.{i}" for i in range(1, 11)]
    target_ports = range(1, 65526)
    
    # 先过滤存活IP
    alive_ips = [ip for ip in target_ips if is_host_alive(ip)]
    
    # 创建所有扫描任务
    tasks = [scan_port(ip, port) for ip in alive_ips for port in target_ports]
    # 并发执行所有任务
    await asyncio.gather(*tasks)
    
    print(open_sockets)

if __name__ == "__main__":
    asyncio.run(main())

额外优化建议

  • 调整超时时间:局域网内网络延迟低,把超时时间从1秒降到0.2-0.5秒,能大幅减少等待时间。
  • 优先扫描常见端口:如果不是需要全端口扫描,可以先扫1-10000的常见端口,再按需扫其他端口,节省时间。
  • 避免不必要的异常捕获:原代码中except:捕获所有异常,建议明确捕获TimeoutErrorConnectionRefusedError等特定异常,避免掩盖其他问题。

内容的提问来源于stack exchange,提问作者gonuladami

火山引擎 最新活动