美文网首页Unity网络通信征服Unity3d其他
Unity技术博客 - 基于ProtoBuf协议实现网络传输(下

Unity技术博客 - 基于ProtoBuf协议实现网络传输(下

作者: 肖马克_蛮牛 | 来源:发表于2016-01-28 09:56 被阅读1630次

    Unity版本: 5.3

    使用语言: C#


    写在前面

    ProtoBuf是Google公司推出的一种二进制序列化工具,适用于数据的网络传输。
    基于Socket实现时时通信,关于数据粘包的编码和解码处理是必不可少的。


    实现功能:

       1.基于ProtoBuf序列化对象
       2.使用Socket实现时时通信
       3.数据包的编码和解码
    

    3.数据包的编码和解码

    首先,举个例子,这个月信用卡被媳妇刷爆了,面对房贷车贷的压力,我只能选择分期付款。。。

    那么OK了,现在我想问一下,当服务器向客户端发送的数据过大时怎么办呢?

    当服务器需要向客户端发送一条很长的数据,也会“分期付款!”,服务器会把一条很长的数据分成若干条小数据,多次发送给客户端。

    可是,这样就又有另外一个问题,客户端接受到多条数据之后如何解析?

    这里其实就是客户端的解码。server发数据一般采用“长度+内容”的格式,Client接收到数据之后,先提取出长度来,然后根据长度判断内容是否发送完毕。

    再次重申,用户在发送序列化好的消息的前,需要先编码后再发送消息;用户在接受消息后,需要解码之后再解析数据(反序列化)。

    using UnityEngine;
    using System.Collections.Generic;
    using System.IO;
    
    /// <summary>
    /// 编码和解码
    /// </summary>
    public class NetEncode {
    
        /// <summary>
        /// 将数据编码 长度+内容
        /// </summary>
        /// <param name="data">内容</param>
        public static byte[] Encode(byte[] data)
        {
            //整形占四个字节,所以声明一个+4的数组
            byte[] result = new byte[data.Length + 4];
            //使用流将编码写二进制
            MemoryStream ms = new MemoryStream();
            BinaryWriter br = new BinaryWriter(ms);
            br.Write(data.Length);
            br.Write(data);
            //将流中的内容复制到数组中
            System.Buffer.BlockCopy(ms.ToArray(), 0, result, 0, (int)ms.Length);
            br.Close();
            ms.Close();
            return result;
        }
    
        /// <summary>
        /// 将数据解码
        /// </summary>
        /// <param name="cache">消息队列</param>
        public static byte[] Decode(ref List<byte> cache)
        {
            //首先要获取长度,整形4个字节,如果字节数不足4个字节
            if(cache.Count < 4)
            {
                return null;
            }
            //读取数据
            MemoryStream ms = new MemoryStream(cache.ToArray());
            BinaryReader br = new BinaryReader(ms);
            int len = br.ReadInt32();
            //根据长度,判断内容是否传递完毕
            if(len > ms.Length - ms.Position)
            {
                return null;
            }
            //获取数据
            byte[] result = br.ReadBytes(len);
            //清空消息池
            cache.Clear();
            //讲剩余没处理的消息存入消息池
            cache.AddRange(br.ReadBytes((int)ms.Length - (int)ms.Position));
    
            return result;
        }
    }
    

    用户接受数据代码如下:

    using System;
    using System.Collections.Generic;
    using System.Net.Sockets;
    
    /// <summary>
    /// 表示一个客户端
    /// </summary>
    public class NetUserToken {
        //连接客户端的Socket
        public Socket socket;
        //用于存放接收数据
        public byte[] buffer;
        //每次接受和发送数据的大小
        private const int size = 1024;
    
        //接收数据池
        private List<byte> receiveCache;
        private bool isReceiving;
        //发送数据池
        private Queue<byte[]> sendCache;
        private bool isSending;
    
        //接收到消息之后的回调
        public Action<NetModel> receiveCallBack;
        
    
        public NetUserToken()
        {
            buffer = new byte[size];
            receiveCache = new List<byte>();
            sendCache = new Queue<byte[]>();
        }
    
        /// <summary>
        /// 服务器接受客户端发送的消息
        /// </summary>
        /// <param name="data">Data.</param>
        public void Receive(byte[] data)
        {
            UnityEngine.Debug.Log("接收到数据");
            //将接收到的数据放入数据池中
            receiveCache.AddRange(data);
            //如果没在读数据
            if(!isReceiving)
            {
                isReceiving = true;
                ReadData();
            }
        }
    
        /// <summary>
        /// 读取数据
        /// </summary>
        private void ReadData()
        {
            byte[] data = NetEncode.Decode(ref receiveCache);
            //说明数据保存成功
            if(data != null)
            {
                NetModel item = NetSerilizer.DeSerialize(data);
                UnityEngine.Debug.Log(item.Message);
                if(receiveCallBack != null)
                {
                    receiveCallBack(item);
                }
                //尾递归,继续读取数据
                ReadData();
            }
            else
            {
                isReceiving = false;
            }
        }
    
        /// <summary>
        /// 服务器发送消息给客户端
        /// </summary>
        public void Send()
        {
            try {
                if (sendCache.Count == 0) {
                    isSending = false;
                    return; 
                }
                byte[] data = sendCache.Dequeue ();
                int count = data.Length / size;
                int len = size;
                for (int i = 0; i < count + 1; i++) {
                    if (i == count) {
                        len = data.Length - i * size;
                    }
                    socket.Send (data, i * size, len, SocketFlags.None);
                }
                UnityEngine.Debug.Log("发送成功!");
                Send ();
            } catch (Exception ex) {
                UnityEngine.Debug.Log(ex.ToString());
            }
        }
    
        public void WriteSendDate(byte[] data){
            sendCache.Enqueue(data);
            if(!isSending)
            {
                isSending = true;
                Send();
            }
        }
    }
    

    写在最后

     #成功的道路没有捷径,代码这条路更是如此,唯有敲才是王道。

    相关文章

      网友评论

      • 非凡_13d9:NetSerilizer.DeSerialize 请问这里报错要怎么解决
        zzzzzzsaid:看方法的属性,改为public static,就可以通过类名来调用了,且外部可以访问
      • d1af4d780298:为什么下面这个明明是客户端代码,里面的Receive函数却是服务器接受客户端发送的消息,不应该是客户端接受服务器端发送的消息吗?
        下面的Send也是服务器发送消息给客户端,不应该是客户端发送消息给服务器端吗?

      本文标题:Unity技术博客 - 基于ProtoBuf协议实现网络传输(下

      本文链接:https://www.haomeiwen.com/subject/qhqdkttx.html