简介:这份计算机网络课程设计报告围绕PING程序的设计与实现展开,面向高校计算机、网络工程等专业的学生及课程设计指导教师,帮助解决网络编程入门阶段对ICMP协议理解不深、Java网络包调用不熟等实际问题。资源包内共1个doc文档,压缩包约1.88MB,内容涵盖问题描述、概要设计、详细设计、调试分析、心得体会与用户手册等完整章节,并附有pingRang、pingAddress等关键算法代码与系统流程图。读者可从中获取基于Java与Eclipse环境模拟Windows ping命令的完整实现思路,理解InetAddress类的isReachable方法、响应时间计算及异常处理机制,同时参考窗体界面绘制与调试排错经验。目前已有512人学习下载,适合作为课程设计范文、网络编程练习及答辩准备素材。
1. 从一份课程设计报告说起:PING 程序到底在测什么
很多人第一次接触网络编程,都是从“写一个 PING 程序”开始的。标题里的 PING 程序设计与实现,表面看是做一个命令行小工具,实际上它逼着你把 ICMP 协议、原始套接字、报文封装、超时重传这几件事一次性串起来。你平时敲ping baidu.com觉得理所当然,但真让你自己构造一个 ICMP 回显请求发出去、再把回显应答收回来算 RTT,才会发现里面全是细节:校验和怎么算、标识符和序列号怎么填、TTL 怎么读、超时怎么判。这篇笔记面向正在做课程设计的学生和想补网络底层的 Java 开发者,用 Java + Eclipse 把 PING 从协议到代码跑通,顺带把 ICMP 抓包验证和常见翻车点讲清楚。热词里那些centos7 无法ping通百度、ping出现一般故障、icmp协议、科来icmp抓包,本质都是同一套东西在不同环境下的表现,理解了实现,排错就有据可依。
2. ICMP 与原始套接字:PING 能跑起来的两个前提
2.1 ICMP 回显报文长什么样
PING 不依赖 TCP 或 UDP,它直接用 ICMP 协议。ICMP 报文被封装在 IP 包里,IP 头协议号字段填 1 就表示承载的是 ICMP。一个回显请求(Echo Request)的 ICMP 部分结构是固定的:类型(Type)为 8,代码(Code)为 0,然后是 2 字节校验和,接着 2 字节标识符(Identifier)、2 字节序列号(Sequence Number),最后是可变长度的数据区。回显应答(Echo Reply)类型是 0,其余字段原样带回。
| 字段 | 长度 | 回显请求取值 | 回显应答取值 |
|---|---|---|---|
| 类型 Type | 1 字节 | 8 | 0 |
| 代码 Code | 1 字节 | 0 | 0 |
| 校验和 Checksum | 2 字节 | 计算得出 | 计算得出 |
| 标识符 Identifier | 2 字节 | 进程自定义 | 原样返回 |
| 序列号 Sequence | 2 字节 | 递增 | 原样返回 |
| 数据 Data | 可变 | 任意填充 | 原样返回 |
标识符的作用是区分同一台机器上不同的 PING 进程,序列号用来匹配请求和应答。数据区常见做法是填时间戳,收到应答后用当前时间减去它,就得到 RTT。校验和覆盖整个 ICMP 报文,计算时先把校验和字段置 0,按 16 位累加再取反。
2.2 Java 里为什么必须用原始套接字
普通Socket只能操作 TCP/UDP,发不了 ICMP。Java 提供DatagramSocket走 UDP,但 ICMP 不属于 UDP。要自己构造 ICMP 报文,得用原始套接字。Java 标准库没有直接暴露 raw socket,常见做法是通过InetAddress.isReachable()间接调用系统 PING,但那只是黑匣子,拿不到 RTT 细节,也控制不了报文内容。真正自己实现,一般用两种路径:一是用 JNI 调系统 socket,二是用第三方库如 jpcap 或 pcap4j 直接抓包发包。课程设计里更常见的是用InetAddress配合isReachable做简化版,或者用ProcessBuilder调系统 ping 命令再解析输出。但标题既然叫“PING 程序设计与实现”,我建议至少把 ICMP 报文的手工构造讲清楚,哪怕发包用系统命令,收包用抓包库,理解链路才算完整。
提示:如果实验环境是 Windows,原始套接字需要管理员权限;Linux 下需要 root 或给 Java 进程加
CAP_NET_RAW能力。Eclipse 里直接 Run 会权限不足,这是第一个翻车点。
2.3 用 InetAddress 做最小可达性探测
先跑通一个不依赖管理员权限的最小版本,用InetAddress.isReachable(timeout)判断目标是否可达。它底层会尝试 ICMP,失败时回退到 TCP 7 端口,所以结果不完全等于 PING,但足够做课程设计的入门演示。
import java.net.InetAddress; public class SimplePing { public static void main(String[] args) throws Exception { String host = "www.baidu.com"; InetAddress addr = InetAddress.getByName(host); // timeout 单位毫秒,超过则判定不可达 boolean reachable = addr.isReachable(3000); System.out.println(host + " -> " + addr.getHostAddress() + " reachable=" + reachable); } }getByName会触发 DNS 解析,如果报temporary failure in name resolution,说明 DNS 配置有问题,和 PING 程序本身无关。isReachable的 timeout 是最长等待时间,设太小会误判,设太大界面卡顿。这个版本拿不到 RTT,只能回答“通不通”,适合先验证网络和 DNS 是否正常,再进入手工构造报文的阶段。
3. 手工构造 ICMP 报文:校验和、序列号与超时重传
3.1 校验和算法的 Java 实现
ICMP 校验和是 16 位反码求和。把报文按 16 位分组,逐组相加,溢出位回卷,最后取反。Java 里没有无符号类型,要用int暂存并手动处理高位。
public static short checksum(byte[] data) { int sum = 0; int i = 0; // 每两个字节组成一个 16 位字 while (i + 1 < data.length) { sum += ((data[i] & 0xff) << 8) | (data[i + 1] & 0xff); i += 2; } // 奇数长度补一个 0 字节 if (i < data.length) { sum += (data[i] & 0xff) << 8; } // 溢出位回卷 while ((sum >> 16) != 0) { sum = (sum & 0xffff) + (sum >> 16); } return (short) ~sum; }& 0xff是为了把有符号 byte 转成 0~255 的无符号值,漏掉它会出现负数导致校验和错误。回卷循环处理进位,最后取反得到校验和。构造报文时先把校验和字段填 0,算完再写回去。这个函数是后面所有发包逻辑的基础,建议单独写个单元测试,用已知报文验证。
3.2 组装 Echo Request 并发送
下面用DatagramSocket发一个 UDP 包到目标,虽然协议不对,但可以用来演示报文组装和序列号管理。真正发 ICMP 需要 raw socket,这里把组装逻辑独立出来,方便替换底层发送方式。
import java.net.*; import java.nio.ByteBuffer; public class IcmpPacketBuilder { public static byte[] buildEchoRequest(int id, int seq, byte[] payload) { int len = 8 + payload.length; ByteBuffer buf = ByteBuffer.allocate(len); buf.put((byte) 8); // Type: Echo Request buf.put((byte) 0); // Code buf.putShort((short) 0); // Checksum 占位 buf.putShort((short) id); // Identifier buf.putShort((short) seq); // Sequence buf.put(payload); byte[] packet = buf.array(); short cs = checksum(packet); packet[2] = (byte) (cs >> 8); packet[3] = (byte) (cs & 0xff); return packet; } public static void main(String[] args) throws Exception { byte[] payload = "hello-ping".getBytes(); byte[] packet = buildEchoRequest(0x1234, 1, payload); DatagramSocket socket = new DatagramSocket(); InetAddress target = InetAddress.getByName("127.0.0.1"); // 这里仅演示发送,实际 ICMP 需 raw socket socket.send(new DatagramPacket(packet, packet.length, target, 7)); socket.close(); } }id建议用进程号或随机数,避免和系统 ping 冲突。seq从 1 开始递增,每发一个加一。payload 里可以放发送时间戳,收到应答后算 RTT。校验和必须在 payload 填完之后算,顺序不能反。这段代码在本地回环能跑通发送,但收不到 ICMP 应答,因为目标端口 7 没有对应服务,这是预期行为。
3.3 超时重传与 RTT 统计
PING 的可靠性靠超时重传。每发一个包启动一个定时器,超时没收到应答就重发,重发次数一般 3 次。RTT 统计要记录最小值、最大值、平均值和丢包率。
public class PingStats { private long min = Long.MAX_VALUE; private long max = 0; private long total = 0; private int sent = 0; private int received = 0; public void onSend() { sent++; } public void onReceive(long rtt) { received++; total += rtt; min = Math.min(min, rtt); max = Math.max(max, rtt); } public void print() { double loss = sent == 0 ? 0 : (sent - received) * 100.0 / sent; System.out.printf("sent=%d received=%d loss=%.1f%%%n", sent, received, loss); if (received > 0) { System.out.printf("rtt min/avg/max = %d/%d/%d ms%n", min, total / received, max); } } }min初始化为Long.MAX_VALUE保证第一个值能更新。丢包率用发送数减接收数除以发送数。RTT 单位统一用毫秒,System.nanoTime()差值除以 1_000_000 得到。重传时序列号不变,这样应答回来能匹配到同一次请求,但统计上要区分首次发送和重传,否则丢包率算不准。
4. 在 Eclipse 里跑通并抓包验证:从代码到现象
4.1 Eclipse 工程结构与运行配置
新建 Java 工程,包名用com.ping.demo,把上面的类分别放进去。Eclipse 2021-09 之后的版本对模块化支持更严,如果报找不到或无法加载主类,检查 Build Path 里的 JRE 是否匹配,以及输出目录bin是否生成 class 文件。运行配置里给 VM arguments 加-Djava.net.preferIPv4Stack=true,避免 IPv6 优先导致解析到不可达地址。
| 配置项 | 建议值 | 说明 |
|---|---|---|
| JRE | 与本机一致 | 避免编译版本不匹配 |
| VM args | -Djava.net.preferIPv4Stack=true | 强制 IPv4 |
| 工作目录 | 工程根目录 | 相对路径读写正常 |
| 控制台编码 | UTF-8 | 避免中文乱码 |
如果要做原始套接字,Windows 下用管理员身份启动 Eclipse,Linux 下用sudo或设置 capabilities。Eclipse 本身不提供 raw socket 权限,这是操作系统层面的限制。
4.2 用 Wireshark 过滤 ICMP 看报文
在 PC 网卡上开启 Wireshark 捕获,过滤条件填icmp,然后运行你的 PING 程序。点开捕获到的数据包,展开 Internet Control Message Protocol 部分,对照你代码里填的 Type、Code、Identifier、Sequence 是否一致。如果 Type 是 8 而收不到 Type 0 的应答,说明目标没回或者被防火墙拦了。科来网络分析系统也能做同样的事,过滤 ICMP 后看会话交互。
注意:Wireshark 抓包需要安装 Npcap 或 WinPcap 驱动,安装时勾选“支持原始套接字”。抓不到包先检查网卡选择是否正确,虚拟网卡和物理网卡要分清。
4.3 常见现象与对应排查
ping 内网显示一般故障通常是目标主机防火墙禁 ICMP,或者网关做了限制。centos7 无法ping通百度先看 DNS:cat /etc/resolv.conf有没有配 nameserver,再ping 8.8.8.8看纯 IP 通不通,通说明 DNS 问题,不通说明路由或网卡问题。ping: baidu.com: temporary failure in name resolution直接指向 DNS 解析失败,和 PING 程序实现无关。h3c 怎么指定交换机接口 ping属于网络设备命令,源接口用ping -a指定。这些现象用抓包一看就清楚,比猜快得多。
5. 避坑与排查:PING 程序实现里最容易翻车的五件事
5.1 校验和算错导致应答被丢弃
现象:程序发出报文,Wireshark 能看到 Type 8,但目标不回 Type 0,或者回了但你的程序收不到。原因:校验和计算时漏了& 0xff,或者先算了校验和再改 payload。解决:把校验和函数单独测试,用已知正确的报文比对;确保 payload 填完再算校验和,算完不要再改任何字段。
5.2 权限不足导致原始套接字创建失败
现象:SocketException: Permission denied或Operation not permitted。原因:Windows 非管理员、Linux 非 root 无法创建 raw socket。解决:Windows 用管理员启动 Eclipse;Linux 用sudo运行,或setcap cap_net_raw+ep $(readlink -f $(which java))给 Java 加能力。课程设计环境一般允许管理员权限,生产环境慎用。
5.3 标识符冲突导致应答匹配错乱
现象:同时运行系统 ping 和你的程序,收到的应答序列号对不上。原因:ICMP 标识符用固定值,和系统 ping 撞了。解决:标识符用ProcessHandle.current().pid()或随机数,保证唯一。收到应答后先校验标识符和序列号,不匹配就丢弃。
5.4 超时设置过短误判丢包
现象:本地网络正常,但程序报丢包率高。原因:timeout 设成 100ms,而实际 RTT 波动到 150ms。解决:首次探测 timeout 设 1000ms 以上,后续根据 RTT 动态调整。重传次数 3 次,总等待时间不超过 5 秒,避免界面卡死。
5.5 DNS 解析阻塞主线程
现象:程序一启动就卡住,界面无响应。原因:InetAddress.getByName是阻塞调用,DNS 慢时卡住。解决:把解析放到独立线程,或者先用 IP 地址测试。Eclipse 控制台里如果看到temporary failure in name resolution,先检查本机 DNS 配置,再检查代码。
6. 进阶技巧:把 PING 程序做成可复用的网络诊断组件
课程设计交完报告不是终点。把 PING 的核心逻辑抽成一个工具类,后面做网络监控、服务健康检查都能直接用。我一般会暴露三个方法:pingOnce(host, timeout)返回单次 RTT 或 -1,pingContinuous(host, count, interval)返回统计对象,isReachable(host)做快速布尔判断。参数上,count 默认 4 次,interval 默认 1000ms,timeout 默认 2000ms,这些和系统 ping 的默认行为对齐,用户迁移成本低。
验证方法上,除了 Wireshark 抓包,还可以用ping -c 4对比你的程序输出。RTT 数值不会完全一样,但丢包率和可达性判断应该一致。如果差异大,先查校验和,再查超时逻辑。另一个技巧是把每次探测的原始报文和 RTT 写日志,出问题时翻日志比重新抓包快。
| 方法 | 返回 | 适用场景 |
|---|---|---|
| pingOnce | long RTT 或 -1 | 单次探测 |
| pingContinuous | PingStats | 批量统计 |
| isReachable | boolean | 快速判断 |
我自己的习惯是,任何网络程序上线前先用这个工具类跑一遍目标地址,确认链路层没问题再排查应用层。血泪经验是,很多“服务不可用”最后查下来是 ICMP 被禁导致健康检查误判,换成 TCP 探测就好了。希望帮到你。
本文还有配套的精品资源,点击获取