Golang中TCP固定大小消息组帧方法

TCP fixed size message framing method in Golang

我无法理解使用固定大小前缀长度 header 的消息框架是如何工作的。

据说一个固定大小的字节数组将包含要发送的消息的长度。但是你如何定义一个固定大小的字节数组,特别是在 Golang 中。

说这是我的信息:

Hello

它的长度是5。

所以如果我想通过 tcp 流发送它,为了确保我在另一端收到所有消息,我必须告诉它应该读取多少字节。

一个简单的 header 将是 length:message:

5:Hello // [53 58 104 101 108 108 111]

但是如果消息长度每次增长 10 倍,就会有更多的字节。所以 header 大小是动态的。

36:Hello, this is just a dumb question. // [51 54 58 72 101 108 108 111 44 32 116 104 105 115 32 105 115 32 106 117 115 116 32 97 32 100 117 109 98 32 113 117 101 115 116 105 111 110 46]

所以这里36需要2个字节。

我想到的一种方法是考虑协议的最大消息长度。假设 10KB = 10240 字节。然后将前导 0 添加到消息长度。这样我确定我将有一个固定的 5 个字节 header.

这适用于所有情况吗?

如果是,如果我有超过 10KB 的消息,我应该将它分成 2 条消息吗?

如果不是,还有什么其他解决方案?

我想在 Golang 中实现解决方案。

更新 1:

我阅读了有关 Endian 的内容,尽管我无法理解他们所做的导致固定长度字节的事情。但是我在python中找到了一个例子,并尝试这样写:

客户:

    const maxLengthBytes = 8
    conn, err := net.Dial("tcp", "127.0.0.1:9999")
    if err != nil {
        fmt.Println(err)
        return
    }
    message := "Hello, this is just a dumb question"
    bs := make([]byte, maxLengthBytes)
    binary.LittleEndian.PutUint64(bs, uint64(len(text)))
    bytes := append(bs, []byte(text)...)
    conn.Write(bytes)

服务器:

    listener, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 9999})
    if err != nil {
        fmt.Println(err)
        return
    }
    for {
        tcp, err := listener.AcceptTCP()
        if err != nil {
            fmt.Println(err)
            continue
        }
        go Reader(tcp)
    }

func Reader(conn *net.TCPConn) {
    foundLength := false
    messageLength := 0
    for {
        if !foundLength {
            var b = make([]byte, maxLengthBytes)
            read, err := conn.Read(b)
            if err != nil {
                fmt.Println(err)
                continue
            }
            if read != 8 {
                fmt.Println("invalid header")
                continue
            }
            foundLength = true
            messageLength = int(binary.LittleEndian.Uint64(b))
        } else {
            var message = make([]byte, messageLength)
            read, err := conn.Read(message)
            if err != nil {
                fmt.Println(err)
                continue
            }
            if read != messageLength {
                fmt.Println("invalid data")
                continue
            }
            fmt.Println("Received:", string(message))
            foundLength = false
            messageLength = 0
        }
    }
}

请参考我的回答post TCP client for Android: text is not received in full

基本上,您必须定义数据的方式stored/formatted。 例如:

  1. 我们将前缀长度存储为 int32(4 个字节),小端。这和你的不一样。

  2. 用你的方案,长度是string,很难固定长度。 对于您的解决方案,您必须使用固定长度的字符串。例如:10个字符,并加前导零。

回答您的问题。

  1. 它并不适用于只有前缀长度的所有情况。它有它的局限性,比如我们使用int32作为前缀长度,消息的长度必须小于Integer32.max,对吗?

  2. 是的,我们必须拆分甚至合并(请参考我上面的解释link)。

  3. 如果我们关心长度限制,我们有很多方法来处理(实际上,几乎应用程序协议都有最大请求大小)。 可以再用一位表示,消息是否超过最大长度来解析,对吧?