为 WebSocket 响应选择缓冲区大小

Choosing a buffer size for a WebSocket response

我正在编写一个连接到 websocket 服务器的 C# 应用程序,并接收一个大小未知的 JSON 响应。为此,我正在使用 ClientWebSocket class。

从客户端接收数据的唯一方法似乎是使用 ReceiveAsync 方法,该方法采用 ArraySegment<byte> 作为参数:

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

问题是,因为我不知道 JSON 响应会有多大,所以我不知道要使支持 ArraySegment 的缓冲区有多大。在这种情况下,我指定了 1000 个字节,这太小了,并且响应被截断了。但是,我担心如果我将缓冲区大小设置为任意大(1,000,000 字节?),我将使用比我需要的更多的内存。

如何在不知道响应大小的情况下选择缓冲区大小?

如果我理解 API 正确,它会在必要时分多个部分为您提供 websocket 消息。

这意味着如果从服务器发送的消息是 2048 字节并且您使用 1000 字节的缓冲区并执行:

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

然后在第一次调用中 result.Count 将被设置为 1000,result.EndOfMessage 将被设置为 false。这意味着您需要继续阅读,直到 EndOfMessage 设置为 true,这意味着此示例中有 3 次阅读。

如果您需要一个缓冲区中的所有内容并且无法忍受单独处理消息片段,您可以从一个小缓冲区开始并在结果告诉您有更多数据传入时调整接收缓冲区的大小。这样您就可以还要检查是否超过最大大小,接收将停止。

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

当然你也应该检查接收结果中的其他字段,比如MessageType