我正在编写一个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 :(得分:2)
仅仅因为您要求读取ReadBuffer.Length
个字节并不意味着实际上填充缓冲区的字节数。您需要保留从int
返回的EndReceive
并且只读取缓冲区中的#个字节。
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 :(得分: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); }
}
有一些优化要做,但我不能写它们,因为我没有完整的代码:
-Modify ByteArrayToString
以避免创建临时数组。
- 如果执行SimNetSocket.EndReceive(async)
时抛出异常,则意味着连接已关闭,处理案例是个好主意。
- 请注意,您正在连接MessageBuffer上的接收数据,这是您在使用数据时清空此变量的责任。
- 你没有考虑读取碎片命令的可能性(至少不在你提供的代码中)。
答案 2 :(得分:1)
目前尚不清楚您预计会发生什么。 Winsock API中的任何内容,以及该API上的瘦.NET层都不会清除"清除"您提供的任何缓冲区。所有API都是将读操作中的字节复制到缓冲区,或者从缓冲区复制字节以进行写操作。
看看你的EndReceive()
回调,你似乎误解了这方面的某些方面。您甚至在完成读取操作(通过调用ReadBuffer
)之前处理EndReceive()
内容,并且您没有做任何事情来考虑接收的实际字节数。如果没有一个好的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
对象,它有一个内部缓冲区,可以保留部分字符,直到你执行下一个解码操作。
答案 3 :(得分:0)
我用它来刷新(清除)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;
}
}