16.网络通信中反序列化2进制数据

16.网络通信-通信必备知识-序列化和反序列化2进制数据-反序列化


16.1 知识点

字节数组转非字符串类型

  • 关键类:BitConverter
  • 所在命名空间:System
  • 主要作用:除字符串的其它常用类型和字节数组相互转换
byte[] byteArray1 = BitConverter.GetBytes(99);
int i = BitConverter.ToInt32(byteArray1, 0); // 第二个参数传从字节数组第几个字节开始转换 
print(i); // 99

字节数组转字符串类型

  • 关键类:Encoding
  • 所在命名空间:System.Text
  • 主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议大家使用UTF-8类型
byte[] byteArray2 = Encoding.UTF8.GetBytes("人间自有韬哥在");
string str = Encoding.UTF8.GetString(byteArray2, 0, byteArray2.Length); // 第二个参数传从字节数组第几个字节开始转换 第三个参数是长度
print(str); // 人间自有韬哥在

如何将二进制数据转为一个类对象

获取到对应的字节数组

PlayerInfo playerInfo1 = new PlayerInfo();
playerInfo1.lev = 10;
playerInfo1.name = "韬老狮";
playerInfo1.atk = 88;
playerInfo1.sex = false;

byte[] playerInfoBytes1 = playerInfo1.GetBytes(); // 使用上次封装好得到字节数组的方法得到字节数组

将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)

PlayerInfo playerInfo2 = new PlayerInfo();

// 等级
int index = 0;
playerInfo2.lev = BitConverter.ToInt32(playerInfoBytes1, index);
index += 4; // 索引加上对应字节
print(playerInfo2.lev); // 10

// 姓名的长度
int length = BitConverter.ToInt32(playerInfoBytes1, index);
index += 4; // 索引加上对应字节

// 姓名字符串
playerInfo2.name = Encoding.UTF8.GetString(playerInfoBytes1, index, length);
index += length; // 索引加上对应字符串长度
print(playerInfo2.name); // 韬老狮

// 攻击力
playerInfo2.atk = BitConverter.ToInt16(playerInfoBytes1, index);
index += 2; // 索引加上对应字节
print(playerInfo2.atk); // 88

// 性别
playerInfo2.sex = BitConverter.ToBoolean(playerInfoBytes1, index);
index += 1; // 索引加上对应字节
print(playerInfo2.sex); // false

总结

  • 我们对类对象的2进制反序列化主要用到的知识点是

    1. BitConverter转换字节数组为非字符串的类型的变量
    2. Encoding.UTF8转换字节数组为字符串类型的变量(注意:先读长度,再读字符串)
  • 转换流程是

    1. 获取到对应的字节数组
    2. 将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)

16.2 知识点代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;
using UnityEngine;

public class Lesson16_网络通信_通信必备知识_序列化和反序列化2进制数据_反序列化 : MonoBehaviour
{    
    void Start()
    {
        #region 知识点一 字节数组转非字符串类型

        //关键类:BitConverter
        //所在命名空间:System
        //主要作用:除字符串的其它常用类型和字节数组相互转换

        byte[] byteArray1 = BitConverter.GetBytes(99);
        int i = BitConverter.ToInt32(byteArray1, 0);//第二个参数传从字节数组第几个字节开始转换 
        print(i);//99

        #endregion

        #region 知识点二 字节数组转字符串类型

        //关键类:Encoding
        //所在命名空间:System.Text
        //主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议大家使用UTF-8类型

        byte[] byteArray2 = Encoding.UTF8.GetBytes("人间自有韬哥在");
        string str = Encoding.UTF8.GetString(byteArray2, 0, byteArray2.Length);//第二个参数传从字节数组第几个字节开始转换 第三个参数是长度
        print(str);//人间自有韬哥在

        #endregion

        #region 知识点三 如何将二进制数据转为一个类对象

        //1.获取到对应的字节数组
        PlayerInfo playerInfo1 = new PlayerInfo();
        playerInfo1.lev = 10;
        playerInfo1.name = "韬老狮";
        playerInfo1.atk = 88;
        playerInfo1.sex = false;

        byte[] playerInfoBytes1 = playerInfo1.GetBytes();//使用上次封装好得到字节数组的方法得到字节数组

        //2.将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)
        PlayerInfo playerInfo2 = new PlayerInfo();

        //等级
        int index = 0;
        playerInfo2.lev = BitConverter.ToInt32(playerInfoBytes1, index);
        index += 4;//索引加上对应字节
        print(playerInfo2.lev);//10

        //姓名的长度
        int length = BitConverter.ToInt32(playerInfoBytes1, index);
        index += 4;//索引加上对应字节

        //姓名字符串
        playerInfo2.name = Encoding.UTF8.GetString(playerInfoBytes1, index, length);
        index += length;//索引加上对应字符串长度
        print(playerInfo2.name);//韬老狮

        //攻击力
        playerInfo2.atk = BitConverter.ToInt16(playerInfoBytes1, index);
        index += 2;//索引加上对应字节
        print(playerInfo2.atk);//88

        //性别
        playerInfo2.sex = BitConverter.ToBoolean(playerInfoBytes1, index);
        index += 1;//索引加上对应字节
        print(playerInfo2.sex);//false

        #endregion

        #region 总结

        //我们对类对象的2进制反序列化主要用到的知识点是
        //1.BitConverter转换字节数组为非字符串的类型的变量
        //2.Encoding.UTF8转换字节数组为字符串类型的变量(注意:先读长度,再读字符串)

        //转换流程是
        //1.获取到对应的字节数组
        //2.将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)

        #endregion
    }
}

16.3 练习题

请在上节课写的基类中,加入反序列化相关的操作

完善自定义序列化转换类读取部分

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public abstract class BaseData
{
    // 将二进制字节数组反序列化到对象的成员变量中
    public abstract int Reading(byte[] bytes, int beginIndex = 0);

    //存储省略..

    #region 读取

    // 根据字节数组读取整数
    protected int ReadInt(byte[] bytes, ref int index)
    {
        int value = BitConverter.ToInt32(bytes, index);
        index += sizeof(int);
        return value;
    }

    // 根据字节数组读取短整数
    protected short ReadShort(byte[] bytes, ref int index)
    {
        short value = BitConverter.ToInt16(bytes, index);
        index += sizeof(short);
        return value;
    }

    // 根据字节数组读取长整数
    protected long ReadLong(byte[] bytes, ref int index)
    {
        long value = BitConverter.ToInt64(bytes, index);
        index += sizeof(long);
        return value;
    }

    // 根据字节数组读取单精度浮点数
    protected float ReadFloat(byte[] bytes, ref int index)
    {
        float value = BitConverter.ToSingle(bytes, index);
        index += sizeof(float);
        return value;
    }

    // 根据字节数组读取字节
    protected byte ReadByte(byte[] bytes, ref int index)
    {
        byte value = bytes[index];
        index += sizeof(byte);
        return value;
    }

    // 根据字节数组读取布尔值
    protected bool ReadBool(byte[] bytes, ref int index)
    {
        bool value = BitConverter.ToBoolean(bytes, index);
        index += sizeof(bool);
        return value;
    }

    // 根据字节数组读取字符串
    protected string ReadString(byte[] bytes, ref int index)
    {
        // 首先读取长度
        int length = ReadInt(bytes, ref index);
        // 再读取字符串
        string value = Encoding.UTF8.GetString(bytes, index, length);
        index += length;
        return value;
    }

    // 根据字节数组读取自定义数据对象
    protected T ReadData<T>(byte[] bytes, ref int index) where T : BaseData, new()
    {
        T value = new T();
        index += value.Reading(bytes, index);
        return value;
    }

    #endregion
}

完善两个测试类

public class TestInfo : BaseData
{
    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        int index = beginIndex; // 用于记录当前反序列化位置的索引
        lev = ReadShort(bytes, ref index);       // 反序列化玩家等级
        player = ReadData<Player>(bytes, ref index); // 反序列化玩家对象
        hp = ReadInt(bytes, ref index);           // 反序列化生命值
        name = ReadString(bytes, ref index);      // 反序列化玩家名称
        sex = ReadBool(bytes, ref index);         // 反序列化玩家性别
        // 反序列化列表的长度以及循环反序列化对应的内容
        return index - beginIndex; // 返回已反序列化的字节数
    }
}

public class Player : BaseData
{
    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        int index = beginIndex; // 用于记录当前反序列化位置的索引
        atk = ReadInt(bytes, ref index); // 反序列化玩家攻击力
        return index - beginIndex; // 返回已反序列化的字节数
    }
}

进行反序列化测试

// 创建 TestInfo 对象用于存储数据
TestInfo testInfo1 = new TestInfo();

// 设置 TestInfo 对象的各个成员变量的值
testInfo1.lev = 87; // 设置玩家等级
testInfo1.player = new Player(); // 创建 Player 对象并设置玩家对象
testInfo1.player.atk = 77; // 设置玩家攻击力
testInfo1.hp = 100; // 设置生命值
testInfo1.name = "人间自有韬哥在"; // 设置玩家名称
testInfo1.sex = false; // 设置玩家性别

// 调用 TestInfo 对象的 Writing 方法,将数据序列化为字节数组
byte[] testInfoByteArray = testInfo1.Writing();

// 创建一个新的 TestInfo 对象,用于反序列化数据
TestInfo testInfo2 = new TestInfo();

// 调用 TestInfo 对象的 Reading 方法,将字节数组解析为对象
testInfo2.Reading(testInfoByteArray);

// 打印反序列化后的数据
print(testInfo2.lev); //87 打印玩家等级
print(testInfo2.player.atk); //77 打印玩家攻击力
print(testInfo2.hp); //100 打印生命值
print(testInfo2.name); //人间自有韬哥在 打印玩家名称
print(testInfo2.sex); //false 打印玩家性别

16.4 练习题代码

BaseData

using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public abstract class BaseData
{
    /// <summary>
    /// 用于子类重写的 获取字节数组容器大小的方法
    /// </summary>
    /// <returns>对象需要占用的字节数</returns>
    public abstract int GetBytesNum();

    /// <summary>
    /// 将对象的成员变量序列化为对应的字节数组
    /// </summary>
    /// <returns>序列化后的字节数组</returns>
    public abstract byte[] Writing();

    /// <summary>
    /// 将二进制字节数组反序列化到对象的成员变量中
    /// </summary>
    /// <param name="bytes">反序列化使用的字节数组</param>
    /// <param name="beginIndex">从该字节数组的第几个位置开始解析,默认为0</param>
    /// <returns>已解析的字节数</returns>
    public abstract int Reading(byte[] bytes, int beginIndex = 0);

    #region 存储

    /// <summary>
    /// 存储整型变量到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="value">具体的整数值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteInt(byte[] bytes, int value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(int);
    }

    /// <summary>
    /// 存储短整型变量到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="value">具体的短整数值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteShort(byte[] bytes, short value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(short);
    }

    /// <summary>
    /// 存储长整型变量到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="value">具体的长整数值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteLong(byte[] bytes, long value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(long);
    }

    /// <summary>
    /// 存储单精度浮点数到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="value">具体的单精度浮点数值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteFloat(byte[] bytes, float value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(float);
    }

    /// <summary>
    /// 存储字节到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="value">具体的字节值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteByte(byte[] bytes, byte value, ref int index)
    {
        bytes[index] = value;
        index += sizeof(byte);
    }

    /// <summary>
    /// 存储布尔值到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="value">具体的布尔值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteBool(byte[] bytes, bool value, ref int index)
    {
        BitConverter.GetBytes(value).CopyTo(bytes, index);
        index += sizeof(bool);
    }

    /// <summary>
    /// 存储字符串到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="value">具体的字符串值</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteString(byte[] bytes, string value, ref int index)
    {
        // 先存储字符串字节数组的长度
        byte[] strBytes = Encoding.UTF8.GetBytes(value);
        WriteInt(bytes, strBytes.Length, ref index);
        // 再存储字符串字节数组
        strBytes.CopyTo(bytes, index);
        index += strBytes.Length;
    }

    /// <summary>
    /// 存储自定义数据对象到指定的字节数组中
    /// </summary>
    /// <param name="bytes">指定的字节数组</param>
    /// <param name="data">具体的自定义数据对象</param>
    /// <param name="index">每次存储后用于记录当前索引位置的变量</param>
    protected void WriteData(byte[] bytes, BaseData data, ref int index)
    {
        data.Writing().CopyTo(bytes, index);
        index += data.GetBytesNum();
    }

    #endregion

    #region 读取

    /// <summary>
    /// 根据字节数组读取整数
    /// </summary>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>读取到的整数值</returns>
    protected int ReadInt(byte[] bytes, ref int index)
    {
        int value = BitConverter.ToInt32(bytes, index);
        index += sizeof(int);
        return value;
    }

    /// <summary>
    /// 根据字节数组读取短整数
    /// </summary>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>读取到的短整数值</returns>
    protected short ReadShort(byte[] bytes, ref int index)
    {
        short value = BitConverter.ToInt16(bytes, index);
        index += sizeof(short);
        return value;
    }

    /// <summary>
    /// 根据字节数组读取长整数
    /// </summary>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>读取到的长整数值</returns>
    protected long ReadLong(byte[] bytes, ref int index)
    {
        long value = BitConverter.ToInt64(bytes, index);
        index += sizeof(long);
        return value;
    }

    /// <summary>
    /// 根据字节数组读取单精度浮点数
    /// </summary>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>读取到的单精度浮点数值</returns>
    protected float ReadFloat(byte[] bytes, ref int index)
    {
        float value = BitConverter.ToSingle(bytes, index);
        index += sizeof(float);
        return value;
    }

    /// <summary>
    /// 根据字节数组读取字节
    /// </summary>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>读取到的字节值</returns>
    protected byte ReadByte(byte[] bytes, ref int index)
    {
        byte value = bytes[index];
        index += sizeof(byte);
        return value;
    }

    /// <summary>
    /// 根据字节数组读取布尔值
    /// </summary>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>读取到的布尔值</returns>
    protected bool ReadBool(byte[] bytes, ref int index)
    {
        bool value = BitConverter.ToBoolean(bytes, index);
        index += sizeof(bool);
        return value;
    }

    /// <summary>
    /// 根据字节数组读取字符串
    /// </summary>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>读取到的字符串值</returns>
    protected string ReadString(byte[] bytes, ref int index)
    {
        // 首先读取长度
        int length = ReadInt(bytes, ref index);
        // 再读取字符串
        string value = Encoding.UTF8.GetString(bytes, index, length);
        index += length;
        return value;
    }

    /// <summary>
    /// 根据字节数组读取自定义数据对象
    /// </summary>
    /// <typeparam name="T">自定义数据对象的类型</typeparam>
    /// <param name="bytes">字节数组</param>
    /// <param name="index">开始读取的索引数</param>
    /// <returns>已读取的自定义数据对象</returns>
    protected T ReadData<T>(byte[] bytes, ref int index) where T : BaseData, new()
    {
        T value = new T();
        index += value.Reading(bytes, index);
        return value;
    }

    #endregion
}

TestInfo

using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class TestInfo : BaseData
{
    public short lev;    // 玩家等级
    public Player player; // 玩家对象
    public int hp;        // 生命值
    public string name;   // 玩家名称
    public bool sex;      // 玩家性别

    public override int GetBytesNum()
    {
        // 返回对象需要占用的字节数,按照各个成员的大小累加
        return sizeof(short) +        // 2 字节
            player.GetBytesNum() +    // 玩家对象序列化大小
            sizeof(int) +             // 4 字节
            4 + Encoding.UTF8.GetBytes(name).Length + // 字符串长度(4字节) + 字符串字节数
            sizeof(bool);              // 1 字节
    }

    public override byte[] Writing()
    {
        int index = 0; // 用于记录当前序列化位置的索引
        byte[] bytes = new byte[GetBytesNum()]; // 创建字节数组
        WriteShort(bytes, lev, ref index);     // 序列化玩家等级
        WriteData(bytes, player, ref index);   // 序列化玩家对象
        WriteInt(bytes, hp, ref index);        // 序列化生命值
        WriteString(bytes, name, ref index);   // 序列化玩家名称
        WriteBool(bytes, sex, ref index);      // 序列化玩家性别
        // 序列化列表的长度以及循环序列化对应的内容
        return bytes; // 返回序列化后的字节数组
    }

    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        int index = beginIndex; // 用于记录当前反序列化位置的索引
        lev = ReadShort(bytes, ref index);       // 反序列化玩家等级
        player = ReadData<Player>(bytes, ref index); // 反序列化玩家对象
        hp = ReadInt(bytes, ref index);           // 反序列化生命值
        name = ReadString(bytes, ref index);      // 反序列化玩家名称
        sex = ReadBool(bytes, ref index);         // 反序列化玩家性别
        // 反序列化列表的长度以及循环反序列化对应的内容
        return index - beginIndex; // 返回已反序列化的字节数
    }
}

public class Player : BaseData
{
    public int atk; // 玩家攻击力

    public override int GetBytesNum()
    {
        return 4; // 返回对象需要占用的字节数,此处为 4 字节
    }

    public override byte[] Writing()
    {
        int index = 0; // 用于记录当前序列化位置的索引
        byte[] bytes = new byte[GetBytesNum()]; // 创建字节数组
        WriteInt(bytes, atk, ref index); // 序列化玩家攻击力
        return bytes; // 返回序列化后的字节数组
    }

    public override int Reading(byte[] bytes, int beginIndex = 0)
    {
        int index = beginIndex; // 用于记录当前反序列化位置的索引
        atk = ReadInt(bytes, ref index); // 反序列化玩家攻击力
        return index - beginIndex; // 返回已反序列化的字节数
    }
}

Lesson16_练习题

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Lesson16_练习题 : MonoBehaviour
{
    void Start()
    {
        // 创建 TestInfo 对象用于存储数据
        TestInfo testInfo1 = new TestInfo();

        // 设置 TestInfo 对象的各个成员变量的值
        testInfo1.lev = 87; // 设置玩家等级
        testInfo1.player = new Player(); // 创建 Player 对象并设置玩家对象
        testInfo1.player.atk = 77; // 设置玩家攻击力
        testInfo1.hp = 100; // 设置生命值
        testInfo1.name = "人间自有韬哥在"; // 设置玩家名称
        testInfo1.sex = false; // 设置玩家性别

        // 调用 TestInfo 对象的 Writing 方法,将数据序列化为字节数组
        byte[] testInfoByteArray = testInfo1.Writing();

        // 创建一个新的 TestInfo 对象,用于反序列化数据
        TestInfo testInfo2 = new TestInfo();

        // 调用 TestInfo 对象的 Reading 方法,将字节数组解析为对象
        testInfo2.Reading(testInfoByteArray);

        // 打印反序列化后的数据
        print(testInfo2.lev); //87 打印玩家等级
        print(testInfo2.player.atk); //77 打印玩家攻击力
        print(testInfo2.hp); //100 打印生命值
        print(testInfo2.name); //人间自有韬哥在 打印玩家名称
        print(testInfo2.sex); //false 打印玩家性别
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏