分包、黏包
分包、黏包指在网络通讯中由于各种因素(网络环境、API规则等)造成的消息与消息之间出现的两种状态(注:分包和黏包可能同时发生)
分包
一个消息分成了多个消息进行发送
黏包
一个消息和另外一个消息黏在了一起
解决分包、黏包
通过消息长度来判断一个消息有没有出现分包或者黏包
为消息添加头部,头部记录消息的长度,当我们接收到消息时,通过消息长度来判断是否分包、黏包
对消息进行拆分处理、合并处理
我们每次只处理完整的消息
1.为所有消息添加头部信息,用于存储其消息长度
//再加入一个记录数据长度的类
WriteInt(bytes, GetBytesNum()-8, ref index);
public override int GetBytesNum()
{
return 4 + 4 + 4 + playerData.GetBytesNum();
}
2.根据分包、黏包的表现情况,修改接收消息处的逻辑
//用于处理分包时 缓冲的 字节数组和字节数组长度
private byte[] cacheBytes = new byte[1024];
private int cacheNum = 0;
///
/// 处理接受消息 分包、黏包问题的方法
///
/// 接收到的字节
/// 接收到的字节的长度
public void HandleReceiveMsg(byte[] receiveBytes,int receiveNum)
{
int msgID = 0; //头标文件的编码,用于解析查看用哪种数据类型
int msgLength = 0; //字符串的长度
int nowIndex = 0; //现在的字节所存在的字节长度
//收到消息时 应该看看 之前有没有缓存的 如果有的话 我们直接拼接到后面
receiveBytes.CopyTo(cacheBytes, cacheNum);
cacheNum += receiveNum;
while (true)
{
//每次将长度设置为-1 是避免上一次解析的数据 影响这一次的判断
msgLength = -1;
//处理一条消息的流程
if(cacheNum - nowIndex >= 8) //只有去除头文件后字节的长度的8数据才能进行解析
{
//解析ID
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
//解析长度
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
}
if(cacheNum - nowIndex >= msgLength && msgLength != -1) //如果缓存的字节减去头文件大于等于该文件的长度而且字节的长度不等于-1
{
//解析消息体
BaseMsg baseMsg = null;
switch (msgID)
{
case 1001:
PlayerMsg msg = new PlayerMsg();
msg.Reading(cacheBytes, nowIndex);
baseMsg = msg;
break;
}
if (baseMsg != null) //如果解析出来的数据不是空就将数据解析出来,并且加上解析的数据长度
receiveQueue.Enqueue(baseMsg);
nowIndex += msgLength;
//如果解析的长度等于nowIndex就可以将所有的复位
if(nowIndex == cacheNum)
{
cacheNum = 0;
break;
}
}
else
{
//如果进行了id和长度的解析 但是 没有成功解析消息体 那么我们需要减去id和长度的解析
if (msgLength != -1)
nowIndex -= 8;
//把剩余没有解析的字节数组内容移到前面来用于缓存下次继续解析
Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);
cacheNum = cacheNum - nowIndex;
break;
}
}
}
数据类
BaseData
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test04
{
public abstract class BaseData
{
///
/// 用于子类重写的虚方法 获取字节数组的容量
///
/// 获取字节数组的容量
public abstract int GetBytesNum();
///
/// 反序列化方法,将字节数组信息提取到对象的变量中,读取顺序和序列化时一至
///
/// 传入字节数组
/// 从该字节数组的第几个位置开始解析,默认是0
///
public abstract int Reading(byte[] bytes, int beginIndex = 0);
///
/// 返回字节数组
///
/// 字节数组
public abstract byte[] Writeing();
///
/// 将整短形数据转化为二进制并且存入字节数组当中
///
/// 字节数组
/// 需要存入的整短型
/// 下标编号,ref代表数据在内部进行改变了,外面也跟在改变
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);
}
///
/// 读取整数字形
///
/// 字节串
/// 下标
/// 整型
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(byte[] bytes, ref int index) where T : BaseData, new()
{
T value = new T();
index += value.Reading(bytes, index);
return value;
}
}
}
BaseMsg
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test04
{
public class BaseMsg : BaseData
{
public override int GetBytesNum()
{
throw new System.NotImplementedException();
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
throw new System.NotImplementedException();
}
public override byte[] Writeing()
{
throw new System.NotImplementedException();
}
///
/// 头文件编号
///
/// 头文件的编号,每个编号对应的一个数据类型,这个编号对应的数据类型可以自己来决定
public virtual int GetID()
{
return 0;
}
}
}
PlayerData
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Test04;
///
/// 玩家数据类
///
public class PlayerData : BaseData
{
public string name;
public int atk;
public int lev;
///
/// 获取玩家类的二进制的字节长度
///
/// 返回的是字节长度
public override int GetBytesNum()
{
return 4 + 4 + 4 + Encoding.UTF8.GetBytes(name).Length;
}
///
/// 读取玩家类的字节长度
///
/// 字节
/// 开始的位置
/// 占了多长的字节
public override int Reading(byte[] bytes, int beginIndex = 0)
{
int index = beginIndex;
name = ReadString(bytes, ref index);
atk = ReadInt(bytes, ref index);
lev = ReadInt(bytes, ref index);
return index - beginIndex;
}
///
/// 写入所要存进去的数据
///
/// 所转换好的二进制的字符串
public override byte[] Writeing()
{
int index = 0;
byte[] bytes = new byte[GetBytesNum()];
Writestring(bytes, name, ref index);
WriteInt(bytes, atk, ref index);
WriteInt(bytes, lev, ref index);
return bytes;
}
}
PlayerMsg
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test04
{
public class PlayerMsg : BaseMsg
{
public int playerID;
public PlayerData playerData;
public override byte[] Writeing()
{
int index = 0;
int bytesNum = GetBytesNum();
byte[] bytes = new byte[bytesNum];
//先写消息ID
WriteInt(bytes, GetID(), ref index);
//写如消息体的长度 我们-8的目的 是只存储 消息体的长度 前面8个字节 是我们自己定的规则 解析时按照这个规则处理就行了
WriteInt(bytes, bytesNum - 8, ref index);
//写这个消息的成员变量
WriteInt(bytes, playerID, ref index);
WriteData(bytes, playerData, ref index);
return bytes;
}
public override int Reading(byte[] bytes, int beginIndex = 0)
{
//反序列化不需要解析ID,应该在反序列化之前就将ID反序列化出来
//用来判断到底使用哪一个自定义类来反序列化
int index = beginIndex;
playerID = ReadInt(bytes, ref index);
playerData = ReadData(bytes, ref index);
return index - beginIndex;
}
public override int GetBytesNum()
{
return 4 + 4 + 4 + playerData.GetBytesNum();
}
///
/// 自定义的消息ID 主要用于区分是哪一个消息类
///
/// 头文件的编号,每个编号对应的一个数据类型,这个编号对应的数据类型可以自己来决定
public override int GetID()
{
return 1001;
}
}
}
服务端
ClientSocket
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace Test04
{
class ClientSocket
{
private static int CLIENT_BEGIN_ID = 1;
public int clientID;
public Socket socket;
//用于处理分包时 缓存的 字节数组 和 字节数组长度
private byte[] cacheBytes = new byte[1024 * 1024];
private int cacheNum = 0;
public ClientSocket(Socket socket)
{
this.clientID = CLIENT_BEGIN_ID;
this.socket = socket;
++CLIENT_BEGIN_ID;
}
///
/// 是否是连接状态
///
public bool Connected => this.socket.Connected;
//我们应该封装一些方法
//关闭
public void Close()
{
if (socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
socket = null;
}
}
//发送
public void Send(BaseMsg info)
{
if (socket != null)
{
try
{
socket.Send(info.Writeing());
}
catch (Exception e)
{
Console.WriteLine("发消息出错" + e.Message);
Close();
}
}
}
//接收
public void Receive()
{
if (socket == null)
return;
try
{
if (socket.Available > 0)
{
byte[] result = new byte[1024 * 5];
int receiveNum = socket.Receive(result);
HandleReceiveMsg(result, receiveNum);
收到数据后 先读取4个字节 转为ID 才知道用哪一个类型去处理反序列化
//int msgID = BitConverter.ToInt32(result, 0);
//BaseMsg msg = null;
//switch (msgID)
//{
// case 1001:
// msg = new PlayerMsg();
// msg.Reading(result, 4);
// break;
//}
//if (msg == null)
// return;
//ThreadPool.QueueUserWorkItem(MsgHandle, msg);
}
}
catch (Exception e)
{
Console.WriteLine("收消息出错" + e.Message);
Close();
}
}
//处理接受消息 分包、黏包问题的方法
private void HandleReceiveMsg(byte[] receiveBytes, int receiveNum)
{
int msgID = 0;
int msgLength = 0;
int nowIndex = 0;
//收到消息时 应该看看 之前有没有缓存的 如果有的话 我们直接拼接到后面
receiveBytes.CopyTo(cacheBytes, cacheNum);
cacheNum += receiveNum;
while (true)
{
//每次将长度设置为-1 是避免上一次解析的数据 影响这一次的判断
msgLength = -1;
//处理解析一条消息
if (cacheNum - nowIndex >= 8)
{
//解析ID
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
//解析长度
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
}
if (cacheNum - nowIndex >= msgLength && msgLength != -1)
{
//解析消息体
BaseMsg baseMsg = null;
switch (msgID)
{
case 1001:
PlayerMsg msg = new PlayerMsg();
msg.Reading(cacheBytes, nowIndex);
baseMsg = msg;
break;
}
if (baseMsg != null)
ThreadPool.QueueUserWorkItem(MsgHandle, baseMsg);
nowIndex += msgLength;
if (nowIndex == cacheNum)
{
cacheNum = 0;
break;
}
}
else
{
//如果进行了 id和长度的解析 但是 没有成功解析消息体 那么我们需要减去nowIndex移动的位置
if (msgLength != -1)
nowIndex -= 8;
//就是把剩余没有解析的字节数组内容 移到前面来 用于缓存下次继续解析
Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);
cacheNum = cacheNum - nowIndex;
break;
}
}
}
private void MsgHandle(object obj)
{
BaseMsg msg = obj as BaseMsg;
if (msg is PlayerMsg)
{
PlayerMsg playerMsg = msg as PlayerMsg;
Console.WriteLine(playerMsg.playerID);
Console.WriteLine(playerMsg.playerData.name);
Console.WriteLine(playerMsg.playerData.lev);
Console.WriteLine(playerMsg.playerData.atk);
}
}
}
}
ServerSocket
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Test04
{
class ServerSocket
{
//服务端Socket
public Socket socket;
//客户端连接的所有Socket
public Dictionary clientDic = new Dictionary();
private bool isClose;
//开启服务器端
public void Start(string ip, int port, int num)
{
isClose = false;
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
socket.Bind(ipPoint);
socket.Listen(num);
ThreadPool.QueueUserWorkItem(Accept);
ThreadPool.QueueUserWorkItem(Receive);
}
//关闭服务器端
public void Close()
{
isClose = true;
foreach (ClientSocket client in clientDic.Values)
{
client.Close();
}
clientDic.Clear();
socket.Shutdown(SocketShutdown.Both);
socket.Close();
socket = null;
}
//接受客户端连入
private void Accept(object obj)
{
while (!isClose)
{
try
{
//连入一个客户端
Socket clientSocket = socket.Accept();
ClientSocket client = new ClientSocket(clientSocket);
clientDic.Add(client.clientID, client);
}
catch (Exception e)
{
Console.WriteLine("客户端连入报错" + e.Message);
}
}
}
//接收客户端消息
private void Receive(object obj)
{
while (!isClose)
{
if (clientDic.Count > 0)
{
foreach (ClientSocket client in clientDic.Values)
{
client.Receive();
}
}
}
}
public void Broadcast(BaseMsg info)
{
foreach (ClientSocket client in clientDic.Values)
{
client.Send(info);
}
}
}
}
Program
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Test04
{
class Program
{
static void Main(string[] args)
{
ServerSocket socket = new ServerSocket();
socket.Start("127.0.0.1", 8080, 1024);
Console.WriteLine("服务器开启成功");
while (true)
{
string input = Console.ReadLine();
if (input == "Quit")
{
socket.Close();
}
else if (input.Substring(0, 2) == "B:")
{
if (input.Substring(2) == "1001")
{
PlayerMsg msg = new PlayerMsg();
msg.playerID = 9876;
msg.playerData = new PlayerData();
msg.playerData.name = "服务器端发来的消息";
msg.playerData.lev = 99;
msg.playerData.atk = 80;
socket.Broadcast(msg);
}
}
}
}
}
}
客户端(unity上的)
NetMgr
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using UnityEngine;
public class NetMgr : MonoBehaviour
{
private static NetMgr instance;
public static NetMgr Instance => instance;
//客户端Socket
private Socket socket;
//用于发送消息的队列 公共容器 主线程往里面放 发送线程从里面取
private Queue sendMsgQueue = new Queue();
//用于接收消息的对象 公共容器 子线程往里面放 主线程从里面取
private Queue receiveQueue = new Queue();
用于收消息的水桶(容器)
//private byte[] receiveBytes = new byte[1024 * 1024];
返回收到的字节数
//private int receiveNum;
//用于处理分包时 缓存的 字节数组 和 字节数组长度
private byte[] cacheBytes = new byte[1024 * 1024];
private int cacheNum = 0;
//是否连接
private bool isConnected = false;
void Awake()
{
instance = this;
DontDestroyOnLoad(this.gameObject);
}
// Update is called once per frame
void Update()
{
if (receiveQueue.Count > 0)
{
BaseMsg msg = receiveQueue.Dequeue();
if (msg is PlayerMsg)
{
PlayerMsg playerMsg = (msg as PlayerMsg);
print(playerMsg.playerID);
print(playerMsg.playerData.name);
print(playerMsg.playerData.lev);
print(playerMsg.playerData.atk);
}
}
}
//连接服务端
public void Connect(string ip, int port)
{
//如果是连接状态 直接返回
if (isConnected)
return;
if (socket == null)
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//连接服务端
IPEndPoint ipPoint = new IPEndPoint(IPAddress.Parse(ip), port);
try
{
socket.Connect(ipPoint);
isConnected = true;
//开启发送线程
ThreadPool.QueueUserWorkItem(SendMsg);
//开启接收线程
ThreadPool.QueueUserWorkItem(ReceiveMsg);
}
catch (SocketException e)
{
if (e.ErrorCode == 10061)
print("服务器拒绝连接");
else
print("连接失败" + e.ErrorCode + e.Message);
}
}
//发送消息
public void Send(BaseMsg msg)
{
sendMsgQueue.Enqueue(msg);
}
///
/// 用于测试 直接发字节数组的方法
///
///
public void SendTest(byte[] bytes)
{
socket.Send(bytes);
}
private void SendMsg(object obj)
{
while (isConnected)
{
if (sendMsgQueue.Count > 0)
{
socket.Send(sendMsgQueue.Dequeue().Writeing());
}
}
}
//不停的接受消息
private void ReceiveMsg(object obj)
{
while (isConnected)
{
if (socket.Available > 0)
{
byte[] receiveBytes = new byte[1024 * 1024];
int receiveNum = socket.Receive(receiveBytes);
HandleReceiveMsg(receiveBytes, receiveNum);
首先把收到字节数组的前4个字节 读取出来得到ID
//int msgID = BitConverter.ToInt32(receiveBytes, 0);
//BaseMsg baseMsg = null;
//switch (msgID)
//{
// case 1001:
// PlayerMsg msg = new PlayerMsg();
// msg.Reading(receiveBytes, 4);
// baseMsg = msg;
// break;
//}
如果消息为空 那证明是不知道类型的消息 没有解析
//if (baseMsg == null)
// continue;
收到消息 解析消息为字符串 并放入公共容器
//receiveQueue.Enqueue(baseMsg);
}
}
}
//处理接受消息 分包、黏包问题的方法
private void HandleReceiveMsg(byte[] receiveBytes, int receiveNum)
{
int msgID = 0;
int msgLength = 0;
int nowIndex = 0;
//收到消息时 应该看看 之前有没有缓存的 如果有的话 我们直接拼接到后面
receiveBytes.CopyTo(cacheBytes, cacheNum);
cacheNum += receiveNum;
while (true)
{
//每次将长度设置为-1 是避免上一次解析的数据 影响这一次的判断
msgLength = -1;
//处理解析一条消息
if (cacheNum - nowIndex >= 8)
{
//解析ID
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
//解析长度
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
}
if (cacheNum - nowIndex >= msgLength && msgLength != -1)
{
//解析消息体
BaseMsg baseMsg = null;
switch (msgID)
{
case 1001:
PlayerMsg msg = new PlayerMsg();
msg.Reading(cacheBytes, nowIndex);
baseMsg = msg;
break;
}
if (baseMsg != null)
receiveQueue.Enqueue(baseMsg);
nowIndex += msgLength;
if (nowIndex == cacheNum)
{
cacheNum = 0;
break;
}
}
else
{
//如果进行了 id和长度的解析 但是 没有成功解析消息体 那么我们需要减去nowIndex移动的位置
if (msgLength != -1)
nowIndex -= 8;
//就是把剩余没有解析的字节数组内容 移到前面来 用于缓存下次继续解析
Array.Copy(cacheBytes, nowIndex, cacheBytes, 0, cacheNum - nowIndex);
cacheNum = cacheNum - nowIndex;
break;
}
}
}
public void Close()
{
if (socket != null)
{
socket.Shutdown(SocketShutdown.Both);
socket.Close();
isConnected = false;
}
}
private void OnDestroy()
{
Close();
}
}
Main
ng System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Main : MonoBehaviour
{
// Start is called before the first frame update
void Start()
{
if (NetMgr.Instance == null)
{
GameObject obj = new GameObject("Net");
obj.AddComponent();
}
NetMgr.Instance.Connect("127.0.0.1", 8080);
}
}
Lesson7
using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;
public class Lesson7 : MonoBehaviour
{
public Button btn;
public Button btn1;
public Button btn2;
public Button btn3;
public InputField input;
// Start is called before the first frame update
void Start()
{
btn.onClick.AddListener(() =>
{
print(1);
PlayerMsg ms = new PlayerMsg();
ms.playerID = 1111;
ms.playerData = new PlayerData();
ms.playerData.name = "缘笙箫客户端发送的信息";
ms.playerData.atk = 22;
ms.playerData.lev = 10;
NetMgr.Instance.Send(ms);
});
//黏包测试
btn1.onClick.AddListener(() =>
{
PlayerMsg msg = new PlayerMsg();
msg.playerID = 1001;
msg.playerData = new PlayerData();
msg.playerData.name = "缘笙箫1";
msg.playerData.atk = 1;
msg.playerData.lev = 1;
PlayerMsg msg2 = new PlayerMsg();
msg2.playerID = 1002;
msg2.playerData = new PlayerData();
msg2.playerData.name = "缘笙箫2";
msg2.playerData.atk = 2;
msg2.playerData.lev = 2;
//黏包
byte[] bytes = new byte[msg.GetBytesNum() + msg2.GetBytesNum()];
msg.Writeing().CopyTo(bytes, 0);
msg2.Writeing().CopyTo(bytes, msg.GetBytesNum());
NetMgr.Instance.SendTest(bytes);
});
//分包测试
btn2.onClick.AddListener(async () =>
{
PlayerMsg msg = new PlayerMsg();
msg.playerID = 1003;
msg.playerData = new PlayerData();
msg.playerData.name = "缘笙箫1";
msg.playerData.atk = 3;
msg.playerData.lev = 3;
byte[] bytes = msg.Writeing();
//分包
byte[] bytes1 = new byte[10];
byte[] bytes2 = new byte[bytes.Length - 10];
//分成第一个包
Array.Copy(bytes, 0, bytes1, 0, 10);
//第二个包
Array.Copy(bytes, 10, bytes2, 0, bytes.Length - 10);
NetMgr.Instance.SendTest(bytes1);
await Task.Delay(500);
NetMgr.Instance.SendTest(bytes2);
});
//分包、黏包测试
btn3.onClick.AddListener(async () =>
{
PlayerMsg msg = new PlayerMsg();
msg.playerID = 1001;
msg.playerData = new PlayerData();
msg.playerData.name = "缘笙箫1";
msg.playerData.atk = 1;
msg.playerData.lev = 1;
PlayerMsg msg2 = new PlayerMsg();
msg2.playerID = 1002;
msg2.playerData = new PlayerData();
msg2.playerData.name = "缘笙箫2";
msg2.playerData.atk = 2;
msg2.playerData.lev = 2;
byte[] bytes1 = msg.Writeing();//消息A
byte[] bytes2 = msg2.Writeing();//消息B
byte[] bytes2_1 = new byte[10];
byte[] bytes2_2 = new byte[bytes2.Length - 10];
//分成第一个包
Array.Copy(bytes2, 0, bytes2_1, 0, 10);
//第二个包
Array.Copy(bytes2, 10, bytes2_2, 0, bytes2.Length - 10);
//消息A和消息B前一段的 黏包
byte[] bytes = new byte[bytes1.Length + bytes2_1.Length];
bytes1.CopyTo(bytes, 0);
bytes2_1.CopyTo(bytes, bytes1.Length);
NetMgr.Instance.SendTest(bytes);
await Task.Delay(500);
NetMgr.Instance.SendTest(bytes2_2);
});
}
// Update is called once per frame
void Update()
{
}
}