【Go语言Decoder报错gob: duplicate type received】

文章讲述了在网络通信中,使用gob编解码时遇到duplicatetypereceived错误的分析,原因包括类型信息重复发送、Decoder缓存未清空、并发问题和代码逻辑错误。提供了检查和修复这类错误的建议和源码解读。
摘要由CSDN通过智能技术生成

1.场景

要写一个网络通信的,服务器一直监听并解析请求。客户端接收到java端后每次都用一个新的Encoder发送一个数据结构给服务器的Decoder。导致报错:gob: duplicate type received

2. 分析

我的代码报错“gob: duplicate type received”,这个错误通常发生在接收端在接收到已经注册过的类型信息时。在使用gob进行编解码时,编解码器会对发送的数据类型进行注册,确保两端都能识别数据的类型。每次使用新的Encoder发送数据时,如果每次都在发送自定义类型的信息,而接收端的Decoder并没有清除之前接收到的类型信息,那么再次接收到同样的类型时,就会出现“duplicate type received”的错误。

以下是可能的原因和解决建议:

  • 类型信息重复发送:确保每个自定义类型只需在通信开始时发送一次类型信息,后续的数据传输直接使用已经注册过的类型ID。

  • Decoder缓存不清空:如果你在每次接收数据时都使用同一个Decoder实例,并且没有清除之前接收到的类型信息,那么在下次接收时就可能发生冲突。在这种情况下,确保在适当的时候清理或重置Decoder的内部状态。

  • 多线程/并发问题:如果你在并发环境下使用多个Encoder发送数据,且没有做好类型同步,也可能导致类型信息重复发送。这时,你需要确保并发环境下类型注册的一致性。

  • 代码逻辑错误:检查发送端是否无意中多次发送了类型信息,或者接收端在处理数据时的逻辑错误导致类型信息被多次读取和注册。

针对上述情况,请检查代码逻辑,确保类型信息只被发送和注册一次,并在接收端正确处理类型信息,避免重复注册。如果是并发场景,请考虑使用适当的同步机制来管理类型信息的发送和接收。

最后我将客户端的Encoder保存起来,实现了Encoder和Decoder一对一即可。

2.源码解读

func (dec *Decoder) Decode(e any) error {
	//首先,函数检查传入的 e 是否为 nil。如果为 nil,则直接丢弃即将读取的值并返回,不会进行实际的解码操作。
	if e == nil {
		return dec.DecodeValue(reflect.Value{})
	}
	//然后,获取 e 的反射值 value,并进一步判断其是否为指针类型。
	//根据 gob 的设计,解码的目标必须是一个指向预期类型值的指针。
	//如果不是指针类型,函数将返回一个错误,提示用户尝试将数据解码到非指针类型。
	value := reflect.ValueOf(e)
	if value.Type().Kind() != reflect.Pointer {
		dec.err = errors.New("gob: attempt to decode into a non-pointer")
		return dec.err
	}
	//如果 value 是指针类型,函数会调用 DecodeValue 方法进行实际的解码工作。
	//DecodeValue 方法会将从输入流读取的值解码并存放到 value 所指向的具体类型实例中。
	return dec.DecodeValue(value)
}


func (dec *Decoder) DecodeValue(v reflect.Value) error {
	//首先,检查传入的反射值v是否有效。如果v无效(v.Kind() == reflect.Invalid),那么函数会跳过该值,不进行任何处理。
	if v.IsValid() {
		//如果v是一个非nil的指针,函数会尝试通过指针存储解码后的值。如果不是指针或者虽然是指针但为nil,那么v必须是可以设置的(v.CanSet()),否则函数会返回一个错误。
		if v.Kind() == reflect.Pointer && !v.IsNil() {
			// 表示正确,什么也不做。接下来会用这个point来存储
		} else if !v.CanSet() {
			return errors.New("gob: DecodeValue of unassignable value")
		}
	}
	// Make sure we're single-threaded through here.
	dec.mutex.Lock()
	defer dec.mutex.Unlock()

	dec.buf.Reset() // In case data lingers from previous invocation.
	// 设置内部错误状态为nil(dec.err = nil)。
	dec.err = nil
	// 通过调用decodeTypeSequence(false)从输入流中解析出要解码的类型ID。
	id := dec.decodeTypeSequence(false)
	if dec.err == nil {
		// 如果解析类型ID过程中没有遇到错误,则调用decodeValue(id, v)将解析出的数据解码,并存入反射值v
		dec.decodeValue(id, v)
	}
	return dec.err
}
func (dec *Decoder) decodeTypeSequence(isInterface bool) typeId {
	firstMessage := true
	// 循环读取编码数据流,直到遇到错误或达到文件结束(EOF)
	for dec.err == nil {
		// 每次循环开始时,首先检查当前缓冲区(dec.buf)是否有剩余数据,如果没有,则尝试接收新的消息(通过 recvMessage 方法)。
		if dec.buf.Len() == 0 {
			if !dec.recvMessage() {
				if !firstMessage && dec.err == io.EOF {
					dec.err = io.ErrUnexpectedEOF
				}
				break
			}
		}
		// 从缓冲区中读取并解析类型 ID (typeId)
		id := typeId(dec.nextInt())
		// 如果类型 ID 为正数,表示接下来的数据属于该类型,此时返回该类型 ID,表明已准备好解码对应的值。
		if id >= 0 {
			return id
		}
		// 如果类型 ID 为负数,表示接下来是类型定义,调用 recvType 方法解析并注册该类型。
		dec.recvType(-id)
		if dec.err != nil {
			break
		}
		// 当解码接口类型(isInterface 参数为 true)时,处理完类型定义之后,可能会有额外的 DelimitedValue 数据。这时,跳过其计数值,以确保正确解析下一个值。
		if dec.buf.Len() > 0 {
			if !isInterface {
				dec.err = errors.New("extra data in buffer")
				break
			}
			dec.nextUint()
		}
		firstMessage = false
	}
	return -1
}
// 接收并加载类型定义。这个函数的主要作用是处理 gob 编码格式中关于自定义类型的定义信息。
func (dec *Decoder) recvType(id typeId) {
	//检查给定的类型 ID(id)是否已经存在于 dec.wireType 映射表中。
	// 如果该类型 ID 已经存在或者小于预设的 firstUserId,说明收到了重复的类型定义,这是一种错误情况,函数将返回错误信息 "gob: duplicate type received"。
	if id < firstUserId || dec.wireType[id] != nil {
		dec.err = errors.New("gob: duplicate type received")
		return
	}
	// 创建一个新的 wireType 结构体实例 wire
	wire := new(wireType)
	// 使用 decodeValue 方法解码类型定义,将接收到的二进制数据转换并填充到 wire 结构体中。这里的 tWireType 是 wireType 结构体类型的反射值。
	dec.decodeValue(tWireType, reflect.ValueOf(wire))
	if dec.err != nil {
		return
	}
	// Remember we've seen this type.
	dec.wireType[id] = wire
}
  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值