C#Socket:如果在BeginReceive之前调用BeginSend怎么办?

时间:2015-11-29 20:26:53

标签: c# sockets

我有一个TCP ping / pong工作,但在尝试添加第二种数据类型后,我遇到接收程序永远不会触发BeginReceive回调的情况。

代码是相同的,除了使用第二个(有问题的)数据类型,序列化是提前完成的。这让我相信问题可能在于BeginSend在第二种数据类型中被更快地调用,可能在另一个程序中调用BeginReceive之前。

或者,我想知道数据大小是否有差异。

所以我对SO的问题是:收到之前发送是否有可能是问题?如果是这样,那怎么处理呢?如果没有,还有什么可能出错?

通用

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,因为我打算在使用重新连接逻辑复杂化之前来回执行此操作(实际上,已删除了一些以使此帖更简洁)。

1 个答案:

答案 0 :(得分:1)

  

接收之前发送是否可能是问题?

不,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 ...

这看起来像经典的错误,你假设一个读取调用将返回你想要的所有数据。相反,它可以返回部分结果。您不可能通过单个读取调用反序列化,因此我假设这是此经典错误的一种情况。

超时对异步IO不起作用(我不知道为什么,它很可怕)。也许你不应该使用异步IO。我可以告诉你,因为你异步接受套接字是对开发时间的纯粹浪费。这让我觉得你没有有意识地在同步和异步IO之间做出决定。

if (_socket.Connected)

连接后怎么会出现这种错误?没有意义。不要在不了解其功能的情况下从其他来源复制代码。这是一种常见的反模式。

套接字真的很难搞定。最好的选择通常是使用现成的东西,如WCF,HTTP,websockets等。