尧图精选

Java端口扫描器设计与实现:Socket多线程与Swing实战

🕒 发布时间:2026/10/1 5:51:35 📁 来源:尧图网络
简介面向高校网络安全课程与Java课程设计的端口扫描器完整实现基于IDEA构建提供图形界面和配套实验报告适合需要完成课设或入门网络探测工具的读者。程序参照Superscan、Nmap等常见端口扫描软件的设计思路使用ICMP与TCP探测目标主机支持ping连通性检测、常见端口扫描结果展示并识别目标操作系统类型同时利用多线程并行扫描多台主机支持单个IP、IP地址段与主机名输入也可限定特定端口或扫描全部端口图形界面中实时显示扫描进度与耗时并对IP范围越界等异常情况弹出告警窗口。包体共33个文件以17个class字节码、3个java源码、6个xml配置文件为主体另含1份doc实验报告、txt说明文档与IDEA工程配置解压后导入IDEA即可运行默认登录账号admin、密码123456。资源包采用7z压缩大小约267KB结构清晰已有1156人浏览学习可支撑课程设计演示、代码二次开发与网络安全实验教学。1. 系统端口扫描课设为什么都爱用 Java一个能跑、能讲、能交差的组合拿到「基于JAVA的系统端口扫描软件设计实现」这个网络安全课设题目时大多数人的第一反应是翻教材找 Socket 示例照着敲一个循环挨个连端口然后被「扫一次要等十分钟」劝退。这个课设真正卡人的不是 TCP 握手理论而是三个现实问题多线程并发效率怎么提上去、图形界面点了开始之后为什么卡成白板、以及实验报告里那一堆扫描结果凭什么让老师相信是真的。用 Java IDEA Swing 这套组合做系统端口扫描恰好能把这三件事都兜住——Java 的 Socket 超时机制和线程池是现成的Swing 做课设级 UI 足够快实验报告还能用 JVM 自带工具拿到真实耗时数据。这篇文章就按「先理解原理、再动手实现、最后把坑填平」的顺序把从零到能演示的完整路径讲清楚适合网络安全方向的学生也适合想快速搭一个内网探测小工具的从业者。2. 端口扫描的底层逻辑connect 扫描与三次握手怎么变成代码2.1 端口状态三态判定开放、关闭、被过滤系统端口扫描器最核心的判断依据是 TCP 连接建立过程中的三种反馈。客户端向目标端口发送 SYN 报文后如果端口是开放的服务端会回复 SYN-ACK三次握手完成Socket 连接成功如果端口是关闭的服务端会回复 RST 包握手被中断Java 里会抛出 ConnectException如果目标主机存在防火墙规则直接丢弃 SYN 包不响应客户端就会一直等下去直到触发我们自己设置的超时时间。这三态对应到 Java 代码里就是三种不同的异常分支。很多第一次做这个课设的人只写两个分支——连接成功算开放、连接失败算关闭结果把「无响应」的过滤端口也归类成关闭,这会让实验报告的扫描结果和 Wireshark 抓包对不上。正确的做法是把 ConnectException、SocketTimeoutException、IOException 分开处理分别映射到关闭、过滤、异常三类结果。这里要特别说明一个选型问题Java 原生能实现的只有 TCP 全连接扫描也就是完成整个三次握手后再断开。真正渗透工具里常见的 SYN 半开扫描需要构造原始报文Java 标准库做不到得借助 JPCAP 之类的外包库。课设场景下全连接扫描完全够用而且老师问起来更好解释——你直接说「我们做的是 TCP connect 扫描是对半开扫描的一种折衷实现操作系统的连接日志里能看见完整握手记录」这是加分项不是减分项。2.2 单端口探测的最小可运行代码先不急着上多线程和 UI把单端口探测写对后面所有扩展都是在这个基础上套壳。下面这段代码是扫描器的原子操作验证的是「给定一个 IP 和一个端口返回端口状态」。import java.io.IOException; import java.net.InetSocketAddress; import java.net.Socket; public class SinglePortProbe { private static final int CONNECT_TIMEOUT 200; // 单位毫秒超过则判定为过滤/不可达 public static String checkPort(String host, int port) { // try-with-resources 确保 Socket 在任何分支下都会关闭避免句柄泄漏 try (Socket socket new Socket()) { socket.connect(new InetSocketAddress(host, port), CONNECT_TIMEOUT); return open; // 三次握手完整走通服务端确实在监听 } catch (java.net.ConnectException e) { return closed; // 目标回了 RST说明端口没进程监听 } catch (java.net.SocketTimeoutException e) { return filtered; // 报文被丢弃防火墙拦截或主机不可达 } catch (IOException e) { return error; // 网络层异常比如 ENETUNREACH } } public static void main(String[] args) { // 自测先扫本机 8080自己起个服务再扫 9998通常无人监听 System.out.println(127.0.0.1:8080 - checkPort(127.0.0.1, 8080)); System.out.println(127.0.0.1:9998 - checkPort(127.0.0.1, 9998)); } }这段代码有三个参数直接决定扫描器行为后面调优全靠改它们。第一个是连接超时 CONNECT_TIMEOUT设 200 毫秒是本机扫内网的经验值扫公网要调到 800 到 1000不然丢包一多全是「filtered」第二个是端口号范围单端口验证时随意批量扫描时要防止 int 溢出第三个是 Socket 的复用方式每次探测新建一个 Socket 对象虽然慢一点但状态隔离干净不会出现上一次连接的残留数据干扰下一次判定。有个很容易漏的细节catch 分支之间不要合并。有人喜欢把 SocketTimeoutException 归进 IOException 一起处理代码是短了但扫描结果里「过滤」和「网络错误」混在一起写实验报告时根本没法解释那些 timeout 是什么意思。三态分开后续做统计分析时才知道哪些端口是「不确定」状态。3. 用 IDEA 搭课设工程从空项目到可运行的多线程扫描核心3.1 工程结构划分一个类负责一件事IDEA 新建项目后不要把所有代码堆在一个 Main 类里。课设虽然规模不大但评审老师会看代码结构。合理的划分是四个包entity 放端口状态枚举和扫描结果对象core 放扫描引擎ui 放 Swing 界面util 放 IP 校验和端口段解析工具。src/main/java/com/courseproject/portscanner/ ├── entity/ │ ├── PortStatus.java // OPEN / CLOSED / FILTERED 三态枚举 │ └── ScanResult.java // 单次扫描的完整记录端口号、状态、耗时 ├── core/ │ ├── PortScanner.java // 暴露 scan(host, startPort, endPort) 入口 │ └── ScanTask.java // 单个端口的探测任务实现 Runnable/Callable ├── ui/ │ ├── MainFrame.java // 主窗口负责布局和事件绑定 │ └── ResultTableModel.java // 表格模型承接扫描结果显示 └── util/ ├── IpValidator.java // IP 格式校验防止 Socket 拿非法地址 └── PortRangeParser.java // 解析 22,80,1000-2000 这类输入这个结构不算复杂但每一层都能独立测试。别小看 entity 里的 ScanResult它里面记录扫描耗时这个字段实验报告的「系统效率对比表」全靠它出数据。很多人课设做完才后悔当时没记时间戳最后只能重新扫一遍补数据这是血泪经验。3.2 多线程扫描引擎线程池参数与任务分配机制单线程挨个端口扫是课设翻车率最高的写法——扫 1 到 1024 这 1024 个端口每个端口超时 200 毫秒的话理想情况要 200 多秒实际因为有握手等待跑下来要 6 到 10 分钟。演示时老师盯着屏幕等结果场面非常尴尬。常见做法是用线程池并发扫描把端口均匀分到多个线程里。package com.courseproject.portscanner.core; import com.courseproject.portscanner.entity.PortStatus; import com.courseproject.portscanner.entity.ScanResult; import java.net.InetSocketAddress; import java.net.Socket; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; public class PortScanner { private final int timeoutMs; private final int threadCount; public PortScanner(int timeoutMs, int threadCount) { this.timeoutMs timeoutMs; this.threadCount threadCount; } public ListScanResult scan(String host, int startPort, int endPort) throws InterruptedException { // 固定大小线程池课设规模不需要缓存线程池的弹性扩缩固定的更好解释 ExecutorService pool Executors.newFixedThreadPool(threadCount); ListFutureScanResult futures new ArrayList(); for (int port startPort; port endPort; port) { int finalPort port; // submit 返回 Future后续可以统一等待并收集异常 FutureScanResult future pool.submit(() - probePort(host, finalPort)); futures.add(future); } ListScanResult results new ArrayList(); for (FutureScanResult future : futures) { try { // get() 不设超时等待最慢的探测完成线程池负责调度 results.add(future.get()); } catch (ExecutionException e) { // 单个端口探测抛出未检查异常时记录 error 状态而不是终止整个扫描 results.add(new ScanResult(-1, PortStatus.ERROR, 0)); } } pool.shutdown(); // 必须关闭线程池否则 JVM 进程不会退出 return results; } private ScanResult probePort(String host, int port) { long start System.nanoTime(); try (Socket socket new Socket()) { socket.connect(new InetSocketAddress(host, port), timeoutMs); return new ScanResult(port, PortStatus.OPEN, System.nanoTime() - start); } catch (java.net.ConnectException e) { return new ScanResult(port, PortStatus.CLOSED, System.nanoTime() - start); } catch (java.net.SocketTimeoutException e) { return new ScanResult(port, PortStatus.FILTERED, System.nanoTime() - start); } catch (Exception e) { return new ScanResult(port, PortStatus.ERROR, System.nanoTime() - start); } } }线程池参数在课设答辩时是必问点要能说清楚为什么这么设。threadCount 取 CPU 核心数的 2 到 4 倍个人电脑 8 核 16 线程的机器用 32 是合理的Socket 连接主要是网络 IO 等待不会打满 CPUtimeoutMs 取 200 是先扫内网再扫公网的折衷如果老师让演示的是外网服务器改到 1000 更稳妥。另外注意线程池用完了必须 shutdown不然 IDE 里看到程序不退出只能手动终止——这是 IDEA 跑课设最常见的玄学黑匣子之一。还有一点关于端口范围endPort 传 65535 时for 循环里 port 后恰好溢出变成负数会陷入死循环。虽然这个 bug 在 JDK 里不会立刻暴露但代码评审时被指出来很难看通常做法是把端口段包一层校验如果 startPort 大于 0 且 endPort 小于等于 65535 才放行。4. 给扫描器装上 UISwing 图形界面与主线程卡死的解法4.1 Swing 组件选型与界面布局图形界面是课设的硬指标也是 Java 课设绕不开的 Swing/AWT 选择题。Swing 组件比 AWT 丰富且跨平台表现一致用 JFrame JTable JProgressBar 三个核心组件就能搭出「输入目标、开始扫描、实时看进度、结果表格展示」的标准流程。界面上需要五个元素目标 IP 输入框、端口范围输入框、并发数滑块或输入框、开始扫描按钮、结果表格。扫描状态里加一个进度条虽然实际算不准但能让老师感觉程序「活着」而不是死机了。布局用 BorderLayout 最省事北区放参数面板中心放带滚动条的表格南区放进度条和状态栏。参数面板里「并发数」这个输入框建议给默认值 32很多课设把并发写死成固定值UI 上不暴露这个参数演示时没法体现你对多线程的理解。一个容易忽略的细节IP 输入框要拦非法输入。如果在 Socket.connect 之前不做校验输入「abc」这种字符串时会抛 UnknownHostException 直接卡住线程Swing 的事件响应也会异常中断。常见做法是在开始按钮的监听器里先调 IpValidator 检查 IP 格式不符合就弹 JOptionPane 提示不让探测逻辑拿到脏数据。4.2 SwingWorker 与事件派发线程扫描跑在后台界面不冻结直接在主线程里执行前面写的 PortScanner.scan()点击开始按钮后界面会整个冻结进度条不动、按钮点不了、窗口拖动都卡。原因是 Swing 的事件派发线程EDT被扫描任务占住了所有界面重绘事件排不上队。解决思路是把扫描任务丢到后台线程Swing 提供了现成的 SwingWorkerV, T 工具类。package com.courseproject.portscanner.ui; import com.courseproject.portscanner.core.PortScanner; import com.courseproject.portscanner.entity.ScanResult; import javax.swing.*; import java.util.List; public class ScanWorker extends SwingWorkerListScanResult, ScanResult { private final String host; private final int startPort; private final int endPort; private int timeoutMs 200; private int threadCount 32; // 通过回调让 MainFrame 更新表格和进度条避免直接操作 Swing 组件造成线程竞争 private final ResultTableModel tableModel; public ScanWorker(String host, int startPort, int endPort, ResultTableModel tableModel) { this.host host; this.startPort startPort; this.endPort endPort; this.tableModel tableModel; } public void setParams(int timeoutMs, int threadCount) { this.timeoutMs timeoutMs; this.threadCount threadCount; } Override protected ListScanResult doInBackground() { PortScanner scanner new PortScanner(timeoutMs, threadCount); try { ListScanResult results scanner.scan(host, startPort, endPort); return results; } catch (InterruptedException e) { Thread.currentThread().interrupt(); return List.of(); } } // publish 收集到的中间结果会在这里合并属于 EDT 线程可以直接改表格 Override protected void process(ListScanResult chunks) { for (ScanResult r : chunks) { tableModel.addRow(r); } } Override protected void done() { // 扫描结束后的收尾恢复按钮可用、刷新状态栏 } }使用 SwingWorker 时有几个关键点必须注意。第一doInBackground 里不能碰任何 Swing 组件更新表格要通过 publish/process 或者最后一次性 setTableModel否则会出现 UI 绘制错乱第二超时扫描几百个端口时process 方法会被频繁调用如果每次都重绘整个表格会明显卡顿更好的做法是把中间结果暂存到一个队列每 20 条刷新一次表格肉眼感知不到延迟第三取消按钮要调用 worker.cancel(true)但这里有个坑——cancel 只是设置中断标志Socket 的 connect 阻塞不会响应中断线程池里的任务会继续运行到超时为止所以「取消」很难立即生效UI 上要提示「正在等待当前批次探测结束」。4.3 进度条和日志区让老师知道程序没死演示时最怕的是扫描进行到一半界面看起来像死机。解决方法是加一个日志滚动区每完成一个端口就在 JTextArea 里追加一行「[ 200ms ] 8080/tcp open」同时用进度条显示完成比例。注意 JTextArea 需要设置 setEditable(false)否则点击日志区会插入光标干扰滚动。进度条的计算用已完成端口数除以总端口数由于每个端口耗时不同进度条会不规则跳动这是正常现象反而是「真在干活」的证据。5. 端口扫描必踩的五个坑误判、超时、卡死与杀软拦截5.1 坑一关闭的端口被显示成开放现象扫描本机,端口 22 明明没有 SSH 服务程序却报 open。原因本机防火墙或安全软件对某些端口返回了特殊的响应包Socket.connect 判断为握手成功还有一种情况是目标服务只做端口敲门校验第一次连接不拒绝但也不完成业务交互。解决不要只看一次连接结果。常见做法是对标记为 open 的端口做二次确认方法是断开后重新连接一次如果两次都成功再判定开放更严格的校验是连接成功后发送一个 HTTP HEAD 请求或读一行服务 Banner能拿到响应才算真正开放。代码实现上就是复用 singlePortProbe但加一个 retry 计数。5.2 坑二扫全网段要一个多小时超时参数不会调现象扫描 1-1024 端口超时设 3000 毫秒结果跑了 20 多分钟老师等不及。原因超时越大每个无响应端口等的时间越长。如果目标网段里有 100 个不可达 IP每个 IP 扫 1024 个端口最坏情况就是 100 x 1024 x 3 秒。解决区分扫描场景。扫本地回环和局域网用 200 毫秒超时扫跨网段服务器用 600 到 1000 毫秒扫公网再放宽到 1500 毫秒。另一个优化是引入「半开放端口预筛」——先用 TCP 连接快速过一遍只对 open 的端口做 Banner 抓取不要每个端口都等完整超时。并发线程数也可以动态调整不需要一上来就是 64 并发8 并发对大多数课设场景已经够用。5.3 坑三UI 冻结的真相是 EDT 线程被占用现象点击开始后窗口变白拖不动MAC 上转彩球Windows 上显示「未响应」。原因扫描逻辑直接写在 ActionListener 里占住了 Swing EDT 线程。解决用 SwingWorker 把扫描移到后台这是最根治的办法。另一个辅助手段是给扫描按钮加「扫描中禁用」逻辑防止用户重复点击启动多个扫描任务导致资源争抢。记住一个判断标准如果你的 UI 里没有出现 SwingWorker 或类似后台线程机制扫描 300 个端口以上时 UI 卡死是必然的这不是玄学是事件队列阻塞。5.4 坑四本机扫描被系统防火墙拦截现象扫描 127.0.0.1 的 80 端口明明开了 Apache 却扫出来 filtered。原因Windows 防火墙和部分杀毒软件默认拦截本地回环的异常连接请求或者对短时间内大量 TCP 连接触发了漏扫防御机制直接丢弃 SYN 包。解决演示前先把 Windows Defender 防火墙和第三方安全软件对「Java」进程的联网限制放开Mac 上则要检查「安全与隐私」里的防火墙设置。如果是在虚拟机里跑注意 VM 的网络模式——NAT 模式下扫宿主机的端口结果不可靠最好在同一网络模式的 VM 里互扫验证。5.5 坑五端口号越界与输入格式解析失败现象输入端口范围「0-99999」或者「1-1024,8080」混合格式程序要么报 NumberFormatException要么扫出根本不存在的端口。原因端口是 16 位无符号整数最大值 65535输入解析代码没有做「IPv4 地址 端口段」的分段校验。解决解析端口段时先 split 再逐段 validate超出 0-65535 范围的直接拒绝并提示混合格式「单端口,区间」要用逗号先拆分再对每段判断是单个数字还是「a-b」区间。IP 校验用正则表达式即可注意不要贪图省事直接拿 InetAddress.getByName 当校验——它能接受「localhost」「www.baidu.com」这类域名而课设题目明确要求 IP。6. 让课设报告和答辩有说服力结果验证的三个习惯最后一个环节不是功能演示而是让老师相信你的扫描结果不是编的。我在做这个课设时总结了三个习惯写报告和答辩时都能直接用。第一个习惯是「对照组」提前用系统自带命令验证目标端口状态。Windows 下用 netstat -an | findstr 8080Linux 下用 ss -tlnp | grep 8080把系统输出截图放进实验报告和你的扫描结果并排对比这是最有力的证据。第二个习惯是抓包验证用 Wireshark 或 tcpdump 抓扫描过程的 TCP 握手包筛选出目标 IP 和端口号能看到完整的 SYN → SYN-ACK → ACK 序列说明三次握手真的发生了这比任何文字描述都有说服力。第三个习惯是记录耗时数据把超时参数、并发线程数、目标网段大小、总耗时整理成一张表展示你做了参数对比实验。我自己的习惯是把每次扫描的参数和结果存成 CSV扫描完自动追加一行「host,startPort,endPort,threads,timeout,totalScanTime,openCount」做多组实验时数据差异一目了然。答辩时老师问「你觉得你这个扫描器有什么不足」你可以直接拿数据回答「全连接扫描会被目标主机的连接日志记录隐蔽性不如半开扫描超时参数是静态的没有自适应调整机制后续可以加入 Banner 识别和 CVE 匹配。」这样既有反思又给出了改进方向整个课设的报告逻辑就完整了。以上这些方法都是这个课设最稳的落地路径。在做的时候不用一味求功能多把原理讲透、代码结构清晰、数据可复现就已经超过大多数同题目的完成了。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联 返回资讯列表 →