美文网首页网络传输
Unity中使用ProtoBuff3.0,与netty服务器通信

Unity中使用ProtoBuff3.0,与netty服务器通信

作者: 极限小黑 | 来源:发表于2017-06-29 15:07 被阅读0次

    3. 建立与服务端通信连接,使用protobuff编码解码

    废话少说,先上代码,注释的也比较清晰了

    using Google.Protobuf;
    using Google.Protobuf.Examples.AddressBook;
    using Google.Protobuf.WellKnownTypes;
    using System;
    using System.Net.Sockets;
    using UnityEngine;
    
    public class NewBehaviourScript : MonoBehaviour {
    
        // Use this for initialization
        void Start () {
    
            StartConnect();
        }
    
        TcpClient tcpClient;                // 
        byte[] receive_buff;                // 专门用来接收Socket里面的数据的
        byte[] data_buff;                   // 用来存当前未处理的数据
    
        CodedOutputStream outputStream;     // 用来绑定SocketStream,方便把proto对象转换成字节流Stream输送给服务器
    
        void StartConnect()
        {
            TcpClient client = new TcpClient();
            tcpClient = client;
     
            //这里写上你自己服务器的ip和端口
            client.Connect("192.168.1.1", 8800);
            
            receive_buff = new byte[client.ReceiveBufferSize];
    
            outputStream = new CodedOutputStream(client.GetStream());
    
            // 监听一波服务器消息
            client.GetStream().BeginRead(receive_buff, 0, client.ReceiveBufferSize, ReceiveMessage, null);
        }
    
        int nFalg = 0;        // 这个变量主要是为了防止和服务端无休无止互发消息,测试代码
        void Update () {
    
            // 因为ReceiveMessage接收数据是异步的方式,不是在主线程,有些方法不能用,比如ToString,所以消息处理放在这里处理
            // 但主要是因为后面要加上消息广播,可以添加在这里
            if (data_buff != null && ++nFalg < 5)
            {
                // 把数据传给CodedInputStream计算本次包的长度
                CodedInputStream inputStream = new CodedInputStream(data_buff);
                int length = inputStream.ReadLength();
                // 计算"包长度"占用的字节数,后面取数据的时候扣掉这个字节数,就是真实数据长度
                int lengthLength = CodedOutputStream.ComputeLengthSize(length);
    
                // 当前数据足够解析一个包了
                if (length + lengthLength <= data_buff.Length)
                {
                    byte[] real_data = new byte[length];
                    // 拷贝真实数据
                    Array.Copy(data_buff, lengthLength, real_data, 0, length);
    
                    // 假设服务器给你发了个AddressBook
                    AddressBook ab = AddressBook.Parser.ParseFrom(real_data);
    
                    // 把这个数据直接还给服务器,验证客户端发给服务器的情况
                    SendMsg(ab);
    
                    // 数据刚刚好,没有多余的
                    if (length + lengthLength == data_buff.Length)
                    {
                        data_buff = null;
                    }
                    else
                    {
                        // 数据有剩余,保存剩余数据,等下一个Update解析
                        byte[] t = new byte[data_buff.Length - length - lengthLength];
                        Array.Copy(data_buff, lengthLength + length, t, 0, t.Length);
                        data_buff = t;
                    }
                }
            }
        }
    
        // 发送数据
        public void SendMsg(IMessage message)
        {
            if (outputStream != null)
            {
                // WriteMessage 里面会先write一个长度,然后再write真实数据
                outputStream.WriteMessage(message);
                outputStream.Flush();       // 把buffer数据写入到tcpClient的流里面
            }
        }
    
        public void ReceiveMessage(IAsyncResult ar)
        {
            try
            {
                // 本次接收到的数据长度
                int bytesRead = tcpClient.GetStream().EndRead(ar);
                if (bytesRead < 1)
                {
                    Debug.LogError("bytesRead < 1");
                    return;
                }
                else
                {
                    if (data_buff == null)
                    {
                        // buff里面没有数据
                        data_buff = new byte[bytesRead];
                        Array.Copy(receive_buff, data_buff, bytesRead);
                    }
                    else
                    {
                        // buff里面有数据,要和新数据整合起来
                        byte[] new_data = new byte[bytesRead + data_buff.Length];
                        Array.Copy(data_buff, new_data, data_buff.Length);
    
                        Array.Copy(receive_buff, 0, new_data, data_buff.Length, bytesRead);
    
                        data_buff = new_data;
                    }
                }
    
                // 继续监听下一波数据
                tcpClient.GetStream().BeginRead(receive_buff, 0, tcpClient.ReceiveBufferSize, ReceiveMessage, null);
            }
            catch (Exception ex)
            {
                // 为了防止报ex没被使用的警告
                Debug.Log(ex);
            }
        }
    }
    

    4. 处理与Netty服务器通信的粘包、拆包

    服务器的粘包拆包是Netty本身支持的解码编码器,如下图


    服务器粘包、拆包处理方式

    总共四行,其中第一行作用在拆包的时候,第三行作用在粘包的时候(我猜的)。
    它这个拆包粘包不是普通的那种固定4个字节标示长度的,而是有时候1个字节,有时候是2、3、4、5个字节,根据当前发送的真实数据的长度定的。

    在普通的方案粘包方案,数据是这样的:4个字节+真实数据
    有的是用换行回车作为标识符拆包、粘包

    那在Netty的方案里,包长度究竟是几个字节呢?
    其实它也是用到了Protobuff里面的数据读取、保存方式,感兴趣的可以打开protobuf3-for-unity-3.0.0\src\Google.Protobuf.sln工程看一下,在Google.Protobuf项目中,打开CodedInputStream.cs

    SlowReadRawVarint32
    包头占用几个字节是由下面这个函数计算的:
    这个是计算一个uint数据的真实长度的方法
    这也是protobuff对象编码后数据会比较小的主要原因。比如一个对象编码后得到的是440个字节数据,那么调用ComputeRawVarint32Size(440)的返回值是2,也就是服务器和客户端发送的数据最终长度是440+2=442个字节。明白了这些,拆包和粘包就都不是问题了。

    上面的代码里,粘包是这一段:

    public void SendMsg(IMessage message)
        {
            if (outputStream != null)
            {
                // WriteMessage 里面会先write一个长度,然后再write真实数据
                outputStream.WriteMessage(message);
                outputStream.Flush();       // 把buffer数据写入到tcpClient的流里面
            }
        }
    

    乍一看,好像没有在真实数据前面加长度啊?其实,在outputStream的WriteMessage里面已经有WriteLength了,帮我们做好了。

    image.png

    再看拆包:

                // 把数据传给CodedInputStream计算本次包的长度
                CodedInputStream inputStream = new CodedInputStream(data_buff);
                int length = inputStream.ReadLength();
                // 计算"包长度"占用的字节数,后面取数据的时候扣掉这个字节数,就是真实数据长度
                int lengthLength = CodedOutputStream.ComputeLengthSize(length);
    
                // 当前数据足够解析一个包了
                if (length + lengthLength <= data_buff.Length)
                {
                    byte[] real_data = new byte[length];
                    // 拷贝真实数据
                    Array.Copy(data_buff, lengthLength, real_data, 0, length);
    
                    // 假设服务器给你发了个AddressBook
                    AddressBook ab = AddressBook.Parser.ParseFrom(real_data);
                    ...
                }
    

    先用CodedInputStream 看看这个“包大小”值是多少,再用CodedOutputStream.ComputeLengthSize计算这个“包大小”占几个字节,然后就明白真实数据从哪里开始,占多少字节了。

    结束语

    测试并不是非常非常充分,仅供参考。

    参考:http://blog.csdn.net/u010841296/article/details/50957471?locationNum=2&fps=1

    相关文章

      网友评论

        本文标题:Unity中使用ProtoBuff3.0,与netty服务器通信

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