Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Socket: What if BeginSend is called before BeginReceive?

Tags:

c#

sockets

I got a TCP ping/pong working, but after trying to add a second data type, I run into a situation where the receiving program never fires the BeginReceive callback.

The code is identical, except that with the second (problematic) data type, serialization is done ahead of time. This leads me to believe that the problem may lie in that BeginSend is called sooner with the second data type, perhaps before BeginReceive is called in the other program.

Alternatively, I wonder if the difference in data size.

So my question for SO is: Is it possible that send before receive is the problem? If so, how does one handle that? If not, what else could be going wrong?

Common

enum DataType : byte { A, B }

Data Receiver

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();
}

Data Sender

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();
}

If I disable _needToUpdateBData, so that only A Data is ever sent, the two parts go back and forth swimmingly. If _needToUpdateBData is set to true, the sender will get stuck on "Sent B data, awaiting response", and the receiver will get stuck on "Receiving B Data", meaning the OnReciveData callback is never fired.

Even after setting Socket.ReceiveTimeout to 1000ms, its left hanging on "Receiving B Data" indefinitely.

Note that try/catches are missing because I intended to get this back and forth working before complicating things with reconnection logic (actually, several have been removed to make this post more concise).

like image 548
M-Pixel Avatar asked Dec 13 '25 21:12

M-Pixel


1 Answers

Is it possible that send before receive is the problem?

No, one side of a TCP connection can never know for sure that the other one has a pending read. Even if the remote side wants to issue a read its threads might be descheduled for a long time. TCP must be able to work without this guarantee. This cannot possibly be a concern.

var bytes = _socket.EndReceive(result);
Debug.Log("Received " + bytes + "bytes of " + _expectedDataType + " Data");

// ... Deserialize several variables from the buffer based on data type ...

This looks like the classic bug where you assume that a single read call will return all the data you want to have. Instead, it can return a partial result. You cannot possibly deserialize with a single read call so I assume this is a case of this classic bug.

The timeouts do not work for async IO (I don't know why, it's terrible). Probably, you should not be using async IO anyway. I can tell because you are accepting sockets asynchronously with is a pure waste of dev time. That makes me think that you have not consciously made the decision between sync and async IO.

if (_socket.Connected)

How could this be false right after connecting? Makes no sense. Do not copy code from other source without understanding what it does. This is a common anti-pattern.

Sockets are really hard to get right. The best choice usually is to use something ready-made such as WCF, HTTP, websockets etc.

like image 143
usr Avatar answered Dec 15 '25 09:12

usr



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!