You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET控制台WebSocket服务器未接收客户端全部消息问题排查

嘿,我帮你找出消息丢失的核心问题了!主要是你的WebSocket服务器在处理TCP流和WebSocket消息的逻辑上有几个关键漏洞,导致快速发送的消息大量丢失,具体原因和修复方法如下:

为啥消息会丢?

1. 没处理「一次收到多条消息」的情况

你客户端一口气发1000条消息,TCP为了省流量,会把好多小消息打包成一个TCP段发给服务器。但你的服务器每次读取完当前可用的字节后,只解析第一条WebSocket消息,剩下的消息字节直接就丢了——因为下一次循环又重新读取新数据,根本没管上次剩下的内容。

举个例子:服务器一次读了3条消息的字节,结果只处理第一条,剩下两条的字节数据直接被抛弃,自然就丢了。

2. 依赖client.Available读数据不靠谱

你用client.Available来创建字节数组然后读取数据,但这个值只是当前瞬间可用的字节数,TCP流是连续的,Read方法实际读到的字节数可能比这个少(比如数据还在网络路上没到全)。这就可能导致部分数据没被正确读取,或者后续新数据覆盖了之前没处理的旧数据。

3. 没保留未处理的剩余字节

每次处理完一条消息,如果当前读取的字节里还有属于下一条消息的剩余数据,你的代码没把这些数据存下来,直接进入下一轮循环重新读,这部分数据就直接丢失了。


怎么修复?

核心思路是维护一个全局缓冲区,把所有读到的字节都存起来,然后循环从缓冲区里解析完整的WebSocket消息,直到缓冲区里的字节不够解析下一条为止。

第一步:加个缓冲区存未处理数据

在Main函数开头声明一个列表当缓冲区:

List<byte> messageBuffer = new List<byte>();

第二步:把读到的数据都追加到缓冲区

替换原来的读取逻辑,别直接处理单次读取的字节,先存到缓冲区里:

// 替换原来的bytes数组读取代码
byte[] tempBuffer = new byte[1024];
int bytesRead = stream.Read(tempBuffer, 0, tempBuffer.Length);
if (bytesRead > 0)
{
    messageBuffer.AddRange(tempBuffer.Take(bytesRead));
}

第三步:循环解析缓冲区里的消息

只要缓冲区里的字节够解析一条消息的头部,就尝试解析,直到不够为止:

// 循环解析缓冲区中的完整消息
while (messageBuffer.Count >= 2) // WebSocket消息至少有2字节头部
{
    bool isFinalFrame = (messageBuffer[0] & 0b10000000) != 0;
    int opcode = messageBuffer[0] & 0b00001111;
    bool hasMask = (messageBuffer[1] & 0b10000000) != 0;
    int msgLength = messageBuffer[1] & 0b01111111;
    int offset = 2;

    // 处理不同长度的消息
    if (msgLength == 126)
    {
        if (messageBuffer.Count < 4) break; // 长度字节不够,等更多数据
        msgLength = BitConverter.ToUInt16(new byte[] { messageBuffer[3], messageBuffer[2] }, 0);
        offset = 4;
    }
    else if (msgLength == 127)
    {
        if (messageBuffer.Count < 10) break; // 64位长度需要更多字节,等一等
        // 注意RFC 6455规定长度是大端字节序,所以要反转
        msgLength = (int)BitConverter.ToUInt64(messageBuffer.Skip(2).Take(8).Reverse().ToArray(), 0);
        offset = 10;
    }

    // 检查缓冲区里的字节是否足够包含掩码和消息内容
    int totalNeededBytes = offset + (hasMask ? 4 : 0) + msgLength;
    if (messageBuffer.Count < totalNeededBytes) break; // 数据不全,等后续数据

    // 解码消息内容
    if (hasMask)
    {
        byte[] maskKey = messageBuffer.Skip(offset).Take(4).ToArray();
        offset += 4;
        byte[] decodedBytes = new byte[msgLength];
        for (int i = 0; i < msgLength; i++)
        {
            decodedBytes[i] = (byte)(messageBuffer[offset + i] ^ maskKey[i % 4]);
        }
        string receivedMsg = Encoding.UTF8.GetString(decodedBytes);
        Console.WriteLine(receivedMsg);
    }
    else
    {
        // 按照RFC 6455,客户端发的消息必须带掩码,这里可以打个日志
        Console.WriteLine("警告:收到不带掩码的客户端消息,违反协议规范");
    }

    // 移除已经处理完的字节,剩下的留着下次解析
    messageBuffer.RemoveRange(0, totalNeededBytes);
}

第四步:调整主循环逻辑

把原来处理消息的分支替换成上面的缓冲区解析逻辑,确保所有读到的数据都被累积和处理,不会漏掉。


额外提醒

  • TCP是面向流的,没有消息边界,必须靠WebSocket的帧结构来拆分消息,不能想当然认为一次读取就是一条消息。
  • 要符合RFC 6455规范:客户端发的消息必须带掩码,服务器发的消息不能带掩码,别搞反了。
  • 如果要处理超大量消息,可以调整缓冲区的初始大小,减少内存分配的开销。

内容的提问来源于stack exchange,提问作者millie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:38:10