Zinx提供了针对传输过程中二进制数据帧进行编解码能力,基于Zinx的Tcp数据流传输,开发者不需要考虑粘包和断包的问题,Zinx内部已经自行处理。

1、LengthField 数据帧解码

Zinx的二进制编解码算法采用的是 LengthField 的算法,开发者需要理解 LengthField 的基本参数,然后可以通过自己业务场景的数据帧格式来配置LengthField的参数,下面是有关LengthField的参数说明:
Zinx的LengthField数据帧的解码算法代码在 zinx/framedocder.go at master · aceld/zinx 中。其中FrameDecoder一个解码器,根据消息中长度字段的值动态地拆分接收到的二进制数据帧, 当您解码具有表示消息正文或整个消息长度的整数头字段的二进制消息时,它特别有用。

ziface.LengthField 有许多配置参数,因此它可以解码任何具有长度字段的消息,这在专有的客户端-服务器协议中经常见到。
LengthField是一种通用的消息编解码协议,在编码过程中将消息的长度作为消息头的一部分进行编码,而在解码过程中,首先读取消息头,从中解析出消息的长度,然后再根据长度读取后续的消息内容。在LengthField协议中,有四个关键参数需要配置,分别为:

  • LengthFieldOffset:表示长度字段在消息头中的偏移量,即长度字段距离消息头起始位置的字节数。通常情况下,长度字段位于消息头的前面,因此这个值通常是一个非负整数。
  • LengthFieldLength:表示长度字段的字节数。通常情况下,这个值是一个小于等于8的正整数,因为现实中的消息通常不会超过8个字节。
  • LengthAdjustment:表示消息长度需要进行的调整,即在读取长度字段后,如果需要对其进行一些调整,可以通过这个参数来实现。例如,如果消息长度包括了消息头的长度,那么LengthAdjustment应该设置为负数,以便在读取消息体时能够正确地计算出消息的实际长度。
  • InitialBytesToStrip:表示在解码时需要跳过的字节数,即跳过消息头的字节数。通常情况下,这个值等于消息头的长度,因为在解码时需要先读取消息头并解析出长度字段,然后再跳过消息头,读取消息体。
  • MaxFrameLength:用于指定解码器能够处理的最大帧(frame)长度。在使用 LengthFieldBasedFrameDecoder 这样的帧解码器时,如果接收到的帧超过了 MaxFrameLength,则解码器将异常,并且将该连接关闭。这个参数的作用是限制解码器的处理能力,以避免内存溢出或拒绝服务攻击等风险。需要根据实际情况合理设置这个参数,以确保解码器能够处理所有合法的帧,但又不会受到恶意或错误数据的影响。需要注意的是,MaxFrameLength 参数的值应该根据具体应用的协议和数据传输方式来设置,例如,如果传输的是大文件,则需要设置较大的 MaxFrameLength 值,以便允许更大的帧。而如果传输的是小文本消息,则可以设置较小的 MaxFrameLength 值,以提高安全性和可靠性。

总的来说,LengthField协议的编解码过程中需要用到这四个参数来描述消息头和消息体的结构,以便正确地解析出消息的长度和内容。需要注意的是,这四个参数的具体取值需要根据消息的实际格式来确定,通常需要事先进行协商和定义。

1.1 LengthField配置案例一

“在偏移量为0的位置使用2字节长度字段,不去掉消息头。”
在这个例子中,长度字段的值是 12 (0x0C),表示”HELLO, WORLD”的长度。默认情况下,解码器会假设长度字段代表跟在长度字段后面的字节数。因此,可以使用简单的参数组合进行解码。
报文格式解码前后如下:
1.2 LengthField配置案例二
“位于偏移量0的2字节长度字段,去掉消息头。”
您可能希望通过指定”InitialBytesToStrip”来去掉长度字段。在此示例中,我们指定了”2”,与长度字段的长度相同,以去掉前两个字节。
报文格式解码前后如下:
1.3 LengthField配置案例三
“位于偏移量0处的2字节长度字段,不剥离头部,该长度字段表示整个消息的长度。”
在大多数情况下,长度字段仅表示消息体的长度,就像前面的例子所示。然而,在一些协议中,长度字段表示整个消息的长度,包括消息头部。在这种情况下,我们需要指定一个非零的LengthAdjustment。因为这个例子消息中长度值总是比消息体长度大2,所以我们将LengthAdjustment设置为-2进行补偿。
报文格式解码前后如下:
1.4 LengthField配置案例四
“5个字节的头部中包含3个字节的长度字段,不去除头部。”
面的消息是第一个示例的简单变体。在消息前面添加了额外的头部值。LengthAdjustment 再次为零,因为解码器始终考虑预置数据的长度进行帧长度计算。
报文格式解码前后如下:
1.5 LengthField配置案例五
“在 5 个字节的头部中有 3 个字节的长度字段,不剥离头部。”
这是一个高级的例子,展示了在长度字段和消息体之间有额外头部的情况。您需要指定一个正的 LengthAdjustment,以便解码器在帧长度计算中计算额外的头部。
报文格式解码前后如下:
1.6 LengthField配置案例六
“4字节头部,其中2字节长度字段位于偏移量1的位置,剥离第一个头部字段和长度字段。”
这是以上所有示例的组合。在长度字段之前有预置的头部,而在长度字段之后有额外的头部。预置头部影响LengthFieldOffset,额外的头部影响LengthAdjustment。我们还指定了一个非零的InitialBytesToStrip,以从帧中剥离长度字段和预置头部。如果您不想剥离预置头部,则可以将initialBytesToSkip指定为0。
报文格式解码前后如下:
1.7 LengthField配置案例七
“2字节长度字段在4字节头部的偏移量为1的位置,去除第一个头字段和长度字段,长度字段表示整个消息的长度。”
让我们对前面的示例进行一些变化。与先前的示例唯一的区别在于,长度字段表示整个消息的长度,而不是消息正文,就像第三个示例一样。我们必须将HDR1和Length的长度计入LengthAdjustment。请注意,我们不需要考虑HDR2的长度,因为长度字段已经包含了整个头部的长度。
报文格式解码前后如下:
2、Zinx已集成的Decoder
2.1 IDecoder
Zinx提供的Decoder解码接口如下:
IDecoder继承了IInterceptor的接口,所以实现一个Decoder实例需要实现以下方法:
2.2 TLV-Decoder(大端字节序)
Zinx目前默认使用的数据报文协议是TLV的协议,且使用的大端字节对齐方式。
注意:TLV-Decoder(大端字节序)为Zinx目前默认的数据帧解析方式
2.2.1 报文格式
2.2.2 LengthField的配置参数
2.2.3 TLV-Decoder实现代码
拦截器实现:
2.3 HTLVCRC-Decoder(大端字节序)
2.3.1 报文格式
2.3.2 LengthField的配置参数
2.3.3 HTLVCRC-Decoder实现代码
2.4 LTV-Decoder(小端字节序)
2.4.1 报文格式
2.4.2 LengthField的配置参数为:
2.4.3 LTV-Decoder的实现代码
3、使用自定义的Decoder解码器
开发者也可以自定义自己的Decoder,只需要参考TLV-Decoder和HTLVCRC-Decoder的实现即可,然后通过server/client 的 SetDecoder(IDecoder)方法来给服务端或者客户端设置自己的Decoder。
例如Server的案例,这里我们假定自定义的Decoder是HTLVCRC-Decoder,那么可以通过下面给Server设置自定义的Decoder:
Go
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func main() {
//创建一个server句柄
s := znet.NewServer()

//处理HTLVCRC协议数据
s.SetDecoder(zdecoder.NewHTLVCRCDecoder())

s.AddRouter(0x10, &router.HtlvCrcBusinessRouter{}) //模拟数据funcode字段为0x10
s.AddRouter(0x13, &router.HtlvCrcBusinessRouter{}) //模拟数据funcode字段为0x13

//开启服务
s.Serve()

}

对应的处理业务如下:
Go
复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
type HtlvCrcBusinessRouter struct {
znet.BaseRouter
}

func (this *HtlvCrcBusinessRouter) Handle(request ziface.IRequest) {

//MsgID
msgID := request.GetMessage().GetMsgID()
zlog.Ins().DebugF("Call HtlvCrcBusinessRouter Handle %d %s\n", msgID, hex.EncodeToString(request.GetMessage().GetData()))

resp := request.GetResponse()
if resp == nil {
    return
}

//得到解码后的数据
tlvData := resp.(zdecoder.HtlvCrcDecoder)

zlog.Ins().DebugF("do msgid=0x10 data business %+v\n", tlvData)

}