【问题标题】:C# How to manually clear TCP socket buffer?C#如何手动清除TCP套接字缓冲区?
【发布时间】:2019-02-27 17:30:51
【问题描述】:

我正在编写一个 TCP 服务器来打开一个端口并与一些仅将字符串数据作为字节数组发送的硬件通信。

环境在 Unity 中,所以我使用异步回调以避免阻塞程序。这似乎工作正常,连接有效,遇到正确的数据,我可以向硬件发送消息,但套接字缓冲区永远不会清除。当我执行Socket.EndReceive(ar) 时,数据只会堆积起来,不会清空。

异步循环如何工作?我不明白为什么这段代码没有完成循环并清除缓冲区。我花了很多时间试图理解这个过程,但无法弄清楚为什么这段代码不应该工作。

    protected TcpListener ListenServer;
    protected Socket SimNetSocket;
    protected byte[] ReadBuffer = new byte[1024];
    protected string MessageBuffer;

[....] [....]

    public void BeginReceive()
    {
        SimNetSocket.BeginReceive(ReadBuffer, 0, ReadBuffer.Length, SocketFlags.None, EndReceive, null);
    }

    protected void EndReceive(IAsyncResult async)
    {
        string msg = "";

        try { msg = ByteArrayToString(ReadBuffer); }
        catch (Exception e) { Debug.LogError(e); }

        Debug.Log("RAW RECEIVE: " + msg);
        MessageBuffer += msg;
        ReadBuffer = new byte[1024];
        SimNetSocket.EndReceive(async);
        BeginReceive();
    }

MessageBuffer 是一个堆栈,稍后在处理消息的更新循环中被清除,与套接字上的ReadBuffer 复合问题无关。

同样,连接有效(代码未显示)并且通信确实有效。我可以看到数据成功地从双方传来传去,但我无法控制另一端硬件在做什么。是否需要一些实现来接收这些调用并确认缓冲区可以清除?

我实际看到的是硬件发送一条消息,然后是另一条消息,它堆积在它的最后一条消息上,一次又一次,一次又一次。不过,我正在通过上面的代码处理每条消息。所以我很困惑。

【问题讨论】:

  • EndReceive的问题已经注意到了;而且:您使用的是什么文本编码?如果它不是 ASCII,你会有更大的问题 - 请参阅我对其中一个答案的评论
  • 调用构造函数是清除缓冲区:ReadBuffer = new byte[1024];

标签: c# sockets tcp


【解决方案1】:

仅仅因为您要求读取ReadBuffer.Length 字节并不意味着实际上填充了多少字节到缓冲区。您需要保留从EndReceive 返回的int,并仅从缓冲区中读取该字节数。

public void BeginReceive()
{
    SimNetSocket.BeginReceive(ReadBuffer, 0, ReadBuffer.Length, SocketFlags.None, EndReceive, null);
}

protected void EndReceive(IAsyncResult async)
{
    string msg = "";

    int bytesRead = SimNetSocket.EndReceive(async);
    try { msg = ByteArrayToString(ReadBuffer,bytesRead); }
    catch (Exception e) { Debug.LogError(e); }

    Debug.Log("RAW RECEIVE: " + msg);
    MessageBuffer += msg;
    //ReadBuffer = new byte[1024]; //Not necessary, you can re-use the old buffer.
    BeginReceive();
}

【讨论】:

  • 打败我。作为 OP 的附注:另请注意,您不能假设您获得了 整个字符,除非您说的是 ASCII。对于 UTF8、UTF16 等 - 您可以将单个字符拆分为多次读取,因此您将拥有两组损坏的数据 - 一组在第一次读取结束时,一组在第二次读取结​​束时。基本上,在完成“框架” 之后之前,您不能转换为字符串
  • @MarcGravell 是的,在这一点上,对于 OP,这里是一篇非常好的文章,可以了解更多关于框架blog.stephencleary.com/2009/04/message-framing.html
  • @MarcGravell 哦,我以前没有看过你的文章。我一定会读的。
  • @MarcGravell:请注意,当使用System.Text.Decoder 对象时,框架不是问题,至少在字符解码方面是这样。 Decoder 对象缓冲部分字符,以确保正确解码以任意编码形式编码为字节的字符流。在读取或识别完整帧之前可以转换字符串。
【解决方案2】:

您完全忽略了 EndReceive 结果,它会告诉您收到了多少字节。

像这样更改您的 EndReceive:

protected void EndReceive(IAsyncResult async)
{
    string msg = "";

    try 
    { 
        int received = SimNetSocket.EndReceive(async);
        var tmpArr = new byte[received];
        Buffer.BlockCopy(ReadBuffer, 0, tmpArr, 0, received);
        msg = ByteArrayToString(tmpArr); 
        Debug.Log("RAW RECEIVE: " + msg);
        MessageBuffer += msg;
        BeginReceive();
    }
    catch (Exception e) { Debug.LogError(e); }
}

有一些优化要做,但我无法编写它们,因为我没有完整的代码:

-修改ByteArrayToString以避免创建临时数组。

-如果执行SimNetSocket.EndReceive(async)时抛出异常,则表示连接已关闭,最好处理一下。

-请注意,您正在将接收到的数据连接到 MessageBuffer 上,您有责任在使用数据时清空此变量。

-您没有考虑读取碎片命令的可能性(至少在您提供的代码中没有)。

【讨论】:

    【解决方案3】:

    目前尚不清楚您期望发生什么。 Winsock API 中的任何内容以及该 API 上的薄 .NET 层都不会“清除”您提供的任何缓冲区。 API 所做的只是将读取操作中的字节复制到缓冲区中,或者将写入操作中的字节复制到缓冲区中。

    查看您的EndReceive() 回调,您似乎确实误解了其中的某些方面。在完成读取操作之前(通过调用 EndReceive()),您正在处理 ReadBuffer 内容,并且您没有做任何事情来考虑接收到的实际字节数。如果没有一个好的 Minimal, Complete, and Verifiable code example 开头,就不可能确定您的代码应该做什么,但是您的方法的更好实现应该是这样的:

    protected void EndReceive(IAsyncResult async)
    {
        try
        {
            int byteCount = SimNetSocket.EndReceive(async);
    
            // For example (you didn't share ByteArrayToString(), so it's not clear
            // what encoding you're using, or if you're even processing the bytes
            // correctly. Feel free to modify as needed...just make sure you take
            // into account the byteCount value!
            string msg = Encoding.ASCII.GetString(ReadBuffer, 0, byteCount);
    
            Debug.Log("RAW RECEIVE: " + msg);
            MessageBuffer += msg;
    
            // There is no need to allocate a new buffer. Just reuse the one you had
            BeginReceive();
        }
        catch (IOException e)
        {
            // Don't catch all exceptions. Only exceptions that should be expected
            // here would be IOException. Other unexpected exceptions should be left
            // unhandled
            Debug.LogError(e);
            // You should close the socket here. Don't try to use that connection again
        }
    
    }
    

    请注意,您实际上可以处理 ASCII 以外的编码,而不必担心部分读取。为此,您必须跟踪从一个读取操作到下一个读取操作的字符解码状态。最简单的方法是使用 Decoder 对象,它有一个内部缓冲区,可以保存部分字符,直到您执行下一次解码操作。

    【讨论】:

    • 感谢您的回复!我接受了这个作为答案,因为我觉得它指出了真正的问题是我误解了电话的预期工作方式——这就是我首先提出这个问题的原因。其他回复有一些非常好的阅读参考。
    【解决方案4】:

    我使用它来刷新(清除)Windows 和 C# 中的接收缓冲区(在我的情况下最大 2KB):

        public void FlushReceiveBuffer()
        {
            byte[] info = new byte[2];
            byte[] outval = new byte[2000];
            try
            {
                mySock.IOControl(IOControlCode.DataToRead, info, outval);
                uint bytesAvailable = BitConverter.ToUInt32(outval, 0);
                if (bytesAvailable != 0 && bytesAvailable < 2000)
                {
                    int len = mySock.Receive(outval); //Flush buffer
                }
            }
            catch
            {
                //Ignore errors
                return;
            }
        }
    

    【讨论】:

      猜你喜欢
      • 2013-05-17
      • 1970-01-01
      • 2014-05-10
      • 1970-01-01
      • 1970-01-01
      • 2020-03-30
      • 2023-03-25
      • 2012-10-07
      • 2020-04-11
      相关资源
      最近更新 更多