我目前正在使用多线程应用程序,我使用以下(简化)代码接收数据:
private void BeginReceiveCallback(IAsyncResult ar)
{
bytesReceived = this.Socket.EndReceive(ar);
byte[] receivedData = new byte[bytesReceived];
Array.Copy(buffer, receivedData, bytesReceived);
long protocolLength = BitConverter.ToInt64(receivedData, 0);
string protocol = Encoding.ASCII.GetString(receivedData, 8, (int)protocolLength);
IList<object> sentObjects =
ParseObjectsFromNetworkStream(receivedData, 8 + protocolLength);
InvokeDataReceived(protocol, sentObjects);
}
我遇到receivedData
不仅包含预期数据,还包含更多内容。我怀疑这是之后发送的数据与流中的前一个混合在一起。
我的问题是,我希望将哪些数据存储在此缓冲区中。它可以包含来自客户端的两个不同发送操作的数据吗?在这种情况下,我想我将不得不提出一种协议,可以区分从客户端发送的数据“消息”。一种简单的方法是分别以特定(唯一)字节开始和结束每个流。是否有一种分离信息的通用方法?此外,我想这意味着单个接收调用可能不足以从客户端获取所有数据,这意味着我必须循环直到找到结束字节?
答案 0 :(得分:3)
TCP / IP套接字连接由两个独立的流组成:一个是传入的,一个是传出的。
这是经常错过的TCP / IP的关键概念之一。从应用程序的角度来看,TCP / IP不对数据包进行操作;它在溪流上运作!
没有方法可以发送数据包。 API根本不存在。发送数据时,只需将这些字节放在传出流中即可。然后从另一侧的传入流中读取它们。
例如,一方可以发送5个字节,然后发送另外5个字节。接收方可以接收两批5个字节,或一次接收一个,或者一次读取全部10个......
要将传入的字节流拆分为消息,您需要消息框架。通常使用两种解决方案中的一种。您建议的是分隔符解决方案,其中SOT / EOT字节用于指定消息边界。另一个(我更喜欢)是长度前缀解决方案,其中消息的长度以消息本身为前缀。
更全面的讨论是on my blog,以及sample code for length prefixing。
答案 1 :(得分:1)
使用TCP / IP,数据通常被视为流。您可以收到尽可能多的收到(并且您可能需要再次“接收”才能获得所有已发送的内容)。常见的情况是两个端点会有某种来回的对话。这听起来不像你所描述的情况。处理应用程序正在执行的操作的简单方法是在前几个字节中发送数据长度(例如,4字节整数)。接收端将接收4个字节以找到预期长度,然后接收该确切数量。
答案 2 :(得分:1)
你是对的,套接字收到的流可能会被截断(强制断开连接),或者你看到它可能包含来自另一个发送操作的其他数据。
你可以使用套接字设置来指定立即发送(nagle?我认为它被称为)
通过TCP完成的工作我有一个用于二进制数据的通信协议和另一个用于ascii的通信协议,当我发送二进制数据时,我有一个已知的数据长度,所有通信都必须以(BlockType)开头(2字节)BlockLength(4字节)数据(nBytes))
接收端然后知道读取6字节以计算出类型和长度。如果我收到少于6个字节,请尝试直到我做并缓冲先前的值等,然后一旦建立的大小已知读取,直到您从数据中读取BlockLength字节(根据需要进行缓冲)。
如果你有套接字断开连接,你需要处理恢复或重启等。
如果我正在处理ascii数据,我会在发送的数据块周围使用一种独特的包装方法(~~~ start ~~~).... DATA ....(~~~ end ~~~)而不仅仅是将内容缓冲到一个stringbuilder或类似的东西,直到你点击(~~~ end ~~~),然后继续你的操作。
希望这有一些帮助。