【问题标题】:How do I make sure I receive the whole message if it doesn't fit in the buffer in TCP?如果它不适合 TCP 的缓冲区,我如何确保我收到整个消息?
【发布时间】:2020-08-18 16:10:38
【问题描述】:

我最近开始在 C# 中使用 TCP。我现在正处于我希望客户端接收服务器发送的数据的地步。

我知道不能保证客户端一次接收所有数据。如果发送的数据大小大于客户端缓冲区的大小,则数据将被分批发送。所以我的问题是:如何将我收到的所有数据存储在一个字节数组中,然后在收到所有数据后将其转换为实际消息?

我已将缓冲区大小设置为 1,因此我可以看到当所有发送的数据都不适合缓冲区时会发生什么。这是我在 Client.cs 中调用 stream.BeginRead() 的方法:

// Deliberately setting the buffer size to 1, to simulate what happens when the message doesn't fit in the buffer.
int bufferSize = 1;
byte[] receiveBuffer;

private void ConnectCallback(IAsyncResult result)
{
    client.EndConnect(result);

    Console.WriteLine("Connected to server.");

    stream = client.GetStream();

    // At this point, the client is connected, and we're expecting a message: "Welcome!"
    receiveBuffer = new byte[bufferSize];
    stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, new AsyncCallback(ReadCallback), stream);
}

private void ReadCallback(IAsyncResult result)
{
    int bytesLength = stream.EndRead(result);

    // Should be "Welcome!". But of course it's "W", because the bufferSize is 1.
    string message = Encoding.UTF8.GetString(receiveBuffer, 0, bytesLength);

    Console.WriteLine("Received message: {0}", receivedMessage);

    // Reset the buffer and begin reading a new message, which will be "e".
    // However, I want the whole message ("Welcome!") in one byte array.
    receiveBuffer = new byte[bufferSize];
    stream.BeginRead(receiveBuffer, 0, receiveBuffer.Length, ReadCallback, null);
}

这是发送消息“Welcome!”时的输出:

Connected to server.
Received message: W
Received message: e
Received message: l
Received message: c
Received message: o
Received message: m
Received message: e
Received message: !

我是否应该临时存储数据直到整个消息到达,然后将其转换为字符串?

后续问题:如果 2 条消息彼此紧挨着发送,例如 Welcome!,然后是 What's your name,该怎么办?那我该如何区分这两条消息呢?

【问题讨论】:

  • “我应该临时存储...”。是的。您也可以在字符串/数据包前面加上它的长度(如果您愿意的话)。 “如果有 2 条消息……”。你不能。为此,您需要一个 protocol。您可以使用分隔符(例如 - 例如 - 每个 message 之后的强制换行符)。如果您使用数据包大小作为前缀,那么您也可以使用它来区分数据包。通常不需要重新发明轮子,有许多现成的协议和库可以做到这一点。提示:您正在发送/接收 消息...
  • 只是为了澄清-即使客户端的缓冲区比数据大,仍然不能保证一次接收到所有数据。这实际上取决于网络如何缓冲数据 - 不一定是代码中定义的缓冲区。 MSDN 声明 NetworkStream.Read -“此方法读取缓冲区参数中可用的尽可能多的数据,并返回成功读取的字节数。”您总是想检查实际读取的字节数。
  • 如果有帮助,您正在研究的主题称为“TCP 消息帧”。这个页面有一些基本的想法:blog.stephencleary.com/2009/04/message-framing.html

标签: c# .net networking tcp tcpclient


【解决方案1】:

我是否应该临时存储数据直到整个消息到达,然后将其转换为字符串?

是的,没错。

后续问题:如果 2 条消息彼此紧挨着发送会怎样,例如 Welcome!然后你叫什么名字?那我该如何区分这两条消息呢?

一般的做法是在消息本身之前发送消息的长度。这样接收端就会知道它何时收到了一个完整的包裹。

【讨论】:

    【解决方案2】:

    正如 500 - Internal Server Error 已经指出的那样,您使用缓冲区。这是一些代码示例: 接收:

    while (true) //you should use a bool variable here to stop this on disconnect.
                    {
                        byte[] bytes;
    
                        bytes = ReadNBytes(ns, 4);
                        //read out the length field we know is there, because the server always sends it.
                        int msgLenth = BitConverter.ToInt32(bytes, 0);
                        bytes = ReadNBytes(ns, msgLenth);
                        //working with the buffer...
                        if (bytes.Length > 0)
                        {
                            try
                            {
                                //do stuff here. bytes contains your complete message.
                            }
                            catch (Exception e) { Log(e.Message); }                            
                        }
                    }
    
    
    public static byte[] ReadNBytes(NetworkStream stream, int n)
        {
            byte[] buffer = new byte[n];
            try
            {
                int bytesRead = 0;
    
                int chunk;
                while (bytesRead < n)
                {
                    chunk = stream.Read(buffer, (int)bytesRead, buffer.Length - (int)bytesRead);
                    if (chunk == 0)
                    {
                        // error out
                        Log("Unexpected disconnect");
                        stream.Close();
                    }
                    bytesRead += chunk;
                }
            }
            catch (Exception e) { Log(e.Message); }
    
            return buffer;
        }
    

    要发送东西,请使用以下链接:

    public static void SendObject(NetworkStream ns, byte[] data)
        {
            byte[] lengthBuffer = BitConverter.GetBytes(data.Length);
            ns.Write(lengthBuffer, 0, lengthBuffer.Length);
            ns.Write(data, 0, data.Length);
        }
    

    希望对你有所帮助!

    【讨论】:

      猜你喜欢
      • 2020-01-29
      • 1970-01-01
      • 1970-01-01
      • 2021-01-23
      • 2012-10-24
      • 2019-11-10
      • 1970-01-01
      • 2019-03-17
      • 1970-01-01
      相关资源
      最近更新 更多