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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,387评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,845评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 110,091评论 0 246
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,308评论 0 214
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,662评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,795评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,008评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,743评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,466评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,687评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,181评论 1 262
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,531评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,177评论 3 239
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,126评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,902评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,862评论 2 283
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,734评论 2 274

推荐阅读更多精彩内容