PING
本篇文章,我们将详细介绍如何在W5500芯片上面实现IPRAW功能,并通过实战例程,为大家讲解如何使用IPRAW模式实现ICMP协议中的PING命令进行网络连通性测试。
该例程用到的其他网络协议,例如DHCP,请参考相关章节。有关 W5500 的初始化过程,请参考 Network install章节,这里将不再赘述。
IPRAW模式简介
IPRAW 模式是W5500提供的一种网络通信模式。在这种模式下,用户可以直接操作IP层数据包,对其进行底层细节的处理,从而支持IP层协议的实现,例如ICMP、IGMP等。
PING简介
PING是一个用于测试网络连接性和诊断网络问题的命令。它通过使用ICMP即因特网控制报文协议(Internet Control Message Protocol)发送“回显请求”消息(Echo Request)并等待“回显应答”消息(Echo Reply),来检查目标主机是否可达以及网络的响应时间。PING命令是网络诊断工具中最常见的工具之一,通常用于验证网络连通性、检测网络延迟、排查网络故障等。
PING命令特点
- 简单性:PING设计非常简单,通常是基于请求-响应的模式。一个设备发送 PING请求包,另一个设备回应PING响应包。它的开销较小,适合嵌入式设备的网络通信需求。
- 低延迟:由于PING本身非常简洁,因此网络延迟很低,适用于需要实时检测设备状态或维持心跳的场景。
- 状态监测与心跳:PING 常用于设备间的心跳检测。
- 无负载数据:PING传输的数据通常没有负载或附加的数据,只有简单的请求和响应字段,因此网络负载非常小。
- 容错性与重试机制:一些实现可能会有超时和重试机制,以确保 PING响应的可靠性。
PING应用场景
接下来,我们了解下在W5500上,可以使用PING完成哪些操作及应用呢?
- 网络连通性测试:可以用于测试本地设备与目标设备之间的网络是否连通。
- 网络故障排查:当网络出现问题,如无法访问某个网站或无法与特定设备进行通信时,可以使用 PING 命令来逐步定位问题所在。如果对目标设备的 PING 请求超时或丢包严重,说明可能存在网络连接中断、路由器配置错误、防火墙阻挡等问题。
- 网络设备状态监测:在网络管理中,需要实时监测网络中各种设备(如服务器、路由器、交换机等)的状态。可以定期使用 PING 命令对这些设备进行检测,根据是否能够收到回复来判断设备是否正常运行。
PING命令基本工作流程
1.发送请求
用户在命令行输入 PING 命令及目标主机的 IP 地址或域名后,系统开始构建 ICMP 回显请求数据包。此数据包包含 ICMP 协议头部和一定数据,默认 32 字节。随后,该数据包被交给 IP 层。
IP 层在数据包中添加源 IP 地址和目标 IP 地址等控制信息,组装成完整的 IP 数据包。接着,需获取目标主机的 MAC 地址。若目标主机与源主机在同一网段,IP 层会查询本地 ARP 缓存表,若有对应映射则直接获取 MAC 地址;若没有,则发送 ARP 请求广播来获取。若目标主机与源主机不在同一网段,IP 层会将数据包交给路由处理,由路由器依据路由表转发,路由器同样需获取下一跳的 MAC 地址。
数据链路层获取目标 MAC 地址后,构建数据帧,将 IP 数据包封装其中,附上源和目的 MAC 地址及控制信息,再将数据帧发送出去。
2.接收响应
目标主机接收到数据帧后,检查目的 MAC 地址,若相符则接收并提取 IP 数据包,交给 IP 层。IP 层检查无误后,将其交给 ICMP 协议。
ICMP 协议构建 ICMP 回显应答数据包,把请求包中的数据复制过来,再交给 IP 层。IP 层封装成 IP 数据包,数据链路层构建新的数据帧,以源主机为目的地址发送出去。
3.结果处理
源主机收到应答数据包后,数据链路层和 IP 层依次处理,将 ICMP 应答包交给 ICMP 协议。ICMP 协议记录当前时间,结合请求时的时间戳计算往返时间。若发送多个请求包,系统会统计未收到应答的数据包数量,计算丢包率。通过往返时间和丢包率,用户可判断网络的连通性和质量。
报文解析
ICMP(Internet Control Message Protocol,互联网控制消息协议)是用于网络设备间传递控制消息的协议,通常用于网络诊断与错误报告。ICMP 报文由类型字段、代码字段、校验和及数据部分构成。常见的 ICMP 报文类型包括回显请求(ping)和回显应答、目的不可达、超时等。
以下是 ICMP 回显请求和应答报文的基本格式:

| 报文原文 |
08 00 e1 a7 12 35 43 22
| 报文解析 |
Internet Control Message Protocol
Type: 8 (Echo (ping) request) (ICMP 类型字段为8,表示这是一个 回显请求(Ping请求))
Code: 0 (ICMP 代码字段为0,表示回显请求的正常类型)
Checksum: 0xe1a7 [correct] (0xe1a7 是校验和值,状态为 Good,表示报文有效)
[Checksum Status: Good]
Identifier (BE): 4661 (0x1235) (表示标识符以大端格式存储,值为 4661(十进制))
Identifier (LE): 13586 (0x3512) (LE表示标识符以小端格式存储,值为 13586)
Sequence Number (BE): 17186 (0x4322) (序列号字段,用于标识回显请求的序列)
Sequence Number (LE): 8771 (0x2243) (序列号帮助配对请求与响应)
[Response frame: 15] (此字段表示接收到的响应帧数量,通常用于指示响应的顺序或超时)
Data (128 bytes) (包括用于测试的实际数据或填充数据)
实现过程
接下来,我们在W5500上实现PING命令。
do_ping()函数起到了控制PING操作流程的作用,它决定了是否继续进行PING操作,以及在操作完成后关闭Socket连接。
这个函数需要主循环中调用,如下图所示:
while (1)
{
do_ping(SOCKET_ID, dest_ip, ping_num);
}
do_ping()函数如下:
void do_ping(uint8_t sn, uint8_t *remote_ip, uint8_t req_num)
{
if (req >= req_num)
{
close(sn);
return;
}
else
{
ping_count(sn, req_num, remote_ip);
}
}
do_ping()函数需要传入3个参数,分别是spcket号,目标主机IP,PING请求次数。
如果req大于req_num,说明已经达到了指定的 PING 请求次数,此时调用close()函数关闭指定的 Socket 连接,然后函数返回,结束本次 PING 操作。
如果req小于req_num,则调用ping_count()函数。PING操作主要在ping_count()函数内进行,进入该函数之后会执行以下步骤。
ping_count()函数如下:
void ping_count(uint8_t s, uint16_t pCount, uint8_t *addr)
{
uint16_t rlen, cnt, i;
cnt = 0;
for (i = 0; i < pCount + 1; i++)
{
if (i != 0)
{
// Output count number
printf("No.%d ", i);
}
switch (getSn_SR(s))
{
case SOCK_CLOSED:
close(s);
// Create Socket
IINCHIP_WRITE(Sn_PROTO(s), IPPROTO_ICMP);
if (socket(s, Sn_MR_IPRAW, 3000, 0) != 0)
{
}
// Check socket register
while (getSn_SR(s) != SOCK_IPRAW)
;
break;
case SOCK_IPRAW:
ping_request(s, addr);
req++;
while (1)
{
if ((rlen = getSn_RX_RSR(s)) > 0)
{
ping_reply(s, addr, rlen);
rep++;
if (ping_reply_received)
break;
}
if ((cnt > 1000))
{
printf("Request Time out\r\n\r\n");
cnt = 0;
break;
}
else
{
cnt++;
delay_ms(5);
}
}
break;
default:
break;
}
if (req >= pCount)
{
printf("Ping Request = %d, Ping Reply = %d, Lost = %d\r\n", req, rep, req - rep);
}
}
}
进入该函数后,程序会执行一个状态机,首先初始化 cnt 为 0 并开启 for 循环。根据Socket的状态进行不同操作,当Socket为 SOCK_CLOSED 时,Socket关闭,设置协议,创建 Sn_MR_IPRAW 模式的 Socket,并等待 Socket 状态变为 SOCK_IPRAW;当Socket为 SOCK_IPRAW 时,发送 ping 请求,进入内层 while 循环,若接收长度大于 0 则处理 ping 回复,超过一定时间无回复则输出超时信息,同时会根据 cnt 进行计数和延迟处理;默认情况不做处理,满足条件时输出 ping 请求、回复和丢失的统计信息。
步骤1:发送 PING 请求
ping_request()函数在ping_count()函数中,当Socket状态为SOCK_IPRAW时被调用,从而实现按照设定的次数发送 PING 请求。
ping_request()函数如下:
void ping_request(uint8_t s, uint8_t *addr)
{
uint16_t i;
ping_reply_received = 0;
PingRequest.Type = PING_REQUEST;
PingRequest.Code = CODE_ZERO;
PingRequest.ID = htons(RandomID++);
PingRequest.SeqNum = htons(RandomSeqNum++);
for (i = 0; i < BUF_LEN; i++)
{
PingRequest.Data[i] = (i) % 8;
}
PingRequest.CheckSum = 0;
PingRequest.CheckSum = htons(checksum((uint8_t *)&PingRequest, sizeof(PingRequest)));
if (sendto(s, (uint8_t *)&PingRequest, sizeof(PingRequest), addr, 3000) == 0)
{
printf("Fail to send ping-reply packet\r\n");
}
else
{
printf("Ping:%d.%d.%d.%d\r\n", (addr[0]), (addr[1]), (addr[2]), (addr[3]));
}
}
该函数ping_request的主要作用是发送一个Ping请求。首先初始化一些请求参数,包括请求类型、代码、随机生成的 ID 和序列号,填充数据并计算校验和。接着尝试使用sendto()函数发送请求,根据发送结果输出相应信息,包括失败提示和请求目标的地址信息。
步骤2:接收并解析 PING 回复:
ping_reply()函数在ping_count()函数中,当检测到Socket收缓冲区有数据(rlen>0)时被调用,从而实现对接收到的 PING 回复进行解析处理。ping_reply()函数负责接收和解析PING回复数据包,根据不同的数据包类型进行相应的处理,并在解析成功后打印相关信息。
ping_reply()函数如下:
void ping_reply(uint8_t s, uint8_t *addr, uint16_t rlen)
{
uint16_t tmp_checksum;
uint16_t len;
uint16_t i;
uint8_t data_buf[136];
uint16_t port = 3000;
PINGMSGR PingReply;
len = recvfrom(s, data_buf, rlen, addr, &port);
if (data_buf[0] == PING_REPLY)
{
PingReply.Type = data_buf[0];
PingReply.Code = data_buf[1];
PingReply.CheckSum = (data_buf[3] << 8) + data_buf[2];
PingReply.ID = (data_buf[5] << 8) + data_buf[4];
PingReply.SeqNum = (data_buf[7] << 8) + data_buf[6];
for (i = 0; i < len - 8; i++)
{
PingReply.Data[i] = data_buf[8 + i];
}
tmp_checksum = ~checksum(data_buf, len);
if (tmp_checksum != 0xffff)
printf("tmp_checksum = %x\r\n", tmp_checksum);
else
{
printf("Reply from %3d.%3d.%3d.%3d ID=%x Byte=%d\r\n\r\n", (addr[0]), (addr[1]), (addr[2]), (addr[3]), htons(PingReply.ID), (rlen + 6));
ping_reply_received = 1;
}
}
else if (data_buf[0] == PING_REQUEST)
{
PingReply.Code = data_buf[1];
PingReply.Type = data_buf[2];
PingReply.CheckSum = (data_buf[3] << 8) + data_buf[2];
PingReply.ID = (data_buf[5] << 8) + data_buf[4];
PingReply.SeqNum = (data_buf[7] << 8) + data_buf[6];
for (i = 0; i < len - 8; i++)
{
PingReply.Data[i] = data_buf[8 + i];
}
tmp_checksum = PingReply.CheckSum;
PingReply.CheckSum = 0;
if (tmp_checksum != PingReply.CheckSum)
{
printf(" \n CheckSum is in correct %x shold be %x \n", (tmp_checksum), htons(PingReply.CheckSum));
}
else
{
}
printf(" Request from %d.%d.%d.%d ID:%x SeqNum:%x :data size %d bytes\r\n",
(addr[0]), (addr[1]), (addr[2]), (addr[3]), (PingReply.ID), (PingReply.SeqNum), (rlen + 6));
ping_reply_received = 1;
}
else
{
printf(" Unkonwn msg. \n");
}
}
ping_reply()函数主要用于处理接收的信息。它通过 recvfrom 接收数据,根据数据包头信息判断是回复还是请求,提取并解析相关信息,计算校验和并进行检查,根据不同情况输出信息,如错误提示、回复来源信息或请求来源信息等,并设置接收状态标志。
运行结果
请注意:
测试实例需要PC端和W5500处于同一网段
烧录例程运行后,首先进行了PHY链路检测,然后打印设置网络信息,最ping目标IP收到回复,如下图所示:

总结
本文讲解了如何在 W5500 芯片上通过 IPRAW 模式实现 ICMP 协议中的 PING 命令,以进行网络连通性测试,通过实战例程展示了从发送 PING 请求、接收并解析回复到统计结果的完整过程。文章详细介绍了 IPRAW 模式和 PING 命令的概念、特点、应用场景、基本工作流程和报文解析,帮助读者理解其在网络测试和故障排查中的实际应用价值。
下一篇文章将使用W5500的MACRAW模式实现ARP协议,敬请期待!