作者:咸鱼FPGA 文章来源:FPGA开源工作室
心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。心跳包在GPRS通信和CDMA通信的应用方面使用非常广泛。数据网关会定时清理没有数据的路由,心跳包通常设定在30-40秒之间。所谓的心跳包就是客户端定时发送简单的信息给服务器端告诉它我还在而已。代码就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息如果服务端几分钟内没有收到客户端信息则视客户端断开。本次设计中,心跳包时间间隔为1秒。
一、心跳包触发器
设计一个 1 秒定时器,每隔一秒就产生一个心跳包触发脉冲,用于下一步心跳包的组建。
二、心跳包粗略框架
本次以太网的心跳包结构如下所示:
本次发送 64 个全为0的数据,当然这个数据是自定义的,因此心跳包总长度为118。此外目的/源 MAC 地址、目的/源 IP 地址、目的/源 port 等值可以用参数的方式先写好。利用刚刚设计的心跳包触发脉冲,我们就能组建这个心跳包了。表中黄色部分为后面需要校验的值,可以先填0进去,后面再覆盖掉这三处即可。代码也没什么说的,用参数和case语句即可。
点击电脑 Win + R 键,输入 cmd,再出入 ipconfig -all,按回车键,即可看到自己电脑的 MAC 地址等参数。
三、心跳包填充:IP校验和UDP校验
1、IP校验方法
IP 校验就是把 IP 首部 20byte 按 2byte(即 16bit)分开后相加,结果如果大于 16’hffff,就将超出 16’hffff 的部分与相加结果的低 16 位相加,直到最终结果小于 16’hffff 为止。最后把小于 16’hffff 的结果取反作为 ip_checksum。高字节在前,低字节在后,替换掉前面在心跳包中填充的 0。
(1) 校验和字段清0
假设有一段以太网包前面没有对 IP 校验和字段清0,而是赋了别的值,例如 IP 首部为:45 00 00 30 80 4c 40 00 80 06 b5 2e d3 43 11 7b cb 51 15 3d,b5 2e 字段即为 IP 校验和字段,清0后数据就变成了:45 00 00 30 80 4c 40 00 80 06 00 00 d3 43 11 7b cb 51 15 3d。
(2) 反码求和
4500+0030+804c+4000+8006+0000+d343+117b+cb51+153d = 34ace,将超过 16’hffff 的部分(即3)与低 16 位(即4ace)上,结果为:3+4ace=4ad1,因为 4ad1 小于 ffff,故作为 Ip_checksum 的反码。
(3) 取反得最终结果
将 4ad1 取反得 b52e,这就是本包数据最终的 IP 校验和,再将 b52e 填充到对应位置即可。
2、UDP校验方法
(1) 校验方法
UDP_checksum 计算稍微复杂一点,需要加入 IP 伪头部,将 IP 伪头部、UDP 首部的 8 个 bytes 和数据包部分按 2byte(即 16bit) 分开后相加,结果如果大于16’hffff,就将超出 16’hffff 的部分与相加结果的低 16 位相加,直到最终结果小于 16’hffff 为止。最后把小于 16’hffff 的结果取反作为 UDP_checksum。高字节在前,低字节在后,替换掉前面在心跳包中填充的0。
(2) UDP结构
UDP_checksum 的组成如图所示。
可以看到,IP 伪头部包含了 IP 源地址,IP 目的地址,一个字节的 0,协议号和 UDP_len ,在前面做的千兆以太网图像传输项目中 IP 源地址,IP 目的地址,协议号都是固定的,而通过上一篇博客设计的 UDP_len 为:UDP首部 8byte + 数据长度 64byte = 8+64='h0048。在设计的时候可以先单独将 IP 伪头部计算出来。
3、计算的时序安排
ip_checksum 和 udp_checksum 计算完成,该数据填充的位置已经经过,那么就没办法将数据填充到原来填充 0 的位置了,但我们想要将其组成完整的以太网包,这一步是不可避免的,那么我们该怎么解决呢?我们可以考虑一下,建立一个足够大的 RAM ,在计算 ip_checksum 和 udp_checksum 的同时将前面心跳包数据存储到 RAM 中,当这一包数据全部计算完再将 RAM 中的数据读出。当读出到要填充 ip_checksum 和 udp_checksum 的位置时,将计算出的两个值取反后填充到对应位置即可。
4、时序图
四、心跳包填充:CRC校验
1、CRC校验范围
CRC校验另起一段,是因为要先算完前面的 ip 校验值和 UDP 校验值才行。CRC校验时必须先去除帧头(即前面的7个55和1个d5),还得去掉帧尾(即后面4个数据,前面我们直接在这填了0)。当 CRC 校验值计算完成后,即可将其填充至数据的末尾 4 位即可。
2、CRC校验方法
(1)CRC校验初始值和空闲值都设置为 32‘hffffffff,即全 1 状态。
(2)计算结果需高低位对调,因为我们计算时是先从高位输入的,所以最后高低位需要对调。
(3)最后结果取反,高低位对调后的结果再取反,即可得到 CRC 的校验值。
3、CRC校验部分代码
always @(posedge sclk) begin
if(rst) begin
crc32_value <= 32'hFFFFFFFF;
end
else if(crc_en) begin
crc32_value[ 0] <= c[24]^c[30]^d[ 1]^d[ 7];
crc32_value[ 1] <= c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7];
crc32_value[ 2] <= c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7];
crc32_value[ 3] <= c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6];
crc32_value[ 4] <= c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^c[30]^d[ 1]^d[ 7];
crc32_value[ 5] <= c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^c[31]^d[ 0]^d[ 6]^c[24]^c[30]^d[1]^d[7];
crc32_value[ 6] <= c[30]^d[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6];
crc32_value[ 7] <= c[31]^d[ 0]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7];
crc32_value[ 8] <= c[ 0]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7];
crc32_value[ 9] <= c[ 1]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6];
crc32_value[10] <= c[ 2]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[24]^d[ 7];
crc32_value[11] <= c[ 3]^c[28]^d[ 3]^c[27]^d[ 4]^c[25]^d[ 6]^c[24]^d[ 7];
crc32_value[12] <= c[ 4]^c[29]^d[ 2]^c[28]^d[ 3]^c[26]^d[ 5]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7];
crc32_value[13] <= c[ 5]^c[30]^d[ 1]^c[29]^d[ 2]^c[27]^d[ 4]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6];
crc32_value[14] <= c[ 6]^c[31]^d[ 0]^c[30]^d[ 1]^c[28]^d[ 3]^c[27]^d[ 4]^c[26]^d[5];
crc32_value[15] <= c[ 7]^c[31]^d[ 0]^c[29]^d[ 2]^c[28]^d[ 3]^c[27]^d[ 4];
crc32_value[16] <= c[ 8]^c[29]^d[ 2]^c[28]^d[ 3]^c[24]^d[ 7];
crc32_value[17] <= c[ 9]^c[30]^d[ 1]^c[29]^d[ 2]^c[25]^d[ 6];
crc32_value[18] <= c[10]^c[31]^d[ 0]^c[30]^d[ 1]^c[26]^d[ 5];
crc32_value[19] <= c[11]^c[31]^d[ 0]^c[27]^d[ 4];
crc32_value[20] <= c[12]^c[28]^d[ 3];
crc32_value[21] <= c[13]^c[29]^d[ 2];
crc32_value[22] <= c[14]^c[24]^d[ 7];
crc32_value[23] <= c[15]^c[25]^d[ 6]^c[24]^c[30]^d[ 1]^d[ 7];
crc32_value[24] <= c[16]^c[26]^d[ 5]^c[25]^c[31]^d[ 0]^d[ 6];
crc32_value[25] <= c[17]^c[27]^d[ 4]^c[26]^d[ 5];
crc32_value[26] <= c[18]^c[28]^d[ 3]^c[27]^d[ 4]^c[24]^c[30]^d[ 1]^d[ 7];
crc32_value[27] <= c[19]^c[29]^d[ 2]^c[28]^d[ 3]^c[25]^c[31]^d[ 0]^d[ 6];
crc32_value[28] <= c[20]^c[30]^d[ 1]^c[29]^d[ 2]^c[26]^d[ 5];
crc32_value[29] <= c[21]^c[31]^d[ 0]^c[30]^d[ 1]^c[27]^d[ 4];
crc32_value[30] <= c[22]^c[31]^d[ 0]^c[28]^d[ 3];
crc32_value[31] <= c[23]^c[29]^d[ 2];
end
end
4、时序图
由于这次 CRC 校验值是在末尾,因此不需要用到 RAM 也来得及填充。
至此,我们组建了以太网发送的心跳包,下一步就可以发送了。
参考资料:威三学院FPGA教程