【问题标题】:TCP fixed size message framing method in GolangGolang中的TCP固定大小消息成帧方法
【发布时间】:2020-06-17 01:28:03
【问题描述】:

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

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

说这是我的信息:

Hello

它的长度是5。

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

一个简单的标题是length:message:

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

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

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 字节标头。

这适用于所有情况吗?

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

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

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

更新 1:

我读过有关 Endians 的文章,尽管我无法理解他们做了什么会导致固定长度的字节。但是我在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
        }
    }
}

【问题讨论】:

    标签: go tcp network-protocols


    【解决方案1】:

    请参考我在这篇文章中的回答 TCP client for Android: text is not received in full

    基本上,您必须定义数据的存储/格式化方式。 例如:

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

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

    对于您的问题。

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

    2. 是的,我们必须拆分甚至合并(请参阅我在上面链接中的解释)。

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

    【讨论】:

    • 因为你改回字符串,所以 10 是两个字符串(2 个字节),606 是三个字符串(3 个字节)。 int32 固定为 4 字节大小
    • 我不是 Golang 专家,但请上传您的 sn-p 代码,我可以帮助解决。
    • 上面的源代码可以正常工作,对吧?我的意思是prefix-length的长度,应该是固定的8字节长度。对于拆分消息逻辑,我不确定上述语法:D
    • 请上传你的代码,我会帮忙修复,应该可以用任何语言实现
    • 我明白你的担心,我认为前缀长度是最好的方法,即使只有 4 个字节 (int32)。
    猜你喜欢
    • 1970-01-01
    • 2013-07-07
    • 1970-01-01
    • 2015-03-28
    • 2011-09-27
    • 1970-01-01
    • 1970-01-01
    • 2013-06-30
    • 1970-01-01
    相关资源
    最近更新 更多