粘包与拆包

本文详细解释了网络通信中粘包和拆包的概念,涉及如何通过消息边界、长度信息和分隔符进行数据包的正确拆解,以及服务器端和客户端如何按照特定格式发送和接收消息,包括使用编码UTF8和BitConverter工具处理数据包内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.粘包与拆包相关的概念:

粘包(Packet Pacing)

粘包是指发送方发送的多个小数据包被接收方一次性接收的情况。这可能导致接收方无法正确地区分和处理每个小数据包,从而造成数据处理错误。粘包通常发生在数据发送方迅速发送多个小数据包而接收方未能及时接收和处理的情况下。

拆包(Packet Splitting)

拆包是指发送方发送的一个大数据包被接收方拆分成多个小数据包的情况。这可能导致接收方无法正确地还原原始的大数据包,从而引起数据的不完整性。拆包通常发生在数据发送方发送的数据较大,而接收方缓冲区较小,不能容纳整个数据包的情况下。

消息边界

在数据包中包含消息边界信息,使接收方能够正确地分割和处理每个消息。

消息长度

在数据包中包含消息长度信息,接收方首先读取消息长度,然后根据长度读取相应字节数的数据。

分隔符

在数据包中使用特殊的分隔符标识消息的结束,接收方根据分隔符将数据包拆分成单独的消息。

消息的格式

包体大小四个字节,消息(协议)Id,包体数据(账号,密码,邮箱等等)
4字节:消息大小(描述消息体大小的协议)。
4字节:协议ID(消息类型的唯一标识符)。
size:消息体的大小,以字节为单位。

先将消息的包体数据求出来,根据包体大小(存储空间占4个字节),再求出消息的总长度 = 8 + 包体数据的长度(这里的值8用于考虑接收到的数据包中占用的额外空间,即协议ID(4字节)和消息大小(4字节)),再根据消息的ID对其进行处理,

private void Handle()
{
    //粘包指的是在网络通信中,多个小的数据包被合并成一个大的数据包发送到接收方的现象。
    //包体大小(4),协议ID(4)包体(byte[])
    if (msgLenth >= 8)//接受到的数据的长度
    {
        byte[] _size = new byte[4];//存放包体里里面的数据
        Array.Copy(data, 0, _size, 0, 4);
        //"包体大小为 4 字节"是在描述消息体大小所占用的存储空间的长度。
        int size = BitConverter.ToInt32(_size, 0);//用于将字节数组表示的整数值转换为一个32位有符号整数。
        //_size 数组中存储的字节数据
        //本次要拿的长度
        var _length = 8 + size;//消息的总长度
        if (msgLenth >= _length)
        {
            //拿出Id
            byte[] _id = new byte[4];//存放包体里里面的数据
            Array.Copy(data, 4, _id, 0, 4);
            //"包体大小为 4 字节"是在描述消息体大小所占用的存储空间的长度。
            int id = BitConverter.ToInt32(_id, 0);
            //包体
            byte[] body = new byte[size];
            Array.Copy(data, 8, body, 0, size);
            if (msgLenth >= _length)//里面还有数据没拿出来
            {
                for (int i = 0; i < msgLenth - _length; i++)
                {
                    data[i] = data[_length + i];
                }

            }
            msgLenth -= _length;
            Console.WriteLine($"收到客户端请求:{id}");
            switch(id)
            {
                case 1001://注册请求
                    RigisterMsgHandle(body);
                    break;
                case 1002://登录请求
                    LoginMsgHandle(body);
                    break;
                case 1003://聊天业务
                    ChatMsgHandle(body);
                    break;
            }
        }
    }
}

2.服务端以及客户端发送,接收消息都要按照格式来

 //发送消息给服务端
 public void SendToServer(int id, string str)
 {
     Debug.Log("ID:" + id);
     //转换成byte[]
     var body = Encoding.UTF8.GetBytes(str);
     //包体大小(4)消息Id(4)包体内容

     byte[] send_buff = new byte[body.Length + 8];

     int size = body.Length;
     //可以在网络上传输和接收数据,而接收端可以根据协议规定的格式将字节数组转换回整数值来获取原始数据。
     var _size = BitConverter.GetBytes(size);
     //将整数值转换为字节数组
     var _id = BitConverter.GetBytes(id);

     Array.Copy(_size, 0, send_buff, 0, 4);
     //从 _size 数组中复制了 4 个元素,从索引 0 开始,然后将它们粘贴到 send_buff 数组中,从索引 0 开始。
     Array.Copy(_id, 0, send_buff, 4, 4);
     Array.Copy(body, 0, send_buff, 8, body.Length);
     Client.Instance.Send(send_buff);//发送给服务端
 }
 //按格式封装后发送消息
 public void SendToClient(int id, string str)
 {
     //转换成byte[]
     var body = Encoding.UTF8.GetBytes(str);
     //包体大小(4)消息Id(4)包体内容

     byte[] send_buff = new byte[body.Length + 8];

     int size = body.Length;
     //可以在网络上传输和接收数据,而接收端可以根据协议规定的格式将字节数组转换回整数值来获取原始数据。
     var _size = BitConverter.GetBytes(size);
     //将整数值转换为字节数组
     var _id = BitConverter.GetBytes(id);

     Array.Copy(_size, 0, send_buff, 0, 4);
     //从 _size 数组中复制了 4 个元素,从索引 0 开始,然后将它们粘贴到 send_buff 数组中,从索引 0 开始。
     Array.Copy(_id, 0, send_buff, 4, 4);
     Array.Copy(body, 0, send_buff, 8, body.Length);
     Send(send_buff);//发送给客户端
 }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值