这是我other question的一个分支。如果你愿意,请阅读它,但没有必要。
基本上,我意识到为了在大型消息上有效地使用C#的BeginReceive(),我需要(a)首先读取数据包长度,然后准确读取那么多字节或(b)使用结尾 - 数据包分隔符。我的问题是,这些都存在于协议缓冲区中吗?我还没有使用它们,但是看一下文档似乎没有长度标题或分隔符。
如果没有,我该怎么办?我应该只构建消息,然后用长度标题/ EOP分隔符前缀/后缀吗?
答案 0 :(得分:14)
您需要在协议中包含大小或结束标记。基于流的套接字(TCP / IP)没有任何内容,除了支持任意分解成单独数据包的无限八位字节流(并且数据包也可以在传输过程中溢出)。
一种简单的方法是每个“消息”具有固定大小的头,包括协议版本和有效载荷大小以及任何其他固定数据。然后是消息内容(有效载荷)。
可选择添加一个消息页脚(固定大小),其中包含校验和甚至加密签名(取决于您的可靠性/安全性要求)。
知道有效负载大小允许您继续读取足以用于剩余消息的多个字节(如果读取完成的次数较少,则对剩余字节执行另一次读取,直到收到整个消息为止)
使用结束消息指示符也可以,但您需要定义如何处理包含相同八位字节序列的消息...
答案 1 :(得分:6)
道歉迟到了。我是protobuf-net的作者,它是C#实现之一。对于网络使用,您应该考虑“[De] SerializeWithLengthPrefix”方法 - 这样,它将自动为您处理长度。来源中有一些例子。
我不会详细介绍旧帖子,但如果您想了解更多信息,请添加评论,我会尽快回复您。
答案 2 :(得分:3)
我同意Matt的说法,标题比协议缓冲区的页脚好,主要原因是,由于PB是二进制协议,因此提出一个也不是有效消息序列的页脚是有问题的。许多基于页脚的协议(通常是EOL协议)都可以工作,因为消息内容在一个定义的范围内(通常为0x20 - 0x7F ASCII)。
一个有用的方法是让你的最低级代码只是从套接字读取缓冲区并将它们呈现到一个框架层,它组装完整的消息并记住部分消息(我提出了异步方法(使用CCR){ {3}},虽然是行协议)。
为了保持一致性,您始终可以将消息定义为包含三个字段的PB消息:fixed-int作为长度,enum作为类型,以及包含实际数据的字节序列。这使您的整个网络协议保持透明。
答案 3 :(得分:1)
TCP / IP以及UDP,数据包包括对其大小的一些引用。 IP header包含一个16位字段,用于指定IP头和数据的长度(以字节为单位)。 TCP header包含一个4位字段,用于指定32位字中TCP标头的大小。 UDP header包含一个16位字段,用于指定UDP标头和数据的长度(以字节为单位)。
这就是事情。
在Windows中使用标准的普通套接字,无论您是在C#中使用System.Net.Sockets命名空间还是在Win32中使用本机Winsock,您都不会看到IP / TCP / UDP标头。这些标题被剥离,以便您在读取套接字时获得的是实际有效负载,即发送的数据。
我使用套接字看到和完成的所有内容的典型模式是您定义一个应用程序级标头,该标头位于您要发送的数据之前。此标头至少应包含要遵循的数据大小。这将允许您完整地阅读每个“消息”,而无需猜测其大小。您可以随心所欲地使用它,例如,同步模式,CRC,版本,消息类型等,但“消息”的大小是您真正所需要的。
对于它的价值,我建议使用标头而不是数据包结束分隔符。我不确定EOP分隔符是否存在明显的缺点,但标头是我见过的大多数IP协议使用的方法。另外,我似乎更直观地从头开始处理消息,而不是等待某些模式出现在我的流中以表明我的消息已完成。
编辑:我刚刚意识到Google Protocol Buffers项目。据我所知,它是WCF的二进制序列化/反序列化方案(我确信这是一个粗略的过度简化)。如果您正在使用WCF,则不必担心正在发送的消息的大小,因为WCF管道在幕后处理这个问题,这可能是您在协议中找不到与消息长度相关的任何内容的原因。缓冲文档。但是,在插座的情况下,如上所述,了解尺寸将有很大帮助。我的猜测是,您将使用Protocol Buffers序列化您的数据,然后在发送之前确定您提出的任何应用程序头。在接收方,您将拉出标题,然后反序列化消息的其余部分。