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

本文关键字:框架 消息 TCP 服务器 异步 | 更新日期: 2023-09-27 18:09:11

我在c#中有一个异步TCP套接字服务器,我使用tcpplistener/TcpClient包装套接字。总的来说,我是一个网络新手,所以我不知道TCP是如何处理发送的数据的,也不知道它是如何不保留消息边界的。经过一番研究,我想我已经想出了一个可靠的解决方案,但我想知道是否有人能给我更多的建议。

目前我发送的数据是一个字节[]的序列化类对象使用protobuf(谷歌的数据交换库)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;
}

我在服务器端有点卡住了。我让它通过使用Buffer读取packetLength变量。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服务器-消息框架建议

TCP保证在流的一端插入的字节将以相同的顺序从另一端流出,没有丢失或重复。除此之外别无所求,当然不支持大于一个字节的实体。

通常在消息头前加上消息长度、类型和子类型。通常会提供一个关联id来匹配请求和响应。

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

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

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