Lanefox
Lanefox

Reputation: 11

C# How to manually clear TCP socket buffer?

I'm writing a TCP server to open a port and talk to some hardware that just sends string data as a byte array.

The environment is in Unity so I went with async callbacks to avoid blocking the program. This seems to work okay, connections are valid, the correct data comes across, I can send messages to the hardware, but the socket buffer never clears. Data just stacks up and does not empty out when I do Socket.EndReceive(ar).

How does the async loop work? I don't understand why this code is not completing the loop and clearing the buffer. I've spent quite a bit of time trying to understand the process and can't figure out why this code shouldn't work.

    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 is a stack which is cleared later in the Update loop where the messages are processed and unrelated to the problem of the ReadBuffer compounding on the socket.

Again, connections are valid (code not shown) and communication is definitely working. I can see data successfully coming and going from both sides but I don't have any control over what the other end hardware is doing. Does it require some implementation to receive these calls and confirm the buffer can clear?

What I actually see is the hardware sending a message, then later another message which gets stacked on it's last message, again, and again, and again. I'm processing every message through the code above, though. So I'm rather confused.

Upvotes: 1

Views: 19234

Answers (4)

Bengt S
Bengt S

Reputation: 1

I use this to flush (clear) a receive buffer in Windows and C# (max 2KB in my case):

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

Upvotes: 0

Peter Duniho
Peter Duniho

Reputation: 70671

It is not clear what you expect to happen. Nothing in the Winsock API, nor the thin .NET layer over that API, would "clear" any buffer you provide it. All the API does is copy bytes from read operations into a buffer, or copy bytes for write operations from a buffer.

Looking at your EndReceive() callback, you do seem to have misunderstood some aspects of this. You are processing the ReadBuffer contents before you have even concluded the read operation (by calling EndReceive()), and you are not doing anything to take into account the actual number of bytes received. Without a good Minimal, Complete, and Verifiable code example to start with, it's impossible to know for sure what your code should be doing, but a better implementation of your method would look something like this:

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
    }

}

Note that you can in fact handle encodings other than ASCII, without having to worry about partial reads. To do that, you have to keep track of character decoding state from one read operation to the next. The easiest way to do that is to use a Decoder object, which has an internal buffer that will hold on to partial characters until you perform your next decode operation.

Upvotes: 0

Gusman
Gusman

Reputation: 15151

You are completely ignoring the EndReceive result, which will tell you how many bytes have you received.

Change your EndReceive like this:

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

There are some optimizations to do but I can't to write them as I don't have the full code:

-Modify ByteArrayToString to avoid the creation of the temporary array.

-If an exception is thrown when you execute SimNetSocket.EndReceive(async) it means the connection has closed, would be a good idea to handle the case.

-Beware that you are concatenating the received data on MessageBuffer, it's your responsability to empty this variable when you consume the data.

-You aren't contempling the possibility to read fragmented commands (at least not in the code you presented).

Upvotes: 2

Scott Chamberlain
Scott Chamberlain

Reputation: 127563

Just because you asked for ReadBuffer.Length bytes to be read does not mean that is actually how many bytes where filled in to the buffer. You need to keep the int returned from EndReceive and read only that # of bytes from the buffer.

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

Upvotes: 2

Related Questions