2017-06-12 61 views
1

我正在写一个TCP服务器来打开一个端口,并与某些刚刚将字符串数据作为字节数组发送的硬件通信。C#如何手动清除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复合在套接字上。

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

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

+0

EndReceive的问题已经被注意到;还包括:你使用的是什么文本编码?如果它不是ASCII码,那么你会遇到更大的问题 - 看到我对其中一个答案的评论 –

+0

调用构造函数是清除缓冲区:ReadBuffer = new byte [1024]; – jdweng

回答

1

目前尚不清楚你期望会发生什么。 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对象,该对象具有内部缓冲区,可以保留部分字符,直到执行下一个解码操作。

+0

感谢您的回复!我接受了这个答案,因为我觉得它指出真正的问题是我误解了这些电话会如何工作 - 这就是为什么我首先提出这个问题的原因。其他答复有一些非常好的参考资料供阅读。 – Lanefox

2

仅仅因为您要求的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(); 
} 
+1

打败我吧。作为OP的一个附注:还要注意,你**不能**认为你得到*整个字符*,除非你在说ASCII。在UTF8,UTF16等的情况下 - 您可以将单个字符拆分为多个读取,因此您将有两组损坏的数据 - 第一次读取结束时,第二次读取结​​束时一次。基本上,直到**之后,您才能将字符串转换为**您已完成“取景” –

+0

@MarcGravell是的,对于OP来说,这里是一篇非常不错的文章,详细了解构建https:/ /blog.stephencleary.com/2009/04/message-framing.html –

+0

我也愿意:http://blog.marcgravell.com/2013/02/how-many-ways-can-you-mess-up -i.html –

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上接收到的数据连接起来,这是您在使用数据时清空此变量的责任。

- 你并不认为阅读碎片命令的可能性(至少不在你提供的代码中)。