1.什么是黏包;
将多条完整的活不完整的消息黏在一起发送发送出去,TCP为解决性能问题,所以他进行黏包。
2.什么是分包:
发送的数量量很大,一条消息多次发送,TCP就会分开发送,一个包被分开10次,服务器就会recive10次
3.在传输层就被粘包和分包了,我们只能在应用层处理。
4.如何解决粘包和分包问题:
用封包和拆包解决
1.封包:
定义消息协议类,
由5大属性组成,分别是:
【(①=1个int)(②=1个int)(③=1个int)(④=1个byte[])(⑤=1个byte[])】
解释一下这个【消息协议类】:
(①=1个int):这个属性占4个字节,可以放一个0到2147483647的整数,我称作1号标志;
(②=1个int):这个属性占1个字节,可以放一个0到2147483647的整数,我称作2号标志。那么1号标志和2号标志就有多达2147483647×2147483647个组合,我们用它来自定义这个消息的标志,比如,0-0表示登录请求消息,1-1表示物理攻击,1-2表示魔法攻击,3-3表示坐标移动;
(③=1个int):这个属性占4个字节,可以放一个0到2147483647的整数,它表示(④=1个byte[])的长度;
(④=1个byte[]):这个属性存着你要发送的全部消息体字节,所以,你的消息体需要被转化为字节数组才可存放进去;
(⑤=1个byte[]):这个属性存着【多余的消息体字节】。那么问题来了,什么是【多余的消息体字节】
再解释一下这个【消息协议类】具体怎么用。
1,【消息发送方】先定义【消息协议类】①②(一级协议和二级协议)属性,也就是随便写2个数;
2,【消息发送方】再将消息“装入”【消息协议类】的④属性,那么③属性就有了;
3,【消息发送方】将封装好的【消息协议类】转为byte[],发送给【消息接收方】,我们把这道工序称作【封包】;
2.拆包
4,【消息接收方】接收到【消息发送方】发来的byte[]时,先判断这个byte[]长度是否大于6,即是否大于①属性+②属性+③属性的长度和,如果byte[]长度小于 【消息接收方】就不做处理循环继续接收;
5,【消息接收方】接收到【消息发送方】发来的byte[]长度大于等于6了(包含一个完整的消息)!则将byte[]还原为【消息协议类】,为了区别,我们暂时把它为【新消息协议类】;
6,循环判断【消息发送方】发来的byte[]长度减去6之后的值是否大于等于【新消息协议类】的③的值。这个可以理解为byte[]是否为一个完整的【消息协议类】,如果是就把【新消息协议类】的④属性拆出来,就得到了一个刚刚好完整的消息,不多也不少。那么,我们就把这道工序称作【拆包】;
7,相信你已经反应过来⑤这个【多余的消息体字节】是干嘛用的了。上一步当中,如果byte[]信息刚好是一个完整长度自然用不到⑤了,但是在网络传输中byte[]自然不会永远那么刚好了,所以当byte[]长度大于一个完整消息时,就把多于的byte放入⑤当中,和下次新接收的byte[]组合在一起,再次进行这样的循环,保证数据的完整性和独立性。
代码如下
一:创建一个消息类
using UnityEngine;
using System.Collections;
using System.IO;
namespace LuaFramework.NetWork
{
///
/// 消息协议类
///
public class MessageProtocol
{
public int oneNumber;//一级协议
public int twoNumber;//二级协议
public int length;//实际数据长度
public byte[] buffer=new byte[] { };//实际消息数据
public byte[] duoYuBytes=new byte[] { };//多余数据字节数组
#region 构造
public MessageProtocol() { }
public MessageProtocol(int oneNumber, int twoNumber, int length, byte[] buffer)
{
this.oneNumber = oneNumber;
this.twoNumber = twoNumber;
this.buffer = buffer;
this.length = this.buffer.Length;
}
#endregion
///
/// 将消息协议对象转化字节数组
///
///
public byte[] ToBytes()
{
byte[] bytes;//自定义字节数组,用以装载消息协议
using (MemoryStream memorySteam = new MemoryStream())//创建内存流
{
BinaryWriter binaryWriter = new BinaryWriter(memorySteam);//以二进制写入器往这个流里写内容
binaryWriter.Write(this.oneNumber);//写入协议一级标志,占4个字节
binaryWriter.Write(this.twoNumber);//写入协议二级标志,占4个字节
binaryWriter.Write(this.length);//写入消息的长度,占4个字节
if (this.length > 0)
{
binaryWriter.Write(this.buffer);//写入消息实际内容
}
bytes = memorySteam.ToArray();
binaryWriter.Close();
}
return bytes;
}
///
/// 从字节数组得到消息类对象
///
///
///
public static MessageProtocol FromBytes(byte[] data)
{
int dataLength = data.Length;
MessageProtocol protocol = new MessageProtocol();
using (MemoryStream memoryStream = new MemoryStream(data))//将字节数组填充至内存流
{
BinaryReader binaryReader = new BinaryReader(memoryStream);//以二进制读取器读取该流内容
protocol.oneNumber = binaryReader.ReadInt32();//读取一级协议,占4字节
protocol.twoNumber = binaryReader.ReadInt32();//读取二级协议,占4字节
protocol.length = binaryReader.ReadInt32();//读取数据的长度,占4字节
//如果【进来的Bytes长度】大于【一个完整的MessageXieYi长度】
if (dataLength - 12 > protocol.length)
{
protocol.buffer = binaryReader.ReadBytes(protocol.length);//读取实际消息的内容,从第13个字节开始读取,长度是消息的场地
protocol.duoYuBytes = binaryReader.ReadBytes(dataLength - 12 - protocol.length);//读取多余字节的数据
}
//如果【进来的Bytes长度】等于于【一个完整的MessageXieYi长度】
if (dataLength - 12 == protocol.length)
{
protocol.buffer = binaryReader.ReadBytes(protocol.length);
}
binaryReader.Close();
}
return protocol;
}
///
/// 按照先后顺序合并2个字节数组,并返回合并后的字节数组
///
/// 第一个字节数组
/// 第一个字节数组的开始截取索引
/// 第一个字节数组的截取长度
/// 第二个字节数组
/// 第二个字节数组的开始截取索引
/// 第二个字节数组的截取长度
///
public static byte[] CombineBytes(byte[] firstBytes, int firstIndex, int firstLength, byte[] secondBytes, int secondIndex, int secondLength)
{
byte[] buffer;
using (MemoryStream memoryStream = new MemoryStream())//创建内存流
{
BinaryWriter binaryWriter = new BinaryWriter(memoryStream);//创建二进制写入器,往流中写入数据
binaryWriter.Write(firstBytes, firstIndex, firstLength);//写入第一个字节数组
binaryWriter.Write(secondBytes, secondIndex, secondLength);//写入第二个字节数组
buffer = memoryStream.ToArray();
binaryWriter.Close();
}
return buffer;
}
}
}
二:创建MessageHandle处理接收到的消息
using UnityEngine;
using System.Collections;
using System;
namespace LuaFramework.NetWork
{
public class MessageHandle
{
public byte[] buffer = new byte[] { };//数据动态缓存区
public MessageHandle() { }
///
/// 处理接收的数据
///
///
///
public void HandleMessage(byte[] data, int count)
{
buffer = MessageProtocol.CombineBytes(buffer, 0, buffer.Length, data, 0, data.Length);
while (true)
{
MessageProtocol protocol = MessageProtocol.FromBytes(data);
if (buffer.Length < 12)//接收的数据不到12个字节不处理
{
return;
}
else
{
//消息对象的包头
protocol = MessageProtocol.FromBytes(buffer);
int firstFlag = protocol.oneNumber;
int secondFlag = protocol.twoNumber;
int msgContentLength = protocol.length;
while (buffer.Length - 12 >= msgContentLength)
{
protocol = null;
protocol = MessageProtocol.FromBytes(buffer);
//取出消息内容,派发消息
Debug.Log(BitConverter.ToString(protocol.buffer));
//再讲多余的数据重新复制给动态数组,此时的动态数组只包含多余的字节
buffer = protocol.duoYuBytes;
if (buffer.Length >= 12)
{
continue;
}
else
{
break;
}
}
}
}
}
}
}
三:创建SocketClient处理连接服务器,接收服务器的消息,向服务器发送消息
using UnityEngine;
using System.Net.Sockets;
using System.Net;
using System;
using System.Threading;
namespace LuaFramework.NetWork
{
public class SocketClient
{
#region 单利
private static SocketClient instance = null;
public static SocketClient Instance
{
get
{
if (instance == null)
{
instance = new SocketClient();
}
return instance;
}
}
#endregion
public Socket client;
private Thread thread;
private MessageHandle messageHandle;
private string ipAdress;
private int port;
public bool isConnect = false;
private byte[] buffer;//接收数据的缓存区
///
/// 连接服务器外部接口
///
///
///
public void Init(string ipAdress, int port)
{
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
messageHandle = new MessageHandle();
this.ipAdress = ipAdress;
this.port = port;
thread = new Thread(ConnectServer);
thread.Start();
}
#region 连接服务器内部
private void ConnectServer()
{
client = null;
try
{
IPAddress[] address = Dns.GetHostAddresses(ipAdress);
if (address.Length == 0)
{
Debug.LogError("host invalid");
return;
}
if (address[0].AddressFamily == AddressFamily.InterNetworkV6)
{
client = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
}
else
{
client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
}
client.SendTimeout = 1000;//设置发送超时时间,超时断开连接
client.ReceiveTimeout = 1000;//设置接收超时时间
client.NoDelay = true;
client.BeginConnect(address, port, ConnectCallBack, client);//开始异步连接服务器
}
catch (Exception e)
{
Debug.Log("连接失败,断开连接");
Close();
}
}
private void ConnectCallBack(IAsyncResult ar)
{
try
{
client.EndConnect(ar);//连接服务器成功
isConnect = true;
Receive();//开始读取数据
}
catch (Exception e)
{
Close();
}
}
#endregion
#region 接收数据
private void Receive()
{
try
{
buffer = new byte[10240];
client.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, ReceiveCallBack, client);
}
catch (Exception e)
{
Close();
}
}
private void ReceiveCallBack(IAsyncResult ar)
{
try
{
int count = client.EndReceive(ar);
if (count == 0)
{
Close();
return;
}
messageHandle.HandleMessage(buffer, count);//处理数据
Receive();//再次接收数据
}
catch (Exception e)
{
Close();
}
}
#endregion
#region 发送数据
public void Send(byte[] data)
{
if (client != null && client.Connected)
{
Debug.Log("向服务器发送的字节数BeginSend" + data.Length);
client.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallBack, client);
}
else
{
Close();
}
}
private void SendCallBack(IAsyncResult ar)
{
try
{
int count = client.EndSend(ar);
Debug.Log("向服务器发送的字节数EndSend" + count);
}
catch (Exception e)
{
Close();
}
}
#endregion
///
/// 关闭连接
///
public void Close()
{
//一旦断开连接 走重新登录
if (thread != null)
{
thread.Abort();
thread = null;
}
if (client != null)
{
client.Close();
isConnect = false;
client = null;
}
}
///
/// 判断是否处于连接
///
public void IsConnected()
{
if (client.Connected == false)
{
Close();
}
}
}
}