【问题标题】:Packet data structure?包数据结构?
【发布时间】:2008-12-27 11:20:26
【问题描述】:

我正在设计一个游戏服务器,但我以前从未做过这样的事情。我只是想知道数据包的好结构是什么?如果重要的话,我正在使用 TCP。这是一个示例,以及我目前正在考虑使用的示例:

(括号中的每个值都是一个字节)

[Packet length][Action ID][Number of Parameters]
[Parameter 1 data length as int][Parameter 1 data type][Parameter 1 data (multi byte)]
[Parameter 2 data length as int][Parameter 2 data type][Parameter 2 data (multi byte)]
[Parameter n data length as int][Parameter n data type][Parameter n data (multi byte)]

就像我说的,我以前真的从来没有做过这样的事情,所以我上面所说的可能是完全的公牛,这就是我问的原因;)。另外,是否有必要传递总数据包长度?

【问题讨论】:

  • 嘿!你能在每个括号后用换行符打破字节行吗?这样更容易阅读。

标签: networking packet


【解决方案1】:

传递总数据包长度是个好主意。它可能会多花费两个字节,但您可以窥视并等待套接字在接收之前准备好完整的数据包。这使代码更容易。

总的来说,我同意 brazzy,语言提供的序列化机制比任何自制的更可取。

除此之外(我认为您使用的是没有序列化的 C-ish 语言),我会将数据包 ID 作为数据包数据结构中的第一个数据。恕我直言,这是某种约定,因为结构的第一个数据成员始终位于位置 0,并且任何结构都可以向下转换为该位置,从而识别其他匿名数据。

您的编译器可能会或可能不会生成打包结构,但这样您就可以分配缓冲区,读取数据包,然后根据第一个数据成员转换结构。如果你运气不好并且它不生成压缩结构,请确保每个结构都有一个序列化方法,该方法将从(显然非目标)内存构造。

字节序是一个因素,尤其是在类 C 语言中。请务必明确数据包始终具有相同的字节序,或者您可以根据签名或其他内容识别不同的字节序。一件非常酷的奇怪事情:当您使用本文中讨论的方法访问它们时,C# 和 .NET 似乎总是以 little-endian 约定保存数据。在将这样的应用程序移植到 SUN 上的 Mono 时发现了这一点。很酷,但如果你有这样的设置,无论如何你都应该使用 C# 的序列化方法。

除此之外,您的设置看起来还不错!

【讨论】:

    【解决方案2】:

    首先考虑一个更简单的基本包装器:标签、长度、值 (TLV)。您的基本数据包将如下所示:

    [Tag] [Length] [Value]
    

    标签是数据包标识符(如您的操作 ID)。

    Length 是数据包长度。您可能需要它来判断您是否拥有完整的数据包。它还可以让您计算出价值部分的长度。

    包含实际数据。它的格式可以是任何东西。

    在上述情况下,值数据包含一系列 TLV 结构(参数类型、长度、值)。您实际上不需要发送参数的数量,因为您可以根据数据长度和遍历数据来处理它。

    正如其他人所说,我会将数据包ID(标签)放在第一位。除非您有跨平台问题,否则我会考虑将应用程序的序列化对象包装在 TLV 中,然后像这样通过网络发送它。如果您犯了错误或以后想更改,您可以随时创建具有不同结构的新标签。

    有关TLV 的更多详细信息,请参阅维基百科。

    【讨论】:

      【解决方案3】:

      为避免重新发明轮子,任何序列化协议都适用于有线数据(例如 XML、JSON),您可以考虑查看 BEEP 以了解基本协议框架。

      BEEP 在其常见问题解答文档中被很好地总结为“自 80 年代初以来经验丰富的应用程序协议设计人员使用的技巧的“最佳”专辑。'

      【讨论】:

        【解决方案4】:

        没有理由让事情变得如此复杂。我看到您有一个操作 ID,所以我想会有固定数量的操作。

        对于每个动作,您需要定义一个数据结构,然后将其中的每一个值放入该结构中。要通过网络发送它,您只需为结构中的每个元素分配 sum(sizeof(struct.i)) 字节。所以你的数据包看起来像这样:

        [action ID][item 1 (sizeof(item 1 bytes)][item 1 (sizeof(item 2 bytes)]...[item n (sizeof(item n bytes)]
        

        这个想法是,您已经知道连接每一端的每个变量的大小和类型,因此您不需要发送该信息。

        对于字符串,您可以将它们以空终止的形式放入,然后当您“知道”根据数据包类型查找字符串时,开始读取并查找空值。

        --

        另一种选择是使用 '\r\n' 来描述你的变量。这将需要一些开销,并且您必须使用文本,而不是数字的二进制值。但是这样你就可以使用 readline 来读取每个变量。你的数据包看起来像这样

        [action ID]
        [item 1 (as text)]
        ...
        [item n (as text)]
        

        --

        最后,简单地序列化对象并通过网络传递它们也是一种很好的方法,并且需要编写最少的代码。请记住,您不想过早地进行优化,这也包括网络流量。如果你以后需要挤出更多的性能,你可以回去找出一个更有效的机制。

        并查看谷歌的protocol buffers,它被认为是一种以平台无关的方式序列化数据的极快方式,有点像二进制 XML,但没有嵌套元素。还有JSON,这是另一种平台中性编码。使用协议缓冲区或 JSON 意味着您不必担心如何专门对消息进行编码。

        【讨论】:

        • OP 声明了不同的参数类型。如果一个数据类型是一个值数组怎么办?您的解决方案更时尚,但不允许任何遏制。
        【解决方案5】:

        您希望服务器支持用不同语言编写的多个客户端吗?如果不是,则可能没有必要准确指定结构;而是使用您的语言提供的任何工具来序列化数据,这只是为了减少出错的可能性。

        如果您确实需要结构是可移植的,上面看起来没问题,但在这种情况下您也应该指定字节顺序和文本编码等内容。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2021-03-10
          • 1970-01-01
          • 1970-01-01
          • 2016-09-22
          • 1970-01-01
          • 1970-01-01
          • 2010-10-30
          • 1970-01-01
          相关资源
          最近更新 更多