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

使用Asyncio开发Python端口扫描器遇文件句柄超限问题求解

解决Asyncio端口扫描器全端口扫描时的“文件过多”问题

问题原因

终端默认的**文件描述符上限(ulimit)**远低于全端口扫描所需的数量(比如默认1024,而你硬编码的并发数是5000),导致瞬间创建大量socket连接时超出系统限制,连接全部失败,因此无开放端口输出。PyCharm启动进程时会自动调整ulimit,所以能正常运行。

具体解决方案

1. 动态适配系统文件描述符上限

不要硬编码并发数,根据系统当前的文件描述符软上限自动计算安全的并发量,预留资源给系统进程:

import resource

def get_safe_concurrency_limit():
    soft_limit, _ = resource.getrlimit(resource.RLIMIT_NOFILE)
    # 预留20%的描述符,避免资源耗尽
    return int(soft_limit * 0.8)

在main函数中替换硬编码的并发数:

async def main(host):
    host = host
    port_range = (0, 65535)
    safe_limit = get_safe_concurrency_limit()  # 动态获取安全并发数
    try:
        open_ports = await scan_ports(host, port_range, safe_limit)
        # ... 后续代码不变

2. 分批扫描,避免瞬间创建过多任务

一次性创建65536个扫描任务会瞬间占满文件描述符,改成分批处理:

async def scan_ports(host, port_range, limit):
    open_ports = []
    semaphore = asyncio.Semaphore(limit)
    ports = list(range(*port_range))
    
    # 按批次处理端口
    for i in range(0, len(ports), limit):
        batch_ports = ports[i:i+limit]
        scan_tasks = [scan_port(semaphore, host, port) for port in batch_ports]
        
        for future in asyncio.as_completed(scan_tasks):
            result = await future
            if result:
                open_ports.append(result)
    return open_ports

3. 修复代码执行与阻塞问题

  • 修复main函数执行入口:原代码在终端运行时未启动事件循环,添加完整执行逻辑:
if __name__ == "__main__":
    import sys
    if len(sys.argv) != 2:
        print("使用方式: python port_scanner.py <目标主机IP>")
        sys.exit(1)
    target_host = sys.argv[1]
    asyncio.run(main(target_host))
  • 将同步服务探测改为异步:原guess_service是同步函数,会阻塞事件循环,改成异步版本:
async def guess_service(host, port):
    try:
        reader, writer = await asyncio.open_connection(host, port)
        # 发送测试指令
        test_cmd = b"NOOP\r\n" if port == 21 else b"TEST\r\n"
        writer.write(test_cmd)
        await writer.drain()
        
        # 读取初始响应
        try:
            response_bv = await asyncio.wait_for(reader.read(1024), timeout=1)
            response_bv = response_bv.decode()
            print(f"端口 {port} 初始响应: {response_bv}")
        except asyncio.TimeoutError:
            print(f"端口 {port} 响应超时")
            response_bv = ""
        
        service_name = socket.getservbyport(port)
        writer.close()
        await writer.wait_closed()
        return service_name, response_bv
    except Exception as e:
        print(f"端口 {port} 服务探测失败: {e}")
        return None, None

在main中异步调用:

for port in open_ports:
    try:
        service_name, response = await guess_service(host, port)
        if service_name:
            dict[port] = [service_name, response]
    except Exception as e:
        print(f"无法探测端口 {port} 的服务,可能被隐藏")

4. 临时调整ulimit(快速测试用)

如果需要临时在终端运行,可先调整文件描述符上限:

# Linux/macOS终端中执行,临时设置软上限为65536
ulimit -n 65536

注意:此设置仅对当前终端会话有效,重启后恢复默认。

总结

通过动态适配系统资源、分批控制并发、修复代码阻塞与执行问题,即可解决全端口扫描时的“文件过多”问题,同时保证扫描效率与系统稳定性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 20:12:17