【问题标题】:TCP Protobuf messages arriving inconsistently on recieving side of Go serverTCP Protobuf 消息在 Go 服务器的接收端不一致地到达
【发布时间】:2021-03-05 09:01:27
【问题描述】:

我有一个“代理”,它将二进制文件解析到缓冲区中,每当该缓冲区被填满时,通过 protobuf 消息将其发送到服务器,然后继续进行下一个二进制解析块,然后再次发送,等等。

在服务器上,我使用简单的net/conn 包来侦听代理连接并在while-for 循环中将其读取到缓冲区中。 当解析完成代理端时,它会在 protobuf 消息中发送一个terminate bool,表明这是最后一条消息,服务器可以继续处理接收到的全部数据。

但是,如果我将调试打印留在发送方,这会正常工作,这会使终端打印显着减慢通过 connection.Write() 发送后续 protobuf 消息的时间间隔。

如果我取消注释此记录器,那么它发送消息的速度太快,服务器处理的第一个传入消息是包含terminate 标志的一个数据包,例如,它没有收到实际有效负载,而是立即收到最后一条消息。

我知道 TCP 并没有真正区分不同的 []byte 数据包,这很可能是导致这种行为的原因。有没有更好的方法可以做到这一点,还有其他选择吗?

伪代码代理端:

    buffer := make([]byte, 1024)
    for {
        n, ioErr := reader.Read(buffer)
        if ioErr == io.EOF {
            isPayloadFinal = true

            // Create protobuf message
            terminalMessage, err := CreateMessage_FilePackage(
                2234,
                protobuf.MessageType_PACKAGE,
                make([]byte, 1),
                isPayloadFinal,
            )
            // Send terminate message
            sendProtoBufMessage(connection, terminalMessage)
            break
        }
        // Create regular protobuf message
        message, err := CreateMessage_FilePackage(
            2234,
            protobuf.MessageType_PACKAGE,
            (buffer)[:n],
            isPayloadFinal)
        sendProtoBufMessage(connection, message)
   }

伪代码服务器端:

    buffer := make([]byte, 2048)
    //var protoMessage protoBufMessage

    for artifactReceived != true {
        connection.SetReadDeadline(time.Now().Add(timeoutDuration))
        n, _ := connection.Read(buffer)
        decodedMessage := &protobuf.FileMessage{}
        if err := proto.Unmarshal(buffer[:n], decodedMessage); err != nil {
            log.Err(err).Msg("Error during unmarshalling")
        }

        if isPackageFinal := decodedMessage.GetIsTerminated(); isPackageFinal == true {
            artifactReceived = true
            log.Info().Msg("Artifact fully received")
            /* Do stuff here */
            break
        }
        // Handle partially arrived bytestream
        handleProtoPackage(packageMessage, artifactPath)
        } else {
            fmt.Println("INVALID PROTOBUF MESSAGE")
        }
    }

以及供参考的proto文件:

message FilePackage{
    int32 id = 1;
    MessageType msgType = 2;
    bytes payload = 3;
    bool isTerminated = 4;

}

【问题讨论】:

    标签: go tcp buffer protocol-buffers


    【解决方案1】:

    正如您所说,最可能的原因似乎是“TCP 并没有真正区分不同的 []byte 数据包”(TCP 流没有消息边界)。当您调用connection.Read(buffer)(我假设connectionnet.Conn)时,它将阻塞直到某些数据可用(或达到读取截止日期),然后返回该数据(直到缓冲区大小)。返回的数据可能是一条消息(正如您在测试中看到的那样),但也可能是部分消息,或者是多条消息(取决于时间和网络堆栈;您不应做任何假设)。

    protobuf docs 提供了一个建议的技术:

    如果您想将多条消息写入单个文件或流,则由您来跟踪一条消息的结束位置和下一条消息的开始位置。协议缓冲区有线格式不是自定界的,因此协议缓冲区解析器无法自行确定消息的结束位置。解决此问题的最简单方法是在编写消息本身之前写入每条消息的大小。当您读回消息时,您会读取大小,然后将字节读入单独的缓冲区,然后从该缓冲区进行解析。

    如果您采用这种方法,那么您可以在接收数据时使用io.ReadFull(因为您将知道该大小需要多少字节,然后使用它来接收数据包)。

    【讨论】:

    • 感谢您的回答。计算 protobuff 消息大小的最有效方法是什么?我尝试使用 gob.NewEncoder(b).Encode(v) 后跟 len() 但它超级慢!
    • @PasinduTennage gob 和 protobuf (benchmarks here) 之间存在很多差异。由于预先定义了 protobuf 消息格式,发送方应该能够相当快地计算出大小。可能最好在一个新问题中提出这个问题,并详细说明您的要求。
    • 感谢您的建议。我在以下链接中提出了一个新问题。 stackoverflow.com/questions/68635618/…
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-10-18
    • 1970-01-01
    • 1970-01-01
    • 2016-04-28
    • 2021-08-06
    • 2019-10-01
    • 2019-10-01
    相关资源
    最近更新 更多