资讯动态

Java端口扫描课设源码详解:多线程+Swing+TCP/ICMP探测

发布时间:2026/10/1 5:52:57 来源:尧图企业网站定制
简介端口扫描是网络安全评估与运维中的基础技术通过探测目标主机的开放端口来识别服务与潜在风险。实现时通常需结合ICMP存活性探测与TCP连接检测并利用多线程并发提升效率。在实际工程中如何平衡扫描速度与资源占用、如何避免UI卡顿是开发者常面临的挑战。Java凭借跨平台特性与丰富的网络、并发及Swing GUI库常被用于教学与课设实践。本文以一个完整的Java端口扫描课设为例解析其源码结构、多线程线程池设计、IP段解析及界面交互逻辑并总结常见坑点与优化方向帮助学习者快速掌握可落地的实现思路。1. 这份 Java 端口扫描课设资源值不值得下先看它解决什么问题端口扫描是网络安全课设里几乎绕不开的一个题目但真正动手写的时候才发现坑不少要处理 TCP 和 ICMP 两种探测方式、要判断主机是否在线、还要应付多线程同时扫多台主机不卡界面。这套基于 Java 的端口扫描软件正好把这些全部打包好了——IDEA 直接打开就能跑登录账号 admin、密码 123456自带图形界面、扫描进度条、异常告警窗口还附一份写好的实验报告。适合正在做网安课设但又不想从零开始搭的学生也适合想快速看一遍“端口扫描 多线程 Swing 界面”完整代码结构的 Java 学习者。下面我按实际拆项目的顺序把源码结构、扫描原理、UI 逻辑和踩过的坑一条条讲清楚。2. 拆解源码与核心探测机制src 里到底放了哪些关键类2.1 先看工程全景IDEA 打开前认识这几个文件与目录解压之后你会看到一堆东西不少第一次接触的人会懵。我用表格把每个文件的用途列清楚你心里就有底了。文件/目录作用说明scan.imlIDEA 模块文件识别项目用的别删打开工程靠它.ideaIDEA 工程配置目录包含运行配置、编码设置等整个目录拷走也不影响src源码目录所有 Java 代码都在这后面重点拆它out编译输出目录IDEA 编译后生成的 class 文件工具会自动生成网安实验报告.doc课设报告包含设计思路、核心代码、截图答辩前改一改就能交账号密码.txt登录凭据说明写着 admin / 123456用 IDEA 打开的时候选Open定位到解压后的目录选scan.iml或直接选根目录都行。常见做法是等 IDEA 右下角索引跑完然后直接运行主类。如果你打开后发现编码乱码检查一下 IDEA 右下角文件编码是不是 UTF-8这个资源按 UTF-8 写的可能性最大出现乱码基本是 IDEA 默认 GBK 在作怪切回 UTF-8 重启就好。out目录是我要提醒你注意的它本质上是编译产物IDEA 每编译一次会刷新。如果运行报错说类找不到先执行一次Build - Rebuild Project不要手动去动out里的文件越动越乱。2.2 TCP 与 ICMP 探测为什么 ping 不通就直接判不在线这个课设的需求第一条很明确先用 ping 测试连通性ping 不通直接显示“主机不在网络”。这背后的逻辑和 nmap / SuperScan 的思路一致——ICMP 探测用来判断主机是否存活TCP connect 扫描用来判断端口是否开放。两者分工不同先讲存活判断再做端口探测能省下大量无效的连接尝试。// 主机存活检测基于 ICMP 的简化实现 public boolean isHostAlive(String host) { try { // 关键参数timeout 设为 3000ms // 注意此方法依赖系统 ping 命令Windows 和 Linux 参数基本一致 Process process Runtime.getRuntime().exec(ping -n 1 -w 3000 host); // 等命令执行完看返回码 int exitCode process.waitFor(); return exitCode 0; } catch (Exception e) { // 异常统一按不可达处理不把异常抛到上层 return false; } }这段代码的核心逻辑是调用系统 ping 命令退出码为 0 说明有 ICMP 回包主机在线非 0 则离线。-n 1是只发一个包-w 3000是等待 3000 毫秒。如果你要扫的是 Linux 目标-n 1要改成-c 1这是个常见的坑后面避坑章节会再讲。生产环境更推荐用java.net.InetAddress.isReachable()但课设场景下直接调系统命令反而更直观答辩时也好解释。主机在线后才会进入端口探测。下面这段是 TCP connect 扫描的核心也是整个工具最关键的循环逻辑// 端口探测遍历端口列表尝试建立 TCP 连接 public void scanPort(String host, int port) { try (Socket socket new Socket()) { // 连接超时设 1500ms比 ping 超时短 // 太长会导致整轮扫描时间成倍增长太短容易误报 socket.connect(new InetSocketAddress(host, port), 1500); // 能连上说明端口开放记录结果 System.out.println(端口 port 开放); } catch (IOException e) { // 连接失败可能端口关闭也可能被防火墙过滤 System.out.println(端口 port 关闭或过滤); } }这里的try-with-resources写法保证了 Socket 用完自动关闭避免句柄泄漏。connect的第二个参数是超时时间我一般习惯设 1500ms——比 ping 的 3000ms 短因为端口扫描要探测的端口往往几十上百个单个超时太长会拖垮总时长。你如果扫的是校园局域网1000ms 也够扫跨网段的公网 IP建议调到 2000ms 以上。2.3 操作系统指纹识别从端口开放情况反推系统类型需求第二条要求识别目标操作系统类型。真正意义上的 OS 指纹识别靠的是 TCP 栈特征差异比如初始 TTL 值、窗口大小但课设里通常用端口特征来反推实现简单演示效果也够。常见做法是维护一张“端口特征到系统类型”的映射表扫描结束后做比对。端口特征系统推断说明139、445、3389 开放WindowsSMB 和远程桌面是 Windows 常驻服务22、53、80 开放Linux/UnixSSH、DNS、HTTP 在 Linux 服务器更常见161、162 开放网络设备/开启了 SNMP 的系统很多网络设备默认开 SNMP没有明显特征未知建议多看几个高端口再下结论代码实现上最简单的思路是维护一个列表扫描完后遍历比对// 系统识别根据端口开放列表做简单判定 public String guessOs(ListInteger openPorts) { if (openPorts.contains(445) openPorts.contains(3389)) { return Windows; } else if (openPorts.contains(22) openPorts.contains(53)) { return Linux/Unix; } else { return 未知; } }这个判定比较粗糙但课设报告里完全够用。你在答辩时可以把原理说明白Windows 默认开放 139/445 做文件共享而 Linux 服务器几乎不会开放 4453389 是 Windows 远程桌面的默认端口Linux 上即使装了 xrdp 也很少用默认端口。把这段逻辑讲清楚比盲目追求精确识别更能拿分。3. 多线程扫描的落地写法同时扫多台主机还不卡死 UI3.1 线程池选型为什么用 ExecutorService 而不是裸 new Thread需求第三条明确要求“使用多线程实现能同时扫描多台主机”。很多课设代码喜欢直接new Thread()一把梭但这样有两个问题一是线程数量不受控制扫一个 C 段 254 个 IP每个 IP 扫 100 个端口可能瞬间创建上万个线程直接导致系统资源耗尽二是线程管理混乱没有统一的终止和异常处理机制。我在这个项目里用的是线程池核心参数这样设import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; // 创建固定大小线程池避免无限制创建线程拖垮系统 ExecutorService executor Executors.newFixedThreadPool(10); for (String ip : ipList) { // 每个 IP 的扫描任务封装成一个 Runnable executor.submit(new ScanTask(ip)); } // 所有任务提交完后不再接收新任务执行完自动关闭 executor.shutdown();newFixedThreadPool(10)表示同时最多 10 个扫描线程在跑其余任务排队等待。为什么是 10 而不是 CPU 核数端口扫描是 IO 密集型操作线程主要在等待 Socket 连接超时CPU 基本闲着所以线程数可以大于 CPU 核数。但也不能太贪我之前试过开 50 个线程扫局域网结果目标主机和交换机先扛不住丢包率飙升扫描结果反而不准。10 到 20 是局域网扫描的合理区间。shutdown()的语义要搞清楚它不是强制终止而是拒绝新任务入队、等已提交任务跑完。如果你想中断正在跑的任务需要保存Future对象再调用cancel(true)这个进阶操作在最后一部分讲。3.2 给 UI 回传进度工作线程不能直接碰界面组件多线程扫描必然要往界面上更新进度条和扫描时间这里有个大坑Swing 组件不是线程安全的在工作线程里直接调progressBar.setValue()轻则界面卡顿重则抛InterruptedException或直接死锁。正确做法是把 UI 更新操作丢回事件分发线程EDT// 扫描线程中更新进度必须通过 SwingUtilities.invokeLater 转交 EDT SwingUtilities.invokeLater(new Runnable() { Override public void run() { // 这里的代码运行在 EDT 上可以安全更新界面 progressBar.setValue(currentCount * 100 / totalCount); timeLabel.setText(已用时间 elapsedTime 秒); } });invokeLater的原理是把 Runnable 对象放入 Swing 事件队列由 EDT 按顺序执行。这样扫描线程只管计算结果界面刷新统一交给 EDT两边不打架。还有一个变体叫invokeAndWait它是阻塞等待界面更新完成才返回扫描场景不要用它因为如果 EDT 正在处理别的任务你的工作线程会被卡住反而拖慢扫描。另外提醒一点扫描到一半用户点“停止”需要用一个volatile boolean标志位来控制。扫描循环里每处理完一个端口就检查一次标志位为 true 就 break。只靠executor.shutdown()是停不下来的它只是不接收新任务已提交的活儿照跑。3.3 解析单个 IP 和 IP 段边界判断要先于扫描执行需求要求既能扫单个 IP也能扫一段范围。IP 段解析这段代码是整个工具里最容易出错的也是异常告警窗口最常触发的地方。我把解析逻辑抽成一个独立方法扫描前先调它做校验// 解析 IP 范围返回所有待扫描 IP 列表 public ListString parseIpRange(String startIp, String endIp) { ListString result new ArrayList(); String[] startParts startIp.split(\\.); String[] endParts endIp.split(\\.); // 先做基础格式校验长度不为 4 直接抛异常 if (startParts.length ! 4 || endParts.length ! 4) { throw new IllegalArgumentException(IP 格式不合法); } int start ipToInt(startIp); int end ipToInt(endIp); // 核心边界判断起始 IP 大于结束 IP视为越界 if (start end) { throw new IllegalArgumentException(IP 地址范围出界); } // 防止扫描范围过大超过 65536 个地址直接拒绝 if (end - start 65535) { throw new IllegalArgumentException(扫描范围过大请缩小 IP 段); } for (int i start; i end; i) { result.add(intToIp(i)); } return result; } // IP 字符串转 int方便比较大小和遍历 private int ipToInt(String ip) { String[] parts ip.split(\\.); return (Integer.parseInt(parts[0]) 24) | (Integer.parseInt(parts[1]) 16) | (Integer.parseInt(parts[2]) 8) | Integer.parseInt(parts[3]); }这里把 IP 转成 int 再比较大小比逐段比较字符串要干净得多。ipToInt用了位移运算左移 24 位处理第一段依次类推。两个边界判断很关键start end是需求里明确要弹出“IP 地址范围出界”告警的场景end - start 65535是我自己加的保险不然有人拿0.0.0.0到255.255.255.255来扫程序会卡到天荒地老。捕获到IllegalArgumentException后在 UI 层弹窗提示这就实现了需求里的“异常告警窗口”。4. UI 图形界面与实验报告登录校验、结果表格和告警窗口4.1 登录页面admin/123456 的校验逻辑放在哪里这套资源的登录逻辑非常简单没有连数据库用户名密码是硬编码在代码里的。课设演示场景下这样做完全合理答辩时老师问起来你反而能说清楚“这是为了演示方便生产环境需要换成数据库或配置文件校验”。登录模块的代码大致是下面这样// 登录按钮的点击事件校验账号密码 loginButton.addActionListener(new ActionListener() { Override public void actionPerformed(ActionEvent e) { String username usernameField.getText().trim(); String password passwordField.getText().trim(); // 硬编码校验注意 trim() 去掉首尾空格 // 防止用户不小心输入空格导致登录失败 if (admin.equals(username) 123456.equals(password)) { // 登录成功关闭登录窗打开主界面 dispose(); new MainFrame().setVisible(true); } else { // 登录失败弹窗提示不清空输入框方便用户修改 JOptionPane.showMessageDialog(LoginFrame.this, 账号或密码错误, 登录失败, JOptionPane.ERROR_MESSAGE); } } });trim()是必须写的用户复制粘贴账号时很容易带进空格不处理就会莫名登录失败。登录失败弹窗用的是JOptionPane.showMessageDialog第二个参数是展示内容第三个是标题第四个是消息类型。这里用ERROR_MESSAGE会在弹窗左上角显示红色错误图标演示效果更明显。登录成功后的dispose()只释放当前窗口的资源不会影响 JVM 退出接下来创建MainFrame主窗口实例并让它可见。4.2 扫描结果表格与进度条JTable 数据填充的典型套路主界面的表格展示用的是JTable DefaultTableModel。JTable本身只负责显示数据都放在DefaultTableModel里。扫描结果是一行一行动态加进去的我一般会写这样的方法// 添加一行扫描结果到表格 private void addScanResult(String ip, int port, String status) { // 注意此方法必须由 EDT 调用扫描线程里要包一层 invokeLater DefaultTableModel model (DefaultTableModel) resultTable.getModel(); // addRow 接收一个 Object 数组每个元素对应一列 model.addRow(new Object[]{ip, port, status, currentTime()}); } // 格式化当前时间用于扫描时间列 private String currentTime() { return new SimpleDateFormat(HH:mm:ss).format(new Date()); }addRow的参数是Object[]数组数组长度要和表格列数一致。这里四列分别是 IP、端口、状态、时间。currentTime()里SimpleDateFormat是线程不安全的但因为这个方法只在 UI 线程被调用所以没这个问题。如果你以后想把这段代码改到多线程环境记得用ThreadLocalSimpleDateFormat包一层。进度条和扫描时间的更新逻辑在 3.2 已经讲过了核心就一句话所有 UI 更新必须经SwingUtilities.invokeLater转交 EDT。还有个小细节扫描过程中要把“开始扫描”按钮禁用扫描结束再启用不然用户连点两次会启动两个扫描任务结果表格里数据会乱。4.3 异常告警需求与实验报告对照IP 出界提示在项目中的位置实验报告这份文档是打包资源里容易被忽略但实际很有价值的部分。你不用重新写了但要检查它和代码的逻辑是否一致。我做课设时习惯把需求点和代码位置列成一张对照表答辩前照着过一遍课设需求点代码对应位置报告对应章节ping 测连通状态isHostAlive()方法系统设计-连通性检测模块端口扫描与 OS 识别scanPort()guessOs()核心功能实现多线程同时扫描ExecutorService线程池系统优化-多线程设计IP 范围与异常告警parseIpRange()抛异常 弹窗异常处理模块图形界面与进度显示Swing组件 进度条界面设计报告中如果某些截图和当前代码界面不一致问题不大但建议重新跑一遍程序截几张新图替换进去。老师重点关注的是设计思路和核心代码逻辑尤其是多线程和 Socket 部分。报告里这块如果写得偏薄你可以在“系统设计”一节里补充一段文字说明线程池为什么选固定大小而不是缓存线程池这样显得你真正理解了自己的项目。5. 端口扫描避坑指南五个我在课设里反复踩的坑5.1 扫描结果大量误报“端口关闭”其实是系统 ping 命令参数不同现象在 Windows 上跑得好好的换到 Linux 环境所有主机都显示“不在网络”。原因ping命令参数不一样——Windows 用-n指定发包数Linux 用-c。代码里写死ping -n 1在 Linux 上执行会报“Unknown option”退出码非 0主机就被误判为离线。解决先判断当前系统再拼命令参数或者干脆用 Java 自带的InetAddress.isReachable(3000)替代系统 ping。课设演示一般在 Windows 上做但你得知道这个坑答辩时老师可能会问“换到 Linux 能不能跑”。5.2 扫描过程中界面卡死进度条一动不动现象点“开始扫描”后整个窗口变成白板鼠标转圈几秒后恢复但结果一次性全冒出来。原因扫描 Socket 连接的过程直接写在了按钮的ActionListener里这个监听器本身运行在 EDT 上。你在 EDT 上做耗时操作界面刷新事件全部排队等看起来就是卡死。解决扫描任务必须丢给线程池执行EDT 只负责接收结果并刷新界面。记住一句话任何可能超过 100ms 的操作都不能放在 EDT 上Socket 连接超时动辄一两秒铁定卡死。5.3 阿里云或本机服务器扫出来全部端口关闭怀疑代码写错现象扫某个固定 IP结果全是关闭状态但这个 IP 上的 Web 服务明明能访问。原因目标主机有防火墙默认丢弃未允许的 TCP 连接请求。Socket 连接超时后状态既不是“开放”也不是“明确关闭”而是“被过滤”。代码把所有连接失败统一算作关闭掩盖了真实情况。解决把异常情况单独归类。SocketTimeoutException表示包被丢了可能是防火墙过滤ConnectionRefusedException表示端口确实关闭。你可以在 UI 上增加“过滤”状态或者至少打印日志区分。演示时优先扫本机回环127.0.0.1绕过防火墙干扰。5.4 IP 段解析没做范围限制扫描任务提交了几百万个现象程序内存飙升线程池队列爆满电脑风扇狂转最后直接 OOM。原因parseIpRange没有做上限判断。有人输入192.168.1.1到192.168.255.255地址数量多达六万多个每个地址再扫几十个端口任务总数瞬间到百万级。解决解析时加上限判断超过 65535 个地址直接抛异常提示缩小范围。这个我在 3.3 的代码里已经写了算是用血泪换来的经验。5.5 主机名解析卡住整个 UI输入无法解析的域名直接假死现象在“主机名”输入框填了一个不存在的域名点扫描后界面假死关都关不掉。原因InetAddress.getByName()做 DNS 解析时可能会阻塞好几秒这个调用发生在扫描流程开头如果放在主线程里就会卡住界面。解决把主机名解析也丢到后台线程或者设置一个解析超时。比较简单的方式是用InetAddress.getByName(host)配合线程池的Future.get(2, TimeUnit.SECONDS)实现超时控制时间到了还解析不出来就给用户提示“主机名无法解析”。6. 验证与进阶用本机回环地址把扫描结果跑准拿到资源后第一步验证我会建议你直接走这条流程启动程序 → 用 admin/123456 登录 → 在“单 IP 扫描”栏输入127.0.0.1→ 端口范围填1-1000→ 开始扫描。本机回环地址不会经过防火墙过滤能最真实地反映代码逻辑。如果你本机开了 Web 服务80 端口应该显示开放装了 MySQL3306 也会开放。系统识别结果可能出现“Windows”或“未知”这取决于你是否开放了 445 端口但扫描本身跑通即可后续再换局域网内其他机器验证。验证完基础流程我建议做两个进阶改造这两个点也是答辩时能加分的地方。第一个是把操作系统识别从硬编码改成外部配置用 properties 文件维护端口特征映射表这样以后要加新的系统类型不用改代码只改配置。第二个是把 TCP 扫描从Socket换成 NIO 的SocketChannel非阻塞模式配合Selector实现单线程管理大量连接扫描速度能提升好几倍。这个改动不需要重写整个项目把scanPort里的“逐个连接”换成“批量注册连接事件”即可代码量大约增加 40 行。NIO 端口扫描是生产级工具的常见实现你在报告的“系统优化”一节加上这段描述整个项目的工程含量会明显不一样。我从一开始接到这个题目到后来给学生讲这个项目每一次都会强制走一遍“先看报告、再跑通主流程、最后加一个自己的改进点”的流程。因为课设答辩的风险点从来不是代码能不能跑而是老师一问“你做了什么优化”就卡壳。建议你至少亲手改一个参数、加一个功能哪怕只是把超时时间从 1500ms 改成 1000ms 并验证结果差异都比原封不动交上去更有底气。希望这篇拆解帮到你动手跑一遍这半个小时花得值。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑