文章背景:

  两端自定义网络协议时,需要对传输的数据包格式进行约定,比如TLV格式,约定数据包头部固定几个字节传的是包头,包头的内容也需要约定。

  这里讲一种包头中表示数据包大小的方式,用golang语言,语言不是问题,大意理解就行。

 

方案设计:

比如首先约定包最大值为60KB,

60KB = 60 * 1024B = 61440B,数据包都是字节数组,所以数据包的最大长度是61440个字节(B=Byte),uint16的最大值是65535,大于数据包的最大长度值,用uint16即可表示,uint16是16位二进制数,2个字节。也就是说,我们将用两个字节来表示数据包的大小。

 

具体实现方案:

我们采用位运算,将数据包的大小值,拆分为两个字节进行存储,一个高字节,一个低字节,

header := make([]byte, 2)
header[0] = byte((dataSize >> 8) & 0xff) 
header[1] = byte(dataSize & 0xff)   

在这段代码中,使用 (dataSz >> 8) & 0xff 将数据包大小的高字节提取出来。

原因是,数据包大小的高字节存储在整数的第二个字节(从右往左数第二个字节)。通过将数据包大小右移 8 位,将第二个字节移动到最低有效位的位置。然后,使用按位与操作符 & 和位掩码 0xff 提取字节。

在这里,右移 8 位(即 dataSz >> 8)是基于数据包大小占用了两个字节(约定数据包长度最大值为60 * 1024 = 61440)。通过右移 8 位,我们将第二个字节移动到最低有效位的位置,以便提取。

可以将这个过程与将整数拆分为多个字节的过程进行类比。在那个过程中,我们使用右移操作符和按位与操作符将整数的各个字节提取出来。而在这个特定的代码段中,我们只需要提取第二个字节,因此使用右移 8 位即可。

综上所述,为了提取数据包大小的高字节,我们使用 (dataSz >> 8) & 0xff 中的右移 8 位操作。这样可以将第二个字节移动到最低有效位的位置,并使用按位与操作符提取字节。

 

0xff是16进制数,转成二进制是11111111,8个1,这个数是很特殊的值,是二进制中单个字节能表示的最大值,也就是255,再通过“&”与运算,二者都为1的情况运算结果才为1,可以使原值的低8位保留原来的值,也就达到了我们的目的,获取原值的低字节(后8位),因为总共就2个字节(16位),所以右移>>8之后,高位的8个字节变成了低位。

 

二次修改补充:

byte(dataSize & 0xff) 实际上 等同于 byte(dataSize),

当将 uint16 类型的数字使用 byte() 方法进行强制类型转换时,会按照以下过程进行转换:

  1. 首先,将 uint16 类型的数字表示为二进制形式,占用 16 位。
  2. 然后,将这个 16 位的二进制数截断为 8 位,只保留低 8 位。
  3. 最后,将这个 8 位的二进制数转换为 byte 类型,也就是一个字节。

这个过程中,只保留了原始 uint16 数字的低 8 位,高 8 位被丢弃。因此,强制类型转换后得到的 byte 类型表示了原始数字的低字节。

 

 

至此,我们将数据包长度拆分为两个字节,分别存储在header头中。

 

客户端接收到数据包后,因为接收到的是字节数组,所以可以直接用Index[0]和Index[1]获取到包头数据,也就是上面我们拆分的两个字节,

通过下面的位运算还原数据包长度值:

dataSize := int(header[0])<<8 | int(header[1]) 

 代码的意思是将高字节左移8位(对应我们前面的右移),在“|”或运算,拼接低8位,从而得到完整的数据包大小的二进制表达。

 

综上,需要掌握的知识点有:

字节,二进制、十六进制转换,位运算

 

 

 

 

 
posted on 2023-06-28 10:33  Boom__Clap  阅读(246)  评论(0编辑  收藏  举报