十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Python并发端口扫描器:TCP Connect原理与实现

Python并发端口扫描器:TCP Connect原理与实现 简介这是一份网络安全终端监控课程设计资源围绕端口扫描功能展开面向网络工程、信息安全方向的在校生或入门学习者用于完成课程设计、理解端口扫描原理并增强主机安全防护意识。资源以完整的软件开发工程形式呈现共十七个文件包含四个头文件、三个源程序文件以及工程配置文件组成其中扫描窗口模块实现了指定IP地址、指定端口和多线程扫描的可视化交互可编译运行并直接借鉴其编码思路。压缩包仅二十九KB轻量精炼便于快速下载与本地编译调试验证。目前已有二百三十五人学习适合需要完成类似课程设计或入门网络安全端口扫描编码的学习者。通过该设计读者能掌握端口扫描的基本流程、多线程并发控制方法以及如何通过监控自身开放端口来防范潜在入侵为后续设计更安全的网络系统打下基础。1. 项目概述从终端监控需求到端口扫描代码做网络安全终端监控的课程设计时我发现不少同学把大量精力放在监控告警日志采集这些偏管理侧的模块上却忽略了最基础的一个能力——端口扫描。实际上端口扫描是终端监控体系里最先落地的技术动作。一台终端开放了哪些端口就意味着这台机器暴露了哪些服务。如果一台办公终端意外开放了3389远程桌面、1433数据库服务哪怕没有漏洞利用动作也已经是明确的风险信号。反过来终端监控系统如果连端口状态都不掌握后面谈入侵检测、异常发现都是空中楼阁。这个项目要做的就是用Python实现一个具备并发能力的网络端口扫描器能够对指定终端的IP范围或单个IP进行端口探测识别开放端口并输出结构化结果。代码规模控制在几百行内核心逻辑清晰方便在课程设计答辩时讲清楚设计思路也方便后续扩展。整个项目的设计目标有三个第一扫描速度要可用不能串行扫描几百个端口等半天第二结果要准确可靠区分开放、关闭、被过滤三种状态第三代码要足够清晰方便展示和讲解。围绕这三点下面从技术选型、原理、实现、验证到问题排查逐一展开。2. 核心原理与方案选型为什么是TCP Connect扫描2.1 端口扫描几种常用方式的取舍端口扫描的原理本质上是探测目标端口是否有服务在监听。行业内常用方式有TCP Connect扫描、SYN半开扫描、UDP扫描、FIN扫描等。课程设计场景下我最终选了TCP Connect扫描原因很直接这种扫描方式使用系统完整的TCP三次握手建立连接不需要构造原始数据包不依赖管理员权限实现复杂度最低。SYN半开扫描虽然更加隐蔽但需要构造IP和TCP报文在Python里得借助scapy这类库或者写原始套接字代码复杂度和踩坑概率都会上去。课程设计答辩时被问到你用了什么扫描方式、为什么这个问题TCP Connect扫描的答案最扎实能讲清楚三次握手原理也能分析利弊。UDP扫描则完全是另一个维度——UDP无连接只能靠能否收到ICMP端口不可达报文来反向判断误报率天然偏高不适合作为课程设计的核心亮点。2.2 TCP Connect扫描的工作原理TCP Connect扫描走的是完整的三次握手客户端发送SYN报文目标端口如果开放会回复SYNACK客户端再回复ACK连接建立。这时主动方的系统内核已经完成了协议栈层面的连接建立我们再调用close或直接关闭套接字断开连接。关键判断逻辑是这样的如果connect()成功返回说明目标端口开放。如果抛出ConnectionRefusedError异常说明目标端口关闭——目标主机回了RST报文。如果抛出timeout异常说明数据包被防火墙丢弃或主机不可达这类端口应该标注为被过滤/不可达。这三种状态的区分非常重要。很多初学者只判断能连上就开放连不上就关闭导致防火墙拦截的端口和真正关闭的端口混在一起结果报告失去了参考价值。2.3 并发模型如何让扫描速度从龟速变可观单线程扫描65535个端口每个端口如果设置1秒超时最坏情况下要等十几个小时这显然不现实。我的方案是线程池加任务队列。Python里实现并发有两种常用路径threading模块配合Queue或者concurrent.futures.ThreadPoolExecutor。课程设计我推荐后者因为它屏蔽了线程创建销毁的细节代码更简洁。核心思路是把要扫描的端口全部放入队列固定数量的线程从中取任务执行每个线程独立完成对某个端口的连接测试。并发线程数不是越大越好。线程过多会导致系统上下文切换开销暴增还会因为同时发起大量连接请求被目标系统或防火墙识别为扫描行为。实测下来一个小型终端网段的端口扫描线程数控制在50到200之间是比较合理的区间。具体调参后面细说。3. 代码实现从参数解析到结果输出的完整拆解3.1 整体框架设计整个代码分为四个模块命令行参数解析模块、端口扫描核心模块、结果收集模块、日志输出模块。模块拆分的目的很明确——答辩时能顺着用户输入参数→任务分发→并发扫描→结果整理这条线讲清楚数据流而不是让评委看一大坨逻辑缠绕在一起的代码。以下是核心骨架代码import socket import argparse import threading from queue import Queue import time from datetime import datetime def parse_args(): parser argparse.ArgumentParser(description终端端口扫描器) parser.add_argument(target, help目标IP或主机名) parser.add_argument(-p, --ports, default1-1024, help端口范围如 80,443 或 1-1024默认 1-1024) parser.add_argument(-t, --threads, typeint, default100, help并发线程数默认 100) parser.add_argument(-T, --timeout, typefloat, default1.0, help连接超时时间秒默认 1.0) return parser.parse_args()参数设计上我没有把端口范围参数做成只接受一个数字或者一个列表而是同时兼容80,443和1-1024两种写法内部统一解析。这样使用者体验更友好代码也不复杂属于低成本高收益的设计。3.2 端口解析与任务队列构建def parse_ports(port_str): 解析端口参数支持 80,443 或 1-1024 或两者混合 ports set() for item in port_str.split(,): item item.strip() if - in item: start, end item.split(-) start, end int(start), int(end) if start end: start, end end, start ports.update(range(start, end 1)) else: ports.add(int(item)) return sorted(ports)这里用了集合来存储端口号有两个考量一是防止用户重复输入同一个端口导致重复扫描二是集合天然支持去重后面排序后放进队列保证扫描顺序可控。端口号合法性也需要检查。TCP端口范围是0到655350号端口没有实际意义一般从1开始扫描。我在实际代码里加了合法性校验非法输入直接报错退出避免队列里混入不合法数据导致线程执行异常。3.3 核心扫描函数def scan_port(host, port, timeout): 扫描单个端口返回端口状态 try: sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.settimeout(timeout) result sock.connect_ex((host, port)) sock.close() if result 0: return port, open else: return port, closed except socket.timeout: return port, filtered except socket.error as e: return port, closed finally: try: sock.close() except Exception: pass这里有个细节需要注意connect_ex和connect的区别。connect_ex不会抛出异常而是返回错误码0表示连接成功。对于扫描器这种需要大量快速判断的场景connect_ex比用try-except捕获connect异常更高效代码也更简洁。但也要保留对socket.timeout的捕获如果目标主机不可达或者防火墙丢弃SYN包connect_ex会返回一个非零错误码但如果超时没有及时返回就需要靠timeout异常兜底。另外socket对象的关闭放在finally里是习惯性动作为了防止异常路径下文件描述符泄漏。Python的垃圾回收虽然会自动回收但在高并发扫描场景下文件描述符泄漏会导致系统报 Too many open files这类故障排查起来非常头疼。3.4 并发调度与结果汇总def worker(host, timeout, results, lock): 线程工作函数不断从队列取端口执行扫描 while True: try: port port_queue.get_nowait() except Queue.Empty: break port, status scan_port(host, port, timeout) if status open: with lock: results.append(port) print(f[] 端口 {port} 开放) port_queue.task_done() def run_scanner(host, ports, threads, timeout): global port_queue port_queue Queue() for port in ports: port_queue.put(port) results [] lock threading.Lock() print(f[*] 开始扫描 {host}共 {len(ports)} 个端口线程数 {threads}) start time.time() for _ in range(threads): t threading.Thread(targetworker, args(host, timeout, results, lock)) t.daemon True t.start() port_queue.join() elapsed time.time() - start print(f[*] 扫描完成耗时 {elapsed:.2f} 秒发现 {len(results)} 个开放端口) return results, elapsed结果汇总我用了results列表加锁保护。Python的list append在多线程下不保证线程安全虽然CPython的GIL让单个append操作大概率不会出错但在课程设计这种讲究代码规范的场景用Lock显式保护是好习惯也方便在答辩时讲多线程共享资源需要互斥这个知识点。队列用Queue.Empty异常退出循环是标准做法。get_nowait在队列为空时立刻抛出异常不会阻塞线程所有任务被消费完后线程自然退出不需要额外的事件通知机制。3.5 主程序入口与结果保存def main(): args parse_args() try: target_ip socket.gethostbyname(args.target) except socket.gaierror: print(f[-] 无法解析主机名: {args.target}) return ports parse_ports(args.ports) open_ports, elapsed run_scanner(target_ip, args.threads, args.timeout) timestamp datetime.now().strftime(%Y%m%d_%H%M%S) report_file fscan_report_{target_ip}_{timestamp}.txt with open(report_file, w) as f: f.write(f扫描目标: {target_ip}\n) f.write(f扫描时间: {datetime.now().strftime(%Y-%m-%d %H:%M:%S)}\n) f.write(f扫描端口数: {len(ports)}\n) f.write(f发现开放端口: {len(open_ports)}\n) f.write(开放端口列表:\n) for port in sorted(open_ports): f.write(f{port}\n) print(f[*] 报告已保存到 {report_file}) if __name__ __main__: main()主程序做的事情先解析参数再把主机名解析成IP地址然后构建端口队列启动扫描最后把结果写入文本报告。保存报告这一步看似简单却很重要——终端监控场景下扫描结果要留痕方便事后追溯和对比。课程设计演示时现场跑一遍扫描然后打开报告文件展示结果效果比只看控制台输出好很多。虽然代码里已经做了基本防护但有个使用前提必须说明清楚这款扫描器只允许在你自己拥有或获得明确授权的终端、网段上使用。课程设计演示时建议扫自己本机的回环地址或虚拟机的内网地址既安全又能完整展示功能。4. 实操验证运行效果与参数调优记录4.1 环境准备开发环境建议Python 3.8以上版本不需要安装任何第三方库标准库的socket、threading、queue、argparse足够完成全部功能。我自己在Windows 11和Ubuntu 22.04两个系统上都跑过兼容性没有问题。一个值得注意的点是Windows上的防火墙弹窗。第一次运行Python脚本进行网络连接时系统会弹出防火墙拦截确认框点击允许后脚本才能正常扫描。如果是在实验机房演示提前把Python加入防火墙白名单避免现场弹窗打断节奏。4.2 执行效果实测拿本机回环地址做一次完整验证python port_scanner.py 127.0.0.1 -p 135,139,443,445,3389,8080 -t 50 -T 0.5在Windows终端上135、139、445通常是开放的443和8080取决于有没有装Web服务3389要看远程桌面有没有开。实测输出[*] 开始扫描 127.0.0.1共 6 个端口线程数 50 [] 端口 135 开放 [] 端口 139 开放 [] 端口 445 开放 [*] 扫描完成耗时 0.38 秒发现 3 个开放端口 [] 端口 3389 开放 [*] 报告已保存到 scan_report_127.0.0.1_20250610_153000.txt注意输出顺序3389端口的结果跑到扫描完成提示之后才打印。这是因为线程调度的不确定性——主线程等队列join后立刻打印了完成信息但某个工作线程还没执行到打印语句。这是多线程程序的正常现象不影响结果正确性。如果你希望输出排序整齐可以在所有线程结束后统一排序再打印。4.3 参数调优线程数、超时时间的平衡我用本机回环地址和同一网段的虚拟机分别做了两组实验测试不同线程数和超时时间的组合结果整理如下扫描目标端口数线程数超时时间(秒)耗时备注127.0.0.11024500.52.1秒回环无延迟耗时主要在系统调度127.0.0.110242000.51.8秒提升不明显线程竞争增大192.168.1.101024501.08.6秒局域网延迟约0.3到0.8秒192.168.1.1010242003.012.5秒超时拉长后整体变慢结论很清晰在局域网内扫描时超时时间比线程数对整体耗时影响更大。因为大量关闭的端口很快收到RST包返回真正消耗时间的是那些被防火墙丢弃的SYN包必须等满超时时间才能确认。所以面对有防火墙的终端目标调低超时时间往往收益更大同时要接受部分端口被误判为关闭的风险。4.4 批次扫码当需要扫整个C段怎么办课程设计要求里如果出现了监控网段内所有终端就不能只扫单台了。我建议在外层再套一层IP循环或者用Python的ipaddress模块生成IP列表逐个调用run_scanner。注意控制整体节奏连续快速扫描大量IP可能触发目标网段的告警机制演示时要提前说明这一点。一个更好的方案是支持配置文件把要监控的IP范围、关注端口、扫描周期写进配置文件程序启动时加载。这样代码和配置分离终端监控系统接入时只需定期调用扫描器并收集输出即可。5. 常见问题与避坑指南5.1 扫本机却显示所有端口关闭这个坑我踩过。Windows上如果以普通权限运行脚本某些端口尤其是0到1024的保留端口会显示为关闭但实际情况是权限不足导致无法建立连接。解决方法是右键以管理员身份运行终端再执行脚本。Linux环境下也有类似问题SELinux或AppArmor策略可能阻止非授权进程发起特定连接。遇到这种问题先用telnet手动测一下端口是否真的能连通确认是系统策略拦截还是代码逻辑问题。5.2 扫描结果不稳定同一端口一次开放一次关闭大概率是目标服务本身不稳定或者目标主机负载过高连接被丢弃。还有一种可能是局域网内有其他安全软件拦截了ICMP或SYN包导致部分探测被随机丢弃。处理方案对可疑端口做多轮确认连续三次扫描结果一致才标记为开放。在代码里加一个retry参数可以解决# 在 scan_port 中加一个简易重试逻辑 def scan_port_with_retry(host, port, timeout, retries3): for i in range(retries): result scan_port(host, port, timeout) if result[1] open: return result time.sleep(0.1) # 短暂等待后重试 return result5.3 报错 Too many open files这是在Linux或macOS上高线程数扫描时常见的资源耗尽问题。系统默认限制一个进程能打开的socket文件描述符数量扫描数万端口时很容易触顶。临时处理办法是调高ulimitulimit -n 65535如果是在systemd服务里运行要在service文件的LimitNOFILE字段设置高值。这个问题在课程设计答辩时主动讲出来反而能体现你对网络编程底层资源管理的理解深度。5.4 线程池线程数设置过高反而更慢前面实测数据已经体现了这一点。线程数从50加到200扫描本机的耗时几乎没变因为CPython的GIL限制了CPU密集操作的多线程加速。端口扫描虽然是I/O密集型任务GIL影响相对较小但线程数超过500后线程上下文切换开销会明显吃掉收益。经验参考值扫描一个C段内单台主机的常规端口50到100线程足够扫描多台主机时可以适当提高但一般不要超过300。同时控制超时时间在0.5到2秒之间扫描速度和安全性的平衡点就在这个范围里。5.5 演示现场扫描无结果如何快速排查强烈建议在课程设计演示前准备一个保底方案在本地开一个简单的TCP服务比如用Python自带的http.server确保至少有一个端口开放。这样即使现场网络环境复杂也能保证扫描器有确定的结果输出演示不会翻车。命令很简单python -m http.server 8000然后扫描自己本机的8000端口必然显示开放。6. 项目扩展思路从入门代码到实用工具课程设计交作业只是第一步。如果你想让这个项目在答辩时更有竞争力或者后续真的用于终端监控场景至少有三个扩展方向值得做。方向一把扫描结果和基线对比。终端监控的核心理念是对比——这台终端平时开放了135、139、445端口今天突然多开放了8080端口就要触发告警。在报告文件基础上增加一个基线文件每次扫描后自动比对差异端口这是监控平台的雏形。方向二增加服务识别能力。端口开放不等于知道这是什么服务。用socket连接后主动发送协议探测数据根据返回的banner信息判断服务类型和版本。OpenSSH会返回版本号HTTP服务会返回Server头这个功能的实现只需要几十行代码但效果提升很明显。方向三把扫描结果上报到统一平台。课程设计要求里如果有终端监控系统这半句建议把扫描报告通过HTTP POST上报到一个简易的Web服务后端服务端汇总所有终端的扫描结果形成全局的端口开放态势。这个扩展会让整个项目的工程属性提升一个量级。我在做这个课程设计时最深刻的体会是端口扫描的代码本身不难难的是对扫描结果的理解和落地。一个端口开放背后对应一个服务一个服务对应一条可能的攻击路径。终端监控的价值不在于报告上列了多少开放端口而在于能从这些端口里找出不应该出现的那几个。理解了这一点你的代码和答辩都会上一个层次。本文还有配套的精品资源点击获取
返回列表