Unity中使用ProtoBuff3.0,与netty服务器通信的粘包、拆包处理(二)

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

Unity中使用ProtoBuff3.0,与netty服务器通信的粘包、拆包处理(二)_第1张图片
SlowReadRawVarint32

包头占用几个字节是由下面这个函数计算的:
Unity中使用ProtoBuff3.0,与netty服务器通信的粘包、拆包处理(二)_第2张图片
这个是计算一个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了,帮我们做好了。

Unity中使用ProtoBuff3.0,与netty服务器通信的粘包、拆包处理(二)_第3张图片
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服务器通信的粘包、拆包处理(二))