我有一个TCP乒乓,但在尝试添加第二种数据类型后,我遇到了接收程序从未触发BeginReceive
回调的情况。
代码是相同的,只是对于第二种(有问题的)数据类型,序列化提前完成。这让我相信,问题可能在于BeginSend
在第二种数据类型中被更快地调用,可能在其他程序中调用BeginReceive
之前。
或者,我想知道数据大小的差异。
所以我对So的问题是:是否可能是先发送后接收的问题?如果是,如何处理?如果没有,还有什么问题?
Common
enum DataType : byte { A, B }
数据接收器
TcpListener _listener = new TcpListener(IPAddress.Any, 2323);
DataType _expectedDataType;
Constructor() { _listener.Start(); }
Connect() { _listener.BeginAcceptSocket(OnConnect, null); }
OnConnect(IAsyncResult result)
{
_socket = _listener.EndAcceptSocket(result);
_socket.ReceiveTimeout = 1000;
_socket.SendTimeout = 1000;
_expectedDataType = DataType.A;
if (_socket.Connected)
ReceiveData();
else
Connect();
}
ReceiveData()
{
Debug.Log("Receiving " + _expectedDataType + " Data");
// EDIT: The problem lied in the fact that the length parameter is sometimes longer than the first length parameter that was ever used
_socket.BeginReceive(_dataBuffer[(int)_expectedDataType], 0, _dataBuffer[_expectedDataType].Length, SocketFlags.None, OnReceiveData, null);
// buffer array is 119 for A, 133 for B
}
OnReceiveData(IAsyncResult result)
{
var bytes = _socket.EndReceive(result);
Debug.Log("Received " + bytes + "bytes of " + _expectedDataType + " Data");
// ... Deserialize several variables from the buffer based on data type ...
if (_needToUpdateBData)
_expectedDataType = DataType.B;
else
_expectedDataType = DataType.A
_socket.BeginSend(new[]{(byte)_expectedDataType}, 0, 1, SocketFlags.None, OnSendRequest, null);
}
OnSendRequest(IAsyncResult result)
{
var bytes = _socket.EndSend(result);
Debug.Log("Sent " + _expectedDataType + " Request " + bytes + "bytes");
ReceiveData();
}
数据发送器
TcpClient _client = new TcpClient();
DataType _expectedDataType;
OnEnterIpAddress(string ipAddress) { _client.BeginConnect(IPAddress.Parse(ipAddress), 2323, OnClientConnect, null); }
OnClientConnect(IAsyncResult result)
{
_client.EndConnect(result);
_stream = _client.GetStream();
_expectedDataType = DataType.A;
SendData();
}
SendData()
{
// ... Serialize into buffer based on expectedDataType
// This is where A takes a long time, and B is nearly instant ...
var bytes = _buffer[(int)_expectedDataType].Length;
Debug.Log("Sending " + _expectedDataType + " data with length of " + bytes + "bytes");
_stream.BeginWrite(_buffer[(int)_expectedDataType], 0, bytes, OnWrite, null);
}
OnWrite(IAsyncCallback)
{
_stream.EndWrite(result);
Debug.Log("Sent " + _expectedDataType + " data, awaiting response");
_stream.BeginRead(_response, 0, 1, OnRead, null);
}
OnRead(IAsyncCallback)
{
var bytes = _stream.EndRead(result);
Debug.Log("Received " + bytes + "bytes of " + _expectedDataType + " data");
_expectedDataType = (DataType)_response[0];
SendData();
}
如果我禁用了_needToUpdateBData
,所以只发送A
数据,那么这两部分就可以顺利地来回切换。如果_needToUpdateBData
设置为true,则发送方将被困在"已发送B数据,等待响应",接收方将被卡在"正在接收B数据",这意味着OnReciveData
回调永远不会触发。
即使在将Socket.ReceiveTimeout设置为1000ms后,它仍然无限期地挂在"接收B数据"上。
请注意,缺少try/catches,因为我打算在使用重新连接逻辑使事情复杂化之前让它来回工作(实际上,为了使本文更加简洁,已经删除了几个)。
有没有可能是先发送后接收的问题?
不,TCP连接的一端永远无法确定另一端是否有挂起的读取。即使远程端想要发出读取,其线程也可能会被长时间取消调度。TCP必须能够在没有此保证的情况下工作。这不可能是一个令人担忧的问题。
var bytes = _socket.EndReceive(result);
Debug.Log("Received " + bytes + "bytes of " + _expectedDataType + " Data");
// ... Deserialize several variables from the buffer based on data type ...
这看起来像是一个经典的错误,您假设一个读取调用将返回您想要的所有数据。相反,它可以返回部分结果。你不可能用一个读调用来反序列化,所以我认为这是一个典型的bug。
超时不适用于异步IO(我不知道为什么,这太可怕了)。也许,无论如何都不应该使用异步IO。我可以告诉你,因为你异步接受套接字是纯粹浪费开发时间。这让我觉得你没有意识到在同步和异步IO之间做出决定
if (_socket.Connected)
连接后怎么会是假的?毫无意义。不要在不了解其他源代码的作用的情况下复制它。这是一种常见的反模式。
插座真的很难调对。最好的选择通常是使用现成的东西,如WCF、HTTP、websockets等。