Choosing a buffer size for a WebSocket response

Migwell picture Migwell · Jan 28, 2017 · Viewed 12.8k times · Source

I'm writing a C# application that connects to a websocket server, and receives a JSON response of unknown size. I'm using the ClientWebSocket class for this purpose.

The only way to receive data from the client seems to be with the ReceiveAsync method, which takes an ArraySegment<byte> as an argument:

var client = new ClientWebSocket();
client.ConnectAsync(new Uri($"ws://localhost:{port}"), token).Wait();
var result = new ArraySegment<byte>(new byte[1000]);
client.ReceiveAsync(result, token).Wait();

The problem is, since I don't know how big the JSON response will be, I don't know how big to make the buffer backing that ArraySegment. In this case I've specified 1000 bytes, which is far too small, and the response is truncated. However I'm worried that if I set the buffer size to be arbitrarily large, (1,000,000 bytes?), I'll be using up more memory than I need.

How do I choose a buffer size without knowing the size of the response?

Answer

Matthias247 picture Matthias247 · Jan 29, 2017

If I understand the API correctly it will give you the websocket message in multiple parts if necessary.

That means if the message sent from the server is 2048 bytes and you use a 1000 bytes buffer and do:

var buffer = new ArraySegment<byte>(new byte[1000]);
var result = await client.ReceiveAsync(buffer, token);

Then in this first call result.Count will be set to 1000 and result.EndOfMessage will be set to false. This means you need to continue reading until EndOfMessage is set to true, which means 3 reads for this example.

If you need everything in one buffer and can't live with processing the message fragments individually you could start with a small buffer and resize the receive buffer if the result tells you that more data is coming in. Thereby you can also check that if a maximum size is exceeded the reception is stopped.

int bufferSize = 1000;
var buffer = new byte[bufferSize];
var offset = 0;
var free = buffer.Length;
while (true)
{
    var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer, offset, free), token);
    offset += result.Count;
    free -= result.Count;
    if (result.EndOfMessage) break;
    if (free == 0)
    {
        // No free space
        // Resize the outgoing buffer
        var newSize = buffer.Length + bufferSize;
        // Check if the new size exceeds a limit
        // It should suit the data it receives
        // This limit however has a max value of 2 billion bytes (2 GB)
        if (newSize > maxFrameSize)
        {
            throw new Exception ("Maximum size exceeded");
        }
        var newBuffer = new byte[newSize];
        Array.Copy(buffer, 0, newBuffer, 0, offset);
        buffer = newBuffer;
        free = buffer.Length - offset;
    }
}

And of course you should also check the other fields in the receive result, like MessageType.