简体   繁体   English

异步TCP服务器-邮件构架建议

[英]Async TCP Server - Message Framing Advice

I have an asynchronous TCP socket server in C# that I've made using the TcpListener/TcpClient wrappers for Socket. 我在C#中有一个异步TCP套接字服务器,该服务器使用Socket的TcpListener / TcpClient包装器制成。 I'm pretty new to networking in general so I was unaware of how TCP treats data sent, and how it doesn't preserve the message boundaries. 我一般对网络还很陌生,因此我不知道TCP如何处理发送的数据以及它如何不保留消息边界。 After a little research I think I've come up with a solid solution but I'm wondering if anyone has more advice for me. 经过一番研究,我认为我已经提出了一个可靠的解决方案,但我想知道是否有人对我有更多建议。

Currently the data I'm sending is a byte[] of a serialized class object using protobuf (Google's data exchange library) https://code.google.com/p/protobuf/ 目前,我要发送的数据是使用protobuf(Google的数据交换库) https://code.google.com/p/protobuf/的序列化类对象的byte []

After I serialize my data, and before it's sent, I've decided to append a 4 byte Int32 at the beginning of the byte array. 在序列化数据之后,在发送数据之前,我决定在字节数组的开头附加一个4字节的Int32。 My idea is that when the packet is sent to the server, it will parse out the Int32 and then wait until it's received that number of bytes before doing anything with the data, otherwise just calling BeginRead again. 我的想法是,当数据包发送到服务器时,它将解析出Int32,然后等待直到接收到该字节数,然后再对数据进行任何处理,否则只需再次调用BeginRead。

Here is the code it's ran through before I write the data, and it seems to work fine, but I'm open to any performance suggestions: 这是在我编写数据之前运行过的代码,它似乎可以正常工作,但是我可以接受任何性能建议:

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;
}

I'm a little stuck on the server end. 我在服务器端有些卡住。 I have it reading the packetLength variable by using Buffer.BlockCopy to copy the first 4 bytes and then BitConverter.ToInt32 to read the length I should be getting. 我通过使用Buffer.BlockCopy复制前4个字节,然后使用BitConverter.ToInt32来读取我应该获取的长度,来读取packetLength变量。 I'm not sure if I should constantly read the incoming data into a client-specific Stream object, or just use a while loop. 我不确定是否应该不断将传入的数据读取到特定于客户端的Stream对象中,还是仅使用while循环。 Here's an example of the code I have on the server end so far: 到目前为止,这是我在服务器端拥有的代码示例:

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

Thanks for your time and advice, I look forward to learning more about this subject. 感谢您的宝贵时间和建议,我希望能进一步了解该主题。

TCP guarantees that the bytes stuffed into the stream at one end will fall out the other end in the same order and without loss or duplication. TCP确保在一端填充到流中的字节将以相同顺序掉落到另一端,并且不会丢失或重复。 Expect nothing more, certainly not support for entities larger than a byte. 仅此而已,当然不支持大于一个字节的实体。

Typically I have prepended a header with a message length, type and subtype. 通常,我在邮件头的前面加上了消息的长度,类型和子类型。 There is often a correlation id provided to match requests to responses. 通常会提供一个关联ID,以将请求与响应进行匹配。

The basic pattern is to get bytes and append them to a buffer. 基本模式是获取字节并将其附加到缓冲区。 If the data in the buffer is sufficient to contain a message header then extract the message length. 如果缓冲区中的数据足以包含消息头,则提取消息长度。 If the data in the buffer is sufficient to contain the message then remove the message from the buffer and process it. 如果缓冲区中的数据足以包含该消息,则从缓冲区中删除该消息并进行处理。 Repeat with any remaining data until there are no complete messages to process. 重复所有剩余数据,直到没有完整的消息要处理。 Depending on your application this may be the point to wait on a read or check the stream for additional data. 根据您的应用程序,这可能是等待读取或检查流中是否有其他数据的关键。 Some applications may need to keep reading the stream from a separate thread to avoid throttling the sender. 某些应用程序可能需要继续从单独的线程读取流,以避免限制发送方。

Note that you cannot assume that you have a complete message length field. 请注意,您不能假定您具有完整的消息长度字段。 You may have three bytes left after processing a message and cannot extract an int . 处理一条消息后,您可能还剩下三个字节,并且无法提取int

Depending on your messages it may be more efficient to use a circular buffer rather than shuffling any remaining bytes each time a message is processed. 根据您的消息,使用循环缓冲区可能比每次处理消息时改组剩余字节的效率更高。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM