【问题标题】:Should I manually embed the data size info in a TCP transfer?我应该在 TCP 传输中手动嵌入数据大小信息吗?
【发布时间】:2011-01-17 10:26:07
【问题描述】:

想象一下你和我正在通过 TCP 发送一个很长的句子(比如 1024000 字节)。

如果你给我写一个 1024000 字节的句子,你实际上是使用 NetworkStream 来写这些字节的。

当我收到时,我应该提前知道你发送的句子的大小吗?

如果没有,我该如何检查何时应该停止 stream.read?

如果是,程序是否应该具有将数据大小嵌入数据头部的功能?所以我先接收 4 个字节,看看我应该读多少个字节?

.Net 是否有任何东西可以自动在传输中嵌入数据大小?

【问题讨论】:

  • 好的,谁能告诉我如何在流的末尾添加NULL?

标签: c# .net tcp data-transfer


【解决方案1】:

当我收到时,我应该事先知道您发送的句子大小吗?

这可能会有所帮助(例如渲染进度条),但不是必需的。

如果没有,我如何检查何时应该停止 stream.read?

您的流的内容定义了这一点。例如,许多消息对一些信息进行编码,告诉您该消息已结束(例如,空字节表示字符串的结尾,或 </html> 表示 HTML 文档的结尾)。

【讨论】:

    【解决方案2】:

    有两种方法可以做到这一点,一种是您描述的方式 - 将消息的大小放在标题中 - 另一种是在流上放置某种终止标记。例如,如果保证您的消息没有嵌入 NUL 字符,则可以使用 NUL 终止。

    【讨论】:

      【解决方案3】:

      由于 TCP 是一种可靠的协议,您可以构建协议以指示即将到来的字节数,或者使用某种终止符来指示传输结束。如果您使用的 UDP 不能保证是可靠的,那么构建一个能够承受丢弃字节的协议或指示自包含终止的数据包以来预期有多少字节(并具有重传机制)将更为重要可能会丢失。最大数据传输时间和超时也可能有用,但前提是您可以确定一个合理的最大值。

      【讨论】:

        【解决方案4】:

        .NET 和 TCP 协议都没有内置任何东西来预先定义消息的大小。 TCP 协议仅指定所有数据将被传输到接收端点(或至少将尽最大努力这样做)。

        您全权负责定义一种方法,让接收者知道要读取多少数据。正如其他人所指出的那样,您如何执行此操作的细节取决于您要传输的内容的性质:您可以像您提到的那样首先发送长度,您可以编码称为终止符的特殊序列,您可以使用预定义的数据块所以所有消息都有相同的大小,等等。

        编辑

        这开始是一条评论,但它不符合这个限制。

        向流中添加 NULL 仅仅意味着附加一个二进制值为 0 的字符(不要与字符 0 混淆)。根据您用于传输的编码(即 ASCII、UTF-8、UTF-16 等),可能会转换为发送一个或多个 0 字节,但如果您使用适当的翻译,您只需输入类似 @ 987654322@ 在您的字符串中。这是一个例子:

        string textToSend = "This is a NULL Terminated text\0";
        byte[] bufferToSend = Encoding.UTF8Encoding.GetBytes(textToSend);
        

        当然,以上所有内容都假设您发送的所有其余数据不包含任何其他 NULL。这意味着它是文本,而不是任意二进制数据(例如文件的内容)。这很重要!否则你不能使用NULL作为消息终止符,你必须想出另一个方案。

        【讨论】:

        • 你能告诉我如何在流的末尾添加NULL吗?
        • 是的。换句话说,TCP 套接字提供了一个可靠的八位字节流。任何结构或边界都必须由发送和接收应用程序强加。
        • 这样做的缺点是,如果您想避免读取超出消息末尾的内容,则始终只能从流中请求一个字节。这可能会变得很慢。
        • 一次读取一个字节是一个非常糟糕的主意,完全没有必要。事实上,你甚至不能一次“请求”一个字节——这不是 TCP 的工作方式,当然也不是它在 .NET 中的实现方式 你总是以块的形式读取数据,然后你可以迭代你收到的数据一个字节来解释它
        【解决方案5】:

        如果您知道或可以轻松找出消息的总长度,我建议您提前发送。如果确定它不可能或非常昂贵,您可以在 HTTP 中使用类似于 chunked transfer encoding 的内容。

        【讨论】:

          【解决方案6】:

          一般来说,使用带有数据大小的标题比使用终止字符更好。终止字符方法容易受到拒绝服务攻击。我可以继续向您的服务发送数据,只要我不包含终止符,您就需要继续处理(并可能分配内存)直到崩溃。

          使用包含总大小的标头,如果传输太大而您无法处理,您可以忽略它,或发回错误。如果恶意方尝试发送的数据多于标头中声明的数据,您会在下一个流的开头注意到一个损坏的标头并忽略它。

          【讨论】:

          • 好点.. 但这假设了接收器的幼稚实现。您可以而且应该对您接收和处理的数据量有一定的界限,这可以根据应用程序通常期望的典型数据大小任意确定
          【解决方案7】:

          主要的一点是,在 TCP 中,传输端的套接字写入的数量和大小与接收端的套接字读取的数量/大小之间没有对应关系。

          如果数据流具有某种结构,则必须在有效负载周围添加某种元/包装数据。

          每当我不得不解决这个问题时,我都会使用以下组合:

          a) 使用幻数来指示数据消息(或两者)的开始或结束

          b) 在 msg 的末尾使用校验和来验证内容是否正确(我知道 TCP 会执行错误检查和重传,但校验和在接收器偶然发现启动的情况下很有用/在流中结束幻数/序列)

          c) 在初始幻数之后使用长度字段(前提是发送方在传输开始之前知道数据的长度)

          在开始自己动手之前,请好好看看为您正在使用的语言/平台实现了哪些更高级别的协议库。网络流?是 Windows API/MFC 什么的。

          例如,我最近必须设置一个客户端/服务器系统。客户端和服务器功能已经用 python 编写,因此只需使用 python xmlrpclib/server 就可以很容易地将这两个程序连接在一起 - 从字面上复制示例,我在 30 分钟内就完成了。如果我自己直接在 tcp 上编写一些自制协议,那将是 5 天!

          【讨论】:

            【解决方案8】:

            我的答案是否定的。特别是对于大型数据集。原因是发送大小首先会在您的系统中增加延迟

            如果要先发送大小,则需要在开始发送之前计算整个答案。

            另一方面,如果您使用终止标记,您可以在数据准备好后立即开始发送第一位数据,同时计算后续数据。

            【讨论】:

              【解决方案9】:

              您可能还想研究 BinaryReader/BinaryWriter 类,它们可以包裹在任何流、TCP 或其他方式周围。

              除了其他功能外,这些还支持读取/写入字符串(以您选择的编码方式),同时还要注意包括字符串的长度。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2020-02-14
                • 2012-03-09
                • 1970-01-01
                • 2015-07-13
                • 2013-09-14
                • 1970-01-01
                • 2018-12-29
                • 2012-03-30
                相关资源
                最近更新 更多