异步TCP服务器 – 消息框架建议

我在C#中有一个异步TCP套接字服务器,我使用Tocket的TcpListener / TcpClient包装器。 我对网络一般都很陌生,所以我不知道TCP如何处理发送的数据,以及它如何不保留消息边界。 经过一番研究后,我想我已经提出了一个可靠的解决方案,但我想知道是否有人对我有更多建议。

目前,我发送的数据是使用protobuf(Google的数据交换库)的序列化类对象的byte [] https://code.google.com/p/protobuf/

在我序列化数据之后,在它发送之前,我决定在字节数组的开头附加一个4字节的Int32。 我的想法是,当数据包被发送到服务器时,它将解析出Int32,然后等到它接收到该数量的字节数,然后再对数据做任何事情,否则再次调用BeginRead。

这是我编写数据之前运行的代码,它似乎工作正常,但我对任何性能建议持开放态度:

public byte[] PackByteArrayForSending(byte[] data) { // [0-3] Packet Length // [3-*] original data // Get Int32 of the packet length byte[] packetLength = BitConverter.GetBytes(data.Length); // Allocate a new byte[] destination array the size of the original data length plus the packet length array size byte[] dest = new byte[packetLength.Length + data.Length]; // Copy the packetLength array to the dest array Buffer.BlockCopy(packetLength, 0, dest, 0, packetLength.Length); // Copy the data array to the dest array Buffer.BlockCopy(data, 0, dest, packetLength.Length, data.Length); return dest; } 

我有点卡在服务器端。 我让它读取packetLength变量,使用Buffer.BlockCopy复制前4个字节,然后使用BitConverter.ToInt32来读取我应该得到的长度。 我不确定是否应该不断地将传入的数据读入特定于客户端的Stream对象,或者只是使用while循环。 这是我到目前为止在服务器端的代码示例:

 NetworkStream networkStream = client.NetworkStream; int bytesRead = networkStream.EndRead(ar); if (bytesRead == 0) { Console.WriteLine("Got 0 bytes from {0}, marking as OFFLINE.", client.User.Username); RemoveClient(client); } Console.WriteLine("Received {0} bytes.", bytesRead); // Allocate a new byte array the size of the data that needs to be deseralized. byte[] data = new byte[bytesRead]; // Copy the byte array into the toDeserialize buffer Buffer.BlockCopy( client.Buffer, 0, data, 0, bytesRead); // Read the first Int32 tp get the packet length and then use the byte[4] to get the packetLength byte[] packetLengthBytes = new byte[4]; Buffer.BlockCopy( data, 0, packetLengthBytes, 0, packetLengthBytes.Length); int packetLength = BitConverter.ToInt32(packetLengthBytes, 0); // Now what do you recommend? // If not all data is received, call // networkStream.BeginRead(client.Buffer, 0, client.Buffer.Length, ReadCallback, client); // and preserve the initial data in the client object 

感谢您的时间和建议,我期待更多地了解这个主题。

TCP保证在一端填充到流中的字节将以相同的顺序从另一端掉出,而不会丢失或重复。 没有更多,当然不支持大于一个字节的实体。

通常我会添加一个带有消息长度,类型和子类型的标头。 通常会提供相关ID以匹配对响应的请求。

基本模式是获取字节并将它们附加到缓冲区。 如果缓冲区中的数据足以包含消息头,则提取消息长度。 如果缓冲区中的数据足以包含该消息,则从缓冲区中删除该消息并对其进行处理。 重复其余任何数据,直到没有完整的消息要处理。 根据您的应用程序,这可能是等待读取或检查流以获取其他数据的点。 某些应用程序可能需要继续从单独的线程读取流以避免限制发送方。

请注意,您不能假设您有完整的消息长度字段。 处理消息后可能还剩三个字节,无法提取int

根据您的消息,使用循环缓冲区可能更有效,而不是在每次处理消息时混洗任何剩余字节。