☰
Java端口扫描器原理与实现:TCP三次握手、UDP超时判定及线程池提速
2026/9/30 1:24:15 网站建设 项目流程

简介:基于Java的TCP/UDP端口扫描器是一项计算机网络课程设计作品,面向网络编程入门者和需要完成课程设计、大作业或实训项目的学生。工具采用多线程并发实现,支持在界面中设置目标IP、起始与结束端口(范围0~65535)以及线程数(0~200),扫描过程中将开放端口实时显示在主窗体,结束后还可保存结果,便于后续分析。压缩包共12个文件,以Java源码、编译后的class字节码为主,另含Eclipse工程文件、Markdown说明文档和运行界面截图,整体仅96KB,结构紧凑,便于快速导入工程并查看设计说明。已有131人浏览学习,说明该作品对同类课程项目具有参考价值。通过源码可学习多线程端口扫描的任务调度与Socket探活思路,结合文档能理解TCP/UDP连接探测的判别逻辑;Eclipse工程文件让项目可直接加载运行,PNG截图则展示了实际界面效果,适合作为网络课程设计的起步模板或进一步扩展的基础。

1. 课程设计里的端口扫描器:为什么网上现成的代码交上去容易翻车

“基于 Java 实现的 TCP/UDP 端口扫描器”是计算机网络课程设计里出现率极高的题目。很多同学第一反应是去网上找一份现成代码,改个类名就交,结果答辩时老师问“TCP 扫描为什么能判断端口开没开”“UDP 扫不到回包怎么解释”,直接卡壳。更常见的是代码跑起来到处是坑:只能扫本机 127.0.0.1,换到局域网就全部报超时,UDP 部分扫什么都显示开放,线程开大了直接报 too many open files。这些问题的根源不在代码量,而在对 TCP 三次握手、UDP 无连接特性、超时语义这三件事的理解。这篇文章把 TCP/UDP 端口扫描器的最小实现、线程池提速、UDP 误判边界讲透,并给出可直接复现的 Java 代码和参数方案,适合计算机网络课程设计、Java 课程设计以及想补网络编程基础的人。核心目标是让你写完能讲清、讲完能扛住追问。

2. 先搞清楚扫描器在看什么:TCP 的握手信号与 UDP 的沉默

2.1 端口扫描的三种思路:全连接、半开扫描与 Java 的边界

端口扫描的本质是向目标端口发起一次探测,然后观察协议栈的反应。按探测方式划分,最常见的三种思路是:

  • TCP 全连接扫描:完整走完 TCP 三次握手。连接建立成功即端口开放,收到 RST 即端口关闭,超时即被过滤。
  • TCP 半开扫描(SYN 扫描):只发送 SYN 包,收到 SYN-ACK 判定开放,收到 RST 判定关闭,然后主动回 RST 而不是完成握手。这种方式不留下完整连接记录,扫描速度快,但需要构造原始数据包。
  • UDP 扫描:发送 UDP 探测报文,等待响应或 ICMP Port Unreachable 回包。

Java 课程设计里最常踩的第一个坑,就是把“半开扫描”写进报告,但代码里压根没有半开扫描。原因在于 Java 标准库的 Socket 抽象由操作系统内核完成三次握手,你无法让内核发一个 SYN 后不完成连接而去等待 SYN-ACK。Java 没有 raw socket 能力,所以纯 Java 实现的是 TCP connect 扫描(即全连接扫描),不是 SYN 扫描。答辩时如果老师问“你实现的是不是半开扫描”,最稳的回答是:“Java 的 Socket 由内核完成完整握手,所以本设计用的是全连接扫描,判断依据是连接建立成功、RST 和超时三种信号。”

这个边界不是缺陷,课程设计的要求通常是“掌握端口扫描原理并做工程实现”,全连接扫描在原理层面完全够用。真正要注意的是:报告里的措辞应当和代码行为一致,不要写“本系统实现 SYN 半开扫描”然后代码里全是 new Socket()。

2.2 TCP 探测的三种判定信号:连接建立、RST 与超时

TCP 端口扫描的判断基础是三次握手的状态机。扫描器向目标端口发送 SYN 包,目标端口有三种响应:

  1. 端口有进程监听,且防火墙允许入站:目标回 SYN-ACK,扫描器回 ACK,连接建立。这时扫描器能确认端口开放。
  2. 端口没有进程监听:目标主机协议栈直接回 RST。扫描器收到 RST,确认端口关闭。
  3. 目标在网络上不可达,或防火墙静默丢弃探测包:扫描器既等不到 SYN-ACK 也等不到 RST,只能在超时时间到达后标记为 filtered(被过滤)。

用 Java 的 Socket 来观察这三种信号非常直接。new Socket() 之后调用 connect 并传入超时时间时,connect 方法内部由内核发起完整的握手过程。如果连接成功,connect 正常返回;如果收到 RST,抛 ConnectException;如果超时未收到任何响应,抛 SocketTimeoutException。这三个异常分支就是 TCP 扫描器的核心逻辑。

关于超时时间的选型,我一般遵循一个经验区间:扫描 127.0.0.1 或同网段主机,timeout 设 200~500 ms 足够;扫描跨网段的局域网主机,设 1000 ms;扫描公网地址,设 2000~3000 ms。超时太短会把响应慢的开放端口漏报成 filtered,超时太长又会导致全端口扫描的时间不可接受。具体数字需要在课程设计报告中说明依据,这是最容易加分的地方。

2.3 UDP 扫描的“黑洞”现象:为什么没消息不能直接判关闭

UDP 没有握手,也没有 ACK 机制。一个 UDP 端口开放着,不代表它会回应你的任意报文。例如一台机器的 DNS 服务监听 53 端口,你只发一个空数据或一个字节的 0x00,DNS 服务可能直接忽略,因为请求格式不正确。反过来,UDP 端口没有监听时,目标主机的协议栈通常回一个 ICMP Port Unreachable 报文,但这个回包行为不是强制的:目标主机可能禁用 ICMP,也可能因为 ICMP 错误报文限速而不回。

于是 UDP 扫描的实际判定逻辑只有三档:

  • 收到响应数据:端口开放。
  • 收到 PortUnreachable 异常(即 ICMP 端口不可达):端口关闭。
  • 超时没有任何响应:无法区分“开放但不应答”和“被过滤”,标准术语记为 open|filtered。

这个 open|filtered 状态是 UDP 扫描不可消除的固有模糊性。课程设计报告中,最不该犯的错误就是把超时直接写为“端口关闭”。一份严谨的报告应当写明:UDP 扫描的结果分为 open、closed、open|filtered 三类,其中第三类是待确认项。这也是后续第 5 章要引入多轮探测的原因。

3. 可复现的 Java 实现:TCP/UDP 扫描核心代码与线程池提速

3.1 最小 TCP 扫描实现:三个异常分支对应三种端口状态

先写一个单端口 TCP 扫描方法,这是整个扫描器的基础。代码结构很简单,但有一个细节很容易翻车:不要用 new Socket(host, port) 这种构造方式,因为它默认使用系统底层的超时时间(可能长达几分钟),一旦目标主机丢弃 SYN,这个调用会卡很久。正确做法是先创建无连接 Socket 对象,再显式调用 connect 方法传入超时时间。

public static String scanTcpPort(String host, int port, int timeoutMs) { try (Socket socket = new Socket()) { // 主动发起 TCP 三次握手,在 timeoutMs 内等待完成 socket.connect(new InetSocketAddress(host, port), timeoutMs); return "open"; } catch (SocketTimeoutException e) { // SYN 发出后既没收到 SYN-ACK 也没收到 RST // 通常是防火墙静默丢弃,或目标主机不可达 return "filtered"; } catch (ConnectException e) { // 目标协议栈回了 RST,说明端口没有进程监听 return "closed"; } catch (IOException e) { // 其他网络层错误(路由不可达、地址异常等) return "unknown"; } }

这个方法的逻辑就是第 2 章讲的三种信号映射。使用 try-with-resources 确保 socket 在任何情况下都被关闭,否则每次探测都会泄漏一个文件描述符,扫描端口多了之后程序必崩。timeoutMs 参数直接传给 connect,它控制的是等待握手完成的期限,不是 SO_TIMEOUT 的读超时,两者语义不同,不要混用。

调用示例:

System.out.println(scanTcpPort("127.0.0.1", 8080, 500)); System.out.println(scanTcpPort("127.0.0.1", 9999, 500));

3.2 线程池提速:把全端口扫描从小时级压到分钟级

单线程扫描 65535 个 TCP 端口,按每个端口 200 ms 计算,最坏情况下需要 3.6 小时。这个时间在课程设计演示时完全不可接受。解决办法是用线程池并发探测,但线程数不是越大越好。

import java.net.*; import java.util.*; import java.util.concurrent.*; public static List<Map.Entry<Integer, String>> scanTcpRange( String host, int startPort, int endPort, int timeoutMs, int threads) throws InterruptedException { ExecutorService pool = Executors.newFixedThreadPool(threads); List<Callable<Map.Entry<Integer, String>>> tasks = new ArrayList<>(); for (int port = startPort; port <= endPort; port++) { int p = port; tasks.add(() -> Map.entry(p, scanTcpPort(host, p, timeoutMs))); } // invokeAll 保持任务提交顺序,后续结果与端口一一对应 List<Future<Map.Entry<Integer, String>>> futures = pool.invokeAll(tasks); pool.shutdown(); List<Map.Entry<Integer, String>> results = new ArrayList<>(); for (Future<Map.Entry<Integer, String>> future : futures) { try { results.add(future.get()); } catch (ExecutionException e) { // 单个任务异常不阻塞整体扫描 results.add(Map.entry(0, "error")); } } return results; }

线程数的选择依据是目标网络环境和本机资源。我一般这样设:扫描本机回环地址用 64~128 线程,扫描局域网用 32~64 线程,扫描公网地址用 16~32 线程。原因是每次 connect 都会占用一个文件描述符,Linux 默认的进程 fd 限制通常是 1024,如果线程数设为 1024,每个线程同时建 socket,还没等扫描完就先报 too many open files。另外对同一目标发起过高并发会导致网络栈或目标防火墙触发连接限速,反而降低成功率。

时间账值得写进报告:100 个线程并发扫描本机全端口,每个端口超时 500 ms,整体最坏耗时约为 65535 / 100 * 0.5 秒,约 5.5 分钟,实际由于开放端口响应快、连接拒绝响应快,大部分任务远低于超时时间。这个估算公式能体现你对并发模型的理解。

3.3 UDP 扫描实现:探测包、ICMP 错误与超时三态判定

UDP 扫描用 DatagramSocket 实现。课程设计里一个常见的错误写法是:new DatagramSocket() 后直接 send 一个包,然后 receive 等待,收不到就判关闭。这样写出来的程序在真实环境下几乎全是误报。正确做法是先调用 DatagramSocket 的 connect 方法,把 socket 与目标地址和端口绑定,这样后续 receive 只接收来自该端口的数据报,且内核能把 ICMP Port Unreachable 错误转成 Java 的 PortUnreachableException。

public static String scanUdpPort(String host, int port, int timeoutMs) { try (DatagramSocket socket = new DatagramSocket()) { // UDP 的 connect 不是三次握手,而是限定收发地址, // 让 receive 只关注来自该端口的响应和 ICMP 错误 socket.connect(new InetSocketAddress(host, port)); socket.setSoTimeout(timeoutMs); byte[] probeData = new byte[]{0x00}; DatagramPacket probe = new DatagramPacket( probeData, probeData.length, new InetSocketAddress(host, port)); socket.send(probe); byte[] buf = new byte[512]; DatagramPacket response = new DatagramPacket(buf, buf.length); try { socket.receive(response); // 收到了任何数据,说明端口有进程应答 return "open"; } catch (PortUnreachableException e) { // 内核收到 ICMP Port Unreachable,端口关闭 return "closed"; } catch (SocketTimeoutException e) { // 既没数据也没 ICMP 错误,状态不可判定 return "open|filtered"; } } catch (IOException e) { return "unknown"; } }

这个判断逻辑对应第 2 章讲的 UDP 三态。探测包只发一个字节 0x00,这是为了让代码可控:很多 UDP 服务对畸形请求不回包,所以我们不把“无响应”当关闭,而是留给开放与过滤两个可能性。如果你选的课程设计题目要求识别具体服务类型(比如 DNS、NTP、SNMP),可以在探测包里放对应协议的查询报文,但这属于进阶功能,基础版先保证判定逻辑不误报就行。

需要特别说明的是,PortUnreachableException 依赖目标主机回 ICMP 错误报文。在 Linux 环境下,当扫描速率过高,内核的 ICMP 限速机制会丢弃多余的 Port Unreachable 报文,导致后续端口全部进入超时分支。在 Windows 环境下,Java 收到 ICMP 错误并转换为异常的可靠性也低于 Linux。所以 UDP 扫描的结果天然带有不确定性,代码里输出 open|filtered 而不是强行归类,正是对这种不确定性的诚实表达。

3.4 合并 TCP/UDP 扫描主流程:一次运行覆盖两种协议

把上面两个方法整合到一个控制类里,方便命令行调用。主流程设计为:解析参数,按协议类型分发到对应扫描方法,收集结果后按端口排序输出。

public static void main(String[] args) { String host = "127.0.0.1"; String protocol = "tcp"; // tcp | udp | both int timeoutMs = 500; int threads = 64; if (protocol.equals("tcp") || protocol.equals("both")) { List<Map.Entry<Integer, String>> tcpResults = scanTcpRange( host, 1, 65535, timeoutMs, threads); tcpResults.stream() .filter(e -> !e.getValue().equals("closed")) .forEach(e -> System.out.println("tcp " + e.getKey() + " " + e.getValue())); } // udp 分支同理,注意全端口扫描默认只扫常用端口区间,详见下文 }

这里有一个所有 UDP 扫描器都必须面对的工程问题:UDP 全端口扫描在公网上不可行,因为每个超时端口要等满 timeoutMs,而 UDP 的 timeout 不能像 TCP 那样普遍共用数百毫秒。常见做法是默认只扫 1~1024 端口加常见服务端口列表,你可以在参数里加一个 --udp-top-ports 选项,默认加载 53、67、68、123、161、500、1900、5353 等端口。这个设计不是偷懒,是对 UDP 扫描时间成本和 ICMP 限速问题的合理妥协,课程设计报告中写明这一点会加分。

4. 端口扫描器踩坑排查:5 个常见翻车场景与解决路径

4.1 现象:扫描本机一切正常,换到局域网 IP 就全部超时

这是课程设计中最常见的翻车现场。本机 127.0.0.1 全端口秒出结果,把 host 改成同一台机器的局域网 IP(比如 192.168.1.100),结果几乎全是 filtered 或 open|filtered。

原因有两层:一是回环地址的流量根本不经过网卡和外层防火墙,本机防火墙通常默认放行;二是换到局域网地址后,数据包要经过本机防火墙的入站规则,Windows 防火墙和 Linux iptables 默认都会丢弃未允许的入站 SYN 包,尤其当目标端口不是常用服务端口时。

解决办法:先把被测机器的防火墙入站规则关掉或者加上临时放行规则,再跑测试。Windows 上是控制面板的“Windows Defender 防火墙”临时关闭专用网络入站;Linux 上是sudo ufw disable或iptables -F(仅限课程设计环境,测试完恢复)。注意这只能解决“被本机防火墙过滤”的场景,如果目标是其他主机,还要检查目标机的防火墙。排查时先用 ping 确认主机在线,再用telnet 目标IP 端口验证单端口连通性,逐层定位。

4.2 现象:UDP 扫描结果像黑匣子,要么全部 closed 要么全部 open|filtered

UDP 扫描跑完后,发现一个明明在监听 DNS 服务的 53 端口显示 open|filtered,而一个没进程监听的端口也显示 same 状态;或者反过来,所有端口都显示 closed,包括那些确实在收包的 UDP 服务。

前一种情况多半是探测包无效:你发一个 0x00 字节给 DNS 53 端口,DNS 服务直接丢弃,不会回包,所以显示 open|filtered 是正确行为,不是 bug。后一种全 closed 的情况,通常是客户端收到了 ICMP Port Unreachable,但这是来自目标主机的另一个端口或是因为目标主机整体不可达而由中间路由器回的错误,如果用了未 connect 的 DatagramSocket,错误范围控制不住。

解决路径有两条:一是确认每个 UDP 端口回包的特性,课程设计阶段优先选择会回包的 UDP 服务做演示,比如用 NTP 时间查询或自定义的 Java UDP 服务端;二是把探测包改成目标服务能识别的格式,比如对 DNS 53 端口发一个标准的 DNS 查询头,这需要你了解应用层协议,但很能体现水平。如果实在无法让服务回包,就在报告里说明 open|filtered 的含义,解释这是 UDP 协议的固有特性。

4.3 现象:线程数开大后系统资源耗尽,扫描反而更慢

有的同学为了追求速度,把线程池线程数设成 1000 甚至 65535,结果程序运行到一半抛出 too many open files,或者系统卡死。

原因是每个 Socket 在操作系统层面对应一个文件描述符(fd),Linux 普通用户的 ulimit -n 默认值通常是 1024。线程数 1000 时,瞬间创建的 socket 数远超 fd 上限。另外线程本身有栈内存开销(默认 1 MB 虚拟内存),大量并发线程还会增加上下文切换成本,扫描吞吐量不一定随线程数线性增长。

解决:线程数设置不超过 256,最常见的选择是 64。另一个要点是确保所有 socket 用 try-with-resources 关闭,否则线程池复用线程时 fd 泄漏会累计到不可收拾。排查时用lsof -p 进程号 | wc -l看运行中的 fd 数,如果只增不减,说明有资源没释放。全端口扫描建议按端口段分批执行,每批 10000 个端口,跑完一批再跑下一批。

4.4 现象:结果乱序,报告里端口号前后跳动

并发扫描后,结果列表如果按完成时间打印,输出顺序是乱序的:8080 出现在 22 前面,443 在 80 前面。课程设计演示时看起来很不专业。

原因很好理解:Future 任务的完成时间不同,先完成的先打印,但顺序不代表端口大小。解决办法有两个方向:一是在提交任务时用一个带端口的队列保存结果,最后统一按端口排序;二是像 3.2 节代码那样使用 invokeAll,它的返回 List 顺序与任务提交顺序一致,天然按端口升序。如果用的是 CompletionService,则必须自行按端口排序再输出。

4.5 现象:socket 连接成功后调 getInputStream().read() 卡住

给 TCP 扫描加 banner 功能(读取服务版本信息)时,连接建立后调用 socket.getInputStream().read(),程序卡在那一行,直到读超时。

原因在于很多服务不会主动向客户端发送 banner。HTTP 服务要等客户端发送请求行才响应,Redis 服务会打印欢迎信息但那是 Telnet 协议的行为,MySQL 协议也是先发握手包。对于不会主动发数据的目标,read() 收不到任何字节。如果只为判断端口开闭,完全不需要读数据;如果要实现 banner 识别,必须预先知道目标服务的协议行为,不能对所有端口统一读取。

解决:banner 识别作为独立可选功能,只对已知协议的目标端口启用,并且给读操作设置 SoTimeout(例如 1000 ms)。不要在基础扫描逻辑里包含 read 操作,否则扫描时间会被无限拖长。

5. 加分改造:给扫描器加参数、多轮探测与结果导出

5.1 命令行参数:把写死的代码变成可配置工具

课程设计评审老师通常不喜欢只能通过改代码来换参数的“扫码器”。一个带命令行参数的小型扫描器,接收主机地址、协议类型、端口范围、超时时间、线程数,既体现了工程意识,又方便演示不同场景。常见的参数设计如下:

public static ScanConfig parseArgs(String[] args) { ScanConfig config = new ScanConfig(); for (int i = 0; i < args.length; i++) { switch (args[i]) { case "-h": config.host = args[++i]; break; case "-p": config.portRange = args[++i]; break; case "-t": config.protocol = args[++i]; break; // tcp / udp / both case "-timeout": config.timeoutMs = Integer.parseInt(args[++i]); break; case "-threads": config.threads = Integer.parseInt(args[++i]); break; default: System.out.println("用法: java PortScanner -h 127.0.0.1 -p 1-1024 -t both"); } } return config; }

ScanConfig 是一个简单的 POJO 类,字段包括 host、portRange、protocol、timeoutMs、threads。portRange 用 "1-1024" 这样的字符串表达,解析时拆成 start 和 end。参数表里值得写的三个默认值:timeoutMs 默认 500,threads 默认 64,protocol 默认 tcp。这三个默认值对齐的是本机/局域网环境,如果想扫公网,需要在答辩时说明调参逻辑。

课程设计中最常见的提问是“为什么 timeout 不是固定值”。你要能答上来:timeout 反映的是网络往返时间与丢包容忍度。局域网 RTT 通常小于 10 ms,500 ms 足够;公网跨运营商 RTT 可能 100 ms 以上且有丢包重传,需要 3000 ms。

5.2 多轮探测与超时分级:降低 UDP 误判的正确做法

UDP 扫描最大的痛点是 open|filtered 的模糊状态。一个实用的工程方案是分三轮扫描,每轮作用不同:

第一轮快速扫描。TCP 用 300 ms 超时快速摸一遍全部端口,UDP 只扫常用端口列表,超时 800 ms。这一轮的产出是候选列表,把所有非 closed 状态都收进来。

第二轮重点复核。对第一轮结果为 open|filtered 的 UDP 端口,逐个做 3 秒超时的重测,且对同一端口最多发三次探测包。这样做能绕过 ICMP 限速窗口(内核的 ICMP 错误限速通常是按秒计数的,多轮间隔几秒再探测,能显著提高收到 Port Unreachable 的概率)。同一探测目标状态如下:

轮次覆盖范围超时设置判定目的
第一轮全端口或常用端口列表300~800 ms快速筛出候选端口
第二轮候选中的 UDP openfiltered 端口3000 ms,最多 3 次
第三轮第二轮仍为 openfiltered 的端口加载应用层探测报文

第三轮的“应用层探测报文”可以只做一个可扩展接口。比如对 DNS 服务发一个构造好的 DNS 查询包,对 NTP 发 NTP 版本请求,收到任何字节就算 open。这个设计能写进报告作为“基于协议指纹的 UDP 服务识别”,是课程设计里很扎实的扩展亮点。实际实现中,我给第三轮的每个端口最多 5 秒,因为有些服务对畸形包处理慢。

多轮探测的代价是总耗时增加,但换来的可信度提升非常明显。演示时选 5 个端口,一轮就能出结果;全端口扫描用三轮,能在一个小时左右跑完一个常见端口集合,这种时间预算要提前算好。

5.3 结果输出:控制台表格、CSV 导出与轻量 GUI

扫描结果只打 System.out.println 的话,端口多了看不清。我给课程设计版配了两种输出:控制台对齐表格和 CSV 文件导出。CSV 是最稳妥的格式,后续可以用 Excel 或 Pandas 做统计图表,汇报时直接用数据说话。

public static void writeCsv(String filePath, List<ScanResult> results) throws IOException { try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(filePath))) { writer.write("protocol,port,state,response_time_ms"); writer.newLine(); for (ScanResult r : results) { writer.write(String.format("%s,%d,%s,%d", r.protocol, r.port, r.state, r.responseTimeMs)); writer.newLine(); } } }

ScanResult 类的字段建议至少包含 protocol、port、state、responseTimeMs 四项。加了响应时间字段后,你可以额外输出一个“高延迟端口”列表,判断哪些目标可能存在流量过滤或网络拥塞,这也是报告里的好素材。

GUI 部分我不建议把逻辑写进 Swing 事件线程。如果你需要图形界面,用一个 Controller 类持有扫描逻辑,Swing 界面只负责调用 controller.start(config) 和监听进度事件。扫描放后台线程执行,用 SwingWorker 或线程池回调更新 JTable 模型。核心网络逻辑与展示层解耦,既是好习惯,也能避免界面卡死被老师当场发现。图形界面不是必需项,但一旦做,至少要保证扫码过程中窗口能拖动、能取消任务。

6. 验证你的扫描器:用 netstat 与本机服务做对照实验

课程设计验收时,最有力的证据不是“我扫出了这些端口”,而是“我的扫描结果和系统真实监听状态一致”。验证方法很简单:先查本机真实监听的端口列表,再拿你自己的扫描器去扫,逐项对照。

Windows 用netstat -ano | findstr LISTENING,Linux 用ss -utln或netstat -utln。从中挑 3 个 TCP 监听端口和 1 个不存在的端口(比如 39999),用你的 TCP 扫描器分别扫,预期是前 3 个显示 open,最后一个显示 closed。用防火墙规则拦截一个端口的入站连接,再扫它,预期显示 filtered,这能验证超时分支被正确触发。

UDP 验证稍微麻烦一点:可以写一个 30 行的 Java UDP 服务端程序,绑定 8888 端口,收到任何数据就回一个固定字节。然后用你的 UDP 扫描器扫 8888,预期返回 open;再扫一个未监听的 UDP 端口,预期要么 closed 要么 open|filtered,但注意全 closed 时要去目标机确认 ICMP 没有被防火墙丢弃。重复扫描同一个端口 5 次,看结果是否稳定;如果一次 open 一次 open|filtered,说明探测报文或网络栈有问题,恰好暴露了 UDP 扫描的真实难度。

我习惯在提交课程设计前做最后一次“三段式自检”:扫本机、扫局域网里的第二台机器、用 Wireshark 抓一次包确认 SYN 和 SYN-ACK 真的在线上出现。抓包这一步如果时间允许一定要做,它能直接证明代码行为符合三次握手理论,比口头解释有说服力得多。整套验证跑下来,你对自己交付的不是“网上抄的代码”这件事,会非常有底气。希望这些经验和参考代码能帮你把端口扫描器这个题目做成一份能讲清楚、经得起追问的课程设计。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询