最轻量无依赖的端口探测方式是用Python socket标准库发起TCP连接尝试,设置0.2秒超时,捕获socket.timeout(主机无响应)和ConnectionRefusedError(端口明确关闭)两类异常,连接后立即关闭套接字。

用 socket 快速探测单个 IP 的端口是否开放
最轻量、无依赖的方式就是用 Python 标准库的 socket 发起 TCP 连接尝试。它不发完整握手包,只做三次握手的客户端行为,响应快、兼容性好,适合局域网内低延迟扫描。
关键点在于设置超时(settimeout()),否则一个死 IP 会卡住整个流程;还要捕获 socket.timeout 和 ConnectionRefusedError 两类异常——前者代表主机存活但端口没响应(可能防火墙拦截),后者才是明确“端口关闭”。
- 超时建议设为
0.1~0.3秒:局域网内足够区分通/不通,设太短容易误判 - 不要用
AF_INET6扫 IPv4 地址,否则在某些系统上会莫名失败 - 连接成功后立刻
sock.close(),避免文件描述符耗尽
import socket
def is_port_open(ip, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(0.2)
try:
sock.connect((ip, port))
return True
except (socket.timeout, ConnectionRefusedError, OSError):
return False
finally:
sock.close()
批量扫整个子网时,别直接 for 循环串行调用
对 192.168.1.0/24 这样的 C 类网段(254 个地址)逐个扫 1 个端口,串行方式要花几十秒,完全不可用。必须并发,但不是无脑开几百个线程。
Python 的 threading 在 I/O 密集场景下比 multiprocessing 更合适:每个线程大部分时间在等 socket 响应,GIL 不构成瓶颈;而进程开销大、启动慢,且跨进程通信反而拖累速度。
- 线程池大小建议控制在
50~100:再大容易触发系统级连接限制或丢包 - 用
concurrent.futures.ThreadPoolExecutor管理,比裸写threading更稳 - 别用
asyncio.open_connection扫大量地址:DNS 解析和连接状态管理在局域网简单场景下反而增加复杂度
构造目标 IP 列表时,别硬编码 192.168.1.x
用户真实环境可能是 10.0.0.0/24、172.16.0.0/16,甚至带 VLAN 的多网段。靠人工改代码不现实。
推荐用 ipaddress 模块解析 CIDR 表达式,它能自动处理掩码、排除网络地址和广播地址,还支持 IPv6(虽然局域网扫 IPv6 少见,但留扩展性)。
- 传参用字符串如
"192.168.1.0/24"或"10.0.0.5-10.0.0.50",别让用户算起止 IP - 跳过
.0和.255(如果掩码是/24),但不要跳过所有末位为 0 的地址——有些设备就配在.0上 - 对 Windows 用户,注意
netifaces获取本机网段更可靠;Linux/macOS 可读/proc/net/route,但没必要,直接让用户输 CIDR 最省事
扫描结果里,“连接被拒绝”和“超时”含义完全不同
这是最容易被忽略的语义差异。很多脚本把两者都标成 “closed”,导致误判防火墙策略或主机状态。
-
ConnectionRefusedError:目标主机收到 SYN,明确回复 RST → 端口确实没服务监听 -
socket.timeout:发了 SYN 没回 SYN-ACK → 可能主机关机、IP 不存在、中间防火墙静默丢包、或目标主机禁用了 ICMP 且 TCP 也过滤了 - 实际中,局域网内出现 timeout 多半是设备离线或 IP 冲突,比“端口关闭”更值得告警
真正有用的输出至少该分三类:open、refused、filtered/timeout。别合并。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











