网络通信中传输的数据
在网络通信中
我们把想要传递的类对象信息序列化为2进制数据(一般为byte字节数组)
再将该2进制数据通过网络传输给远端设备
远端设备获取到该2进制数据后再将其反序列化为对应的类对象
序列化和反序列化
序列化:
将类对象信息转换为可保存或传输的格式的过程
反序列化:
与序列化相对,将保存或传输过来的格式转换为类对象的过程
数据持久化2进制
网络通信相关
1.BitConverter类:主要用于处理各类型和字节数组间的相互转换
2.Encoding类:主要用于处理字符串类型和字节数组间的相互转换
3.加密相关:了解2进制数据加密的常用手段和思路
本地持久化知识点:
1.File类:文件操作类,用于操作文件
2.FileStream类:文件流,以流的形式进行文件存储读取操作
3.MemoryStrem:内存流对象
4.BinaryFormatter:2进制格式化对象
在网络开发时,我们不会使用BinaryFormatter类来进行数据的序列化和反序列化 因为客户端和服务端的开发语言大多数情况下是不同的 BinaryFormatter类序列化的数据无法兼容其它语言
序列化
非字符串类型转字节数组
主要作用:
除字符串的其它常用类型和字节数组相互转换
命名空间:using System
将一个类对象(构造体)转换为二进制
//构建一个构造体存储玩家信息
public class PlayerInfo
{
public int lev;
public string playerName;
public short atk;
public bool sex;
}
1.明确字节数组的容量(注意:在确定字符串字节长度时要考虑解析时如何处理)
PlayerInfo info = new PlayerInfo();
info.lev = 10;
info.playerName = "缘笙箫";
info.atk = 88;
info.sex = false;
//得到的这个info数据,如果想要转换成为字节数组,那么字节数组容器需要的容量就要提前计算出来
int indexNum = sizeof(int) +
sizeof(int) + //这里的int代表的是字符串的长度,因为UTF-8是可以有多个取值的在存储这个数据的时候要将UTF-8的值传入,方便反序列化的时候确定字符串的长度
Encoding.UTF8.GetBytes(info.playerName).Length +
sizeof(short) +
sizeof(bool);
2.申明一个装载信息的字节数组容器
byte[] playerBytes = new byte[indexNum];
3.将对象中的所有信息转为字节数组并放入该容器中(可以利用数组中的CopeTo方法转存字节数组)
CopyTo方法的第二个参数代表,从容器的第几个位置开始存储
int index = 0; //从 playerBytes数组的第几个位置开始存储数据
//等级
BitConverter.GetBytes(info.lev).CopyTo(playerBytes, index);
index += sizeof(int); //下标进行移动
//姓名
byte[] strBytes = Encoding.UTF8.GetBytes(info.playerName);
int num = strBytes.Length;
//存储的是姓名转换成字节数组后 字节数组的长度
BitConverter.GetBytes(num).CopyTo(playerBytes, index);
index += sizeof(int);
//存储字符串的字节数组
strBytes.CopyTo(playerBytes, index);
index += num;
//攻击力
BitConverter.GetBytes(info.atk).CopyTo(playerBytes, index);
index += sizeof(short);
//性别
BitConverter.GetBytes(info.sex).CopyTo(playerBytes, index);
index += sizeof(bool);
也可以直接写在构造函数内,方便调用,不用每次再编写一次
public class PlayerInfo
{
public int lev;
public string name;
public short atk;
public bool sex;
public byte[] GetBytes()
{
int indexNum = sizeof(int) + //lev int类型 4
sizeof(int) + //代表 name字符串转换成字节数组后 数组的长度 4
Encoding.UTF8.GetBytes(name).Length + //字符串具体字节数组的长度
sizeof(short) + //atk short类型 2
sizeof(bool); //sex bool类型 1
byte[] playerBytes = new byte[indexNum];
int index = 0;//从 playerBytes数组中的第几个位置去存储数据
//等级
BitConverter.GetBytes(lev).CopyTo(playerBytes, index);
index += sizeof(int);
//姓名
byte[] strBytes = Encoding.UTF8.GetBytes(name);
int num = strBytes.Length;
//存储的是姓名转换成字节数组后 字节数组的长度
BitConverter.GetBytes(num).CopyTo(playerBytes, index);
index += sizeof(int);
//存储字符串的字节数组
strBytes.CopyTo(playerBytes, index);
index += num;
//攻击力
BitConverter.GetBytes(atk).CopyTo(playerBytes, index);
index += sizeof(short);
//性别
BitConverter.GetBytes(sex).CopyTo(playerBytes, index);
index += sizeof(bool);
return playerBytes;
}
}
总结
对类对象的2进制序列化
1.BitConverter转换非字符串的类型的变量为字节数组
2.Encoding.UTF8转换字符串类型的变量为字节数组(注意:为了考虑反序列化,我们在转存2进制,序列化字符串之前,先序列化字符串字节数组的长度)
转换流程
1.明确字节数组的容量
2.申明一个装载信息的字节数组容器
3.将对象中的所有信息转为字节数组并放入该容器中(利用数组中的CopeTo方法转存字节数组)
基类(写)
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[] Writeing();
/// <summary>
/// 将整短形数据转化为二进制并且存入字节数组当中
/// </summary>
/// <param name="bytes">字节数组</param>
/// <param name="shortData">需要存入的整短型</param>
/// <param name="index">下标编号,ref代表数据在内部进行改变了,外面也跟在改变</param>
protected void WriteShort(byte[] bytes,short shortData,ref int index)
{
BitConverter.GetBytes(shortData).CopyTo(bytes, index);
index += sizeof(short);
}
protected void WriteData(byte[] bytes,BaseData Data, ref int index)
{
Data.Writeing().CopyTo(bytes, index);
index += Data.GetBytesNum();
}
protected void WriteInt(byte[] bytes, int IntData, ref int index)
{
BitConverter.GetBytes(IntData).CopyTo(bytes, index);
index += sizeof(int);
}
protected void Writelong(byte[] bytes, short longData, ref int index)
{
BitConverter.GetBytes(longData).CopyTo(bytes, index);
index += sizeof(long);
}
protected void Writestring(byte[] bytes, string stringData, ref int index)
{
//先存储string字节数组的长度
byte[] strBytes = Encoding.UTF8.GetBytes(stringData);
WriteInt(bytes, strBytes.Length, ref index);
//BitConverter.GetBytes(strBytes.Length).CopyTo(bytes, index);
//index += sizeof(int);
//再存 string字节数组
strBytes.CopyTo(bytes, index);
index += strBytes.Length;
}
protected void WriteBool(byte[] bytes,bool boolData, ref int index)
{
BitConverter.GetBytes(boolData).CopyTo(bytes, index);
index += sizeof(bool);
}
}
调用(写)
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class PlayerATK : BaseData
{
public int atk;
public override int GetBytesNum()
{
return sizeof(int);
}
public override byte[] Writeing()
{
int index = 0;
byte[] bytes = new byte[GetBytesNum()];
WriteInt(bytes, atk, ref index);
return bytes;
}
}
public class Player : BaseData
{
public int lev;
public PlayerATK aTK;
public string name;
public override int GetBytesNum()
{
return sizeof(int) +
aTK.GetBytesNum() +
sizeof(int) +
Encoding.UTF8.GetBytes(name).Length;
}
public override byte[] Writeing()
{
int index = 0;
byte[] bytes = new byte[GetBytesNum()];
WriteInt(bytes, lev, ref index);
WriteData(bytes, aTK, ref index);
Writestring(bytes, name, ref index);
return bytes;
}
}
public class Test : MonoBehaviour
{
void Start()
{
Player player = new Player();
player.lev = 1;
player.aTK = new PlayerATK();
player.aTK.atk = 4;
player.name = "gfqig";
byte[] bytes = player.Writeing();
}
}
反序列化
字节数组转非字符串类型
关键类:BitConverter
所在命名空间:using System
主要作用:除字符串的其它常用类型和字节数组相互转换
byte[] bytes = BitConverter.GetBytes(99);
int i = BitConverter.ToInt32(bytes, 0);
print(i);
字节数组转换非字符串类型
关键类:Encoding
所在命名空间:using System.Text
主要作用:将字符串类型和字节数组相互转换,并且决定转换时使用的字符编码类型,网络通信时建议使用UTF-8
byte[] bytes2 = Encoding.UTF8.GetBytes("yhtgjn");
string str = Encoding.UTF8.GetString(bytes2, 0, bytes2.Length);
print(str);
将二进制数据转换为一个类对象
1.获取到对应的字节数组
PlayerInfo info = new PlayerInfo();
info.lev = 5;
info.playerName = "yuan";
info.atk = 88;
info.sex = false;
byte[] playerBytes = info.GetBytes();
2.将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)
PlayerInfo info2 = new PlayerInfo();
//等级
int index = 0;
info2.lev = BitConverter.ToInt32(playerBytes, index);
index += 4;
print(info2.lev);
//姓名的长度
int length = BitConverter.ToInt32(playerBytes, index);
index += 4;
//姓名字符串
info2.playerName = Encoding.UTF8.GetString(playerBytes, index, length);
index += length;
print(info2.playerName);
//攻击力
info2.atk = BitConverter.ToInt16(playerBytes, index);
index += 2;
print(info2.atk);
//性别
info2.sex = BitConverter.ToBoolean(playerBytes, index);
index += 1;
print(info2.sex);
总结
类对象的2进制反序列化
1.BitConVerter转换字节数组为非字符串的类型的变量
2.Encoding.UTF8转换字节数组为字符串类型的变量(注意:先读长度,再读字符串)
转换流程是
1.获取到对应的字节数组
2.将字节数组按照序列化时的顺序进行反序列化(将对应字节分组转换为对应类型变量)
基类(读)
/// <summary>
/// 反序列化方法,将字节数组信息提取到对象的变量中,读取顺序和序列化时一至
/// </summary>
/// <param name="bytes">传入字节数组</param>
/// <param name="beginIndex">从该字节数组的第几个位置开始解析,默认是0</param>
/// <returns></returns>
public abstract int Reading(byte[] bytes,int beginIndex = 0);
/// <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;
}
protected short ReadShort(byte[] bytes, ref int index)
{
short value = BitConverter.ToInt16(bytes, index);
index += sizeof(short);
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);
//index += sizeof(int);
//再去将字符串给转换出来
string value = Encoding.UTF8.GetString(bytes, index, length);
index += length;
return value;
}
//T代表是泛型,where代表给这个泛型加个范围只能是BaseData里面的类型
protected T ReadData<T>(byte[] bytes, ref int index) where T:BaseData,new()
{
T value = new T();
index += value.Reading(bytes,index);
return value;
}
调用(读)
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex;
atk = ReadInt(bytes, ref index);
return index - beginIndex;
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex;
lev = ReadInt(bytes, ref index);
aTK = ReadData<PlayerATK>(bytes, ref index);
name = ReadString(bytes, ref index);
return index - beginIndex;
}
Player player2 = new Player();
player2.Reading(bytes);
print(player2.lev);
print(player2.aTK.atk);
print(player2.name);