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

C#异步Socket BeginSend/BeginReceive数据接收不完整问题求助

C#异步Socket聊天应用:List命令数据接收不完整问题

问题概述

使用C#异步Socket开发聊天应用时,客户端发送List命令查询在线用户列表,服务器返回的数据存在接收不完整的情况,转换为字符串时触发空值类异常。对比收发双方的字节数据,发现内容存在明显错位与截断。

服务器代码片段

case Data.Command.List:
    //Send the names of all users in the chat room to the new user
    msgToSend.cmdCommand = Data.Command.List;
    msgToSend.strMessage = null;
    msgToSend.strName = null;

    //Collect the names of the user in the chat room
    foreach (Cliente client in clientes)
    {
        //To keep things simple we use asterisk as the marker to separate the user names
        msgToSend.strMessage += client.strName + "*";
    }
    // DEBUG: The server is setting the message correctly
    Console.WriteLine(msgToSend.cmdCommand);
    Console.WriteLine(msgToSend.strMessage);
    Console.WriteLine(msgToSend.strName);

    message = msgToSend.ToByte();

    // DBEUG: Printing on the screen the array of data that is being sent to the client.
    foreach (var item in message)
    {
        Console.WriteLine(item);
    }

    //Send the name of the users in the chat room
    clientSocket.BeginSend(message, 0, message.Length, SocketFlags.None,
            new AsyncCallback(OnSend), clientSocket);
    break;

收发数据对比

服务器发送的字节数据

3
0
0
0
0
0
0
0
9
0
0
0
77
101
117
85
115
101
114
49
42

总长度21字节,对应:命令(3)、name长度(0)、消息长度(9)、消息内容(MeuUser1*的UTF8字节)。

客户端接收的字节数据

3
0
0
0
8
0
0
0
0
0
0
0
77
101
117
85
115
101
114
49

总长度19字节,出现数据错位:name长度被解析为8,消息长度为0,仅收到部分消息内容。

客户端数据解析代码

public Data(byte[] data)
{
    //The first four bytes are for the Command
    this.cmdCommand = (Command)BitConverter.ToInt32(data, 0);

    //The next four store the length of the name
    int nameLen = BitConverter.ToInt32(data, 4);

    //The next four store the length of the message
    int msgLen = BitConverter.ToInt32(data, 8);

    //This check makes sure that strName has been passed in the array of bytes
    if (nameLen > 0)
        this.strName = Encoding.UTF8.GetString(data, 12, nameLen);
    else
        this.strName = null;

    //This checks for a null message field
    if (msgLen > 0)
        this.strMessage = Encoding.UTF8.GetString(data, 12 + nameLen, msgLen);
    else
        this.strMessage = null;
}

问题原因与解决方案

核心原因

异步Socket基于流式传输,BeginReceive无法保证一次接收完整的数据包。当前客户端未处理拆包/粘包问题,仅尝试用单次接收的字节数组解析完整数据,导致数据截断或错位。

解决方案

1. 客户端实现分阶段接收逻辑

必须先接收数据包头部(前12字节:命令+name长度+消息长度),计算出总数据包长度后,循环接收直到获取完整数据:

private byte[] _receiveBuffer = new byte[0];

private void OnReceive(IAsyncResult ar)
{
    Socket client = (Socket)ar.AsyncState;
    int bytesRead = client.EndReceive(ar);
    
    if (bytesRead <= 0) 
    {
        // 处理连接断开逻辑
        client.BeginReceive(new byte[1024], 0, 1024, SocketFlags.None, OnReceive, client);
        return;
    }

    // 将新收到的数据追加到全局缓冲区
    byte[] tempBuffer = new byte[_receiveBuffer.Length + bytesRead];
    Buffer.BlockCopy(_receiveBuffer, 0, tempBuffer, 0, _receiveBuffer.Length);
    Buffer.BlockCopy(client.ReceiveBuffer, 0, tempBuffer, _receiveBuffer.Length, bytesRead);
    _receiveBuffer = tempBuffer;

    // 循环解析所有完整的数据包
    while (_receiveBuffer.Length >= 12)
    {
        int nameLen = BitConverter.ToInt32(_receiveBuffer, 4);
        int msgLen = BitConverter.ToInt32(_receiveBuffer, 8);
        int totalPacketLength = 12 + nameLen + msgLen;

        if (_receiveBuffer.Length >= totalPacketLength)
        {
            // 提取完整数据包并解析
            byte[] completePacket = new byte[totalPacketLength];
            Buffer.BlockCopy(_receiveBuffer, 0, completePacket, 0, totalPacketLength);
            Data data = new Data(completePacket);
            
            // 处理解析后的数据(如更新在线用户列表)
            ProcessReceivedData(data);

            // 更新缓冲区,移除已处理的数据包
            byte[] remainingBuffer = new byte[_receiveBuffer.Length - totalPacketLength];
            Buffer.BlockCopy(_receiveBuffer, totalPacketLength, remainingBuffer, 0, remainingBuffer.Length);
            _receiveBuffer = remainingBuffer;
        }
        else
        {
            // 未收到完整数据包,退出循环等待下一次接收
            break;
        }
    }

    // 继续监听下一次接收
    client.BeginReceive(new byte[1024], 0, 1024, SocketFlags.None, OnReceive, client);
}

2. 验证服务器序列化方法正确性

确保msgToSend.ToByte()方法正确计算字节长度(而非字符数),示例正确实现:

public byte[] ToByte()
{
    List<byte> dataBytes = new List<byte>();
    
    // 写入命令
    dataBytes.AddRange(BitConverter.GetBytes((int)cmdCommand));
    
    // 写入名称的字节长度与内容
    byte[] nameBytes = strName != null ? Encoding.UTF8.GetBytes(strName) : Array.Empty<byte>();
    dataBytes.AddRange(BitConverter.GetBytes(nameBytes.Length));
    if (nameBytes.Length > 0) dataBytes.AddRange(nameBytes);
    
    // 写入消息的字节长度与内容
    byte[] msgBytes = strMessage != null ? Encoding.UTF8.GetBytes(strMessage) : Array.Empty<byte>();
    dataBytes.AddRange(BitConverter.GetBytes(msgBytes.Length));
    if (msgBytes.Length > 0) dataBytes.AddRange(msgBytes);

    return dataBytes.ToArray();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 17:20:36