您的位置:首页 > 理论基础 > 计算机网络

[网络]Unity的Socket通讯_3_粘包丢包

2015-10-08 01:52 489 查看
好了,现在啥数据都能通讯了,功能也都能实现了。。

可是,为毛有时候服务端发了2条数据客户端只收到了一个?有时候服务器发送一条数据客户端居然没反映。

等等,之前咱们定义了byte[] receiveData=new byte[1024],是不是太小了,咱们给他变成2048,4096…………

好吧,现在服务器发送一条数据基本都有反映了,可是为什么有时发两条,却只收一条呢?

来检测下数据包吧,还记得第一篇写的Receive方法吗?

private void Receive(IAsyncResult ar)
{
int size = client.EndReceive(ar);
Debug.Log("收到数据" + size);
if (size < 1)
{
Debug.Log("服务器关闭");
closeConnect();
return;
}
Debug.Log(Encoding.UTF8.GetString(receiveData,0,size));
//Handle Date
client.BeginReceive(receiveData, 0, receiveData.Length, SocketFlags.None, new AsyncCallback(Receive), null);
}


通过Debug,我们发现有的时候收到的是512,有的时候是1024,有时候甚至是1536,好奇怪啊,有些跟每次服务器发送的包显示的大小不一样啊。

好吧,原来Socket有时候会把几个包打包成一个包发送了,有时候会把一个包拆成几个包发送(这个根据byte[]定义的大小决定)

关于Socket 分包,粘包的问题,可以自行百度。

这怎么办,而且万一传一个几G的数据呢,我总不能预先定义个空byte[]吧。

看来我们得给数据包添加点东西,也是通用的公认的解决办法,那就是包头。

我们来改下上一篇的转换代码,看看如何加入包头。

public static byte[] ObjectToByte(object obj)
{
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(ms, obj);
byte[] data = ms.GetBuffer();//取出byte[]
byte[] head =BitConverter.GetBytes(data.Length);//获得数据长度
byte[] newData = new byte[head.Length + data.Length];//算出数据加包头的总大小
Array.Copy(head, 0, newData, 0, head.Length);//先把包头存进去
Array.Copy(data, 0, newData, head.Length, data.Length);//再把数据存进去,起始位置要设成包头的后面噢
return newData;
}
}
public static object ByteToObject(byte[] data)
{
using (MemoryStream ms = new MemoryStream(data))
{
BinaryFormatter bf = new BinaryFormatter();
return bf.Deserialize(ms);
}
}
[Serializable]//可被序列化
struct Person
{
public string name;
public int age;
}
public void Test()
{
Person P1 = new Person();
P1.name = "叶良辰";
P1.age = 250;
byte[] data = ObjectToByte(P1);
int length = BitConverter.ToInt32(data,0);//当收到数据时,先获取包头
byte[] newData = new byte[length];//建立对应长度的byte[]
Array.Copy(data, 4, newData,0,length);//把数据以包头的长度读取出来,这里的4是int的长度,也可以写成sizeof(int)
Person P2 = (Person)ByteToObject(newData);
Debug.Log("姓名:" + P2.name + "\n年龄:" + P2.age);
}


这样,我们就能准确的知道每个数据包中数据的大小,准确的取出对应的数据了。

等等,,我们只是知道了数据包的大小,但没有解决粘包的问题啊。而且万一服务端要发送大包,把包拆开发过来了,怎么办?

看来我们得写个缓存,每当有数据进来了,就放进这个缓存里面,然后我们就判断包头,如果包头的长度小于缓存了,我们就取出一个数据包,如果小于了,就继续等待接受。

事实上,聪明的人应该已经知道了,网络通讯本身就是以流的形式这样发送的,可不是一下给你发送一个完整的包,只不过Socket帮咱们简化了这其中的步骤而已。

那我们就封装一个简单的缓存吧。

class ByteBuffer
{
private byte[] buf = new byte[0];//申请
private int dataLength = 0;
public int ReadShort { get { dataLength = BitConverter.ToInt16(buf, 0); return dataLength; } }//读取数据包的长度
public int Length { get { return buf.Length; } }//获取缓存目前的总长度
public byte[] ReadByte()//读取数据
{
if (ReadShort  < Length-4)//如果长度不够,直接跳出
return null;
byte[] readByte = new byte[dataLength];//获得数据包的长度
Array.Copy(buf, 4, readByte, 0, readByte.Length);//复制对应长度的数据
int readIndex = 4 + dataLength;//下个数据包的位置
byte[] newbuf = new byte[buf.Length - readIndex];//新数据的大小
Array.Copy(buf, readIndex, newbuf, 0, newbuf.Length);
buf = newbuf;//将取出数据后的剩余数据覆盖到缓存中
return readByte;
}
public void WriteByte(byte[] data)//写入数据
{
byte[] newbuf = new byte[buf.Length + data.Length];//缓存增加对应长度
Array.Copy(buf, 0, newbuf, 0, buf.Length);
Array.Copy(data, 0, newbuf, buf.Length, data.Length);//数据复制进来
buf = newbuf;//覆盖缓存
}
}
很简单的一个类,这个类的主要功能是,动态读写数据,检查包头。

这样我们在Receive方法中略作更改即可。

public static List<byte[]> Data { set { connectserver.data = value; } get { return connectserver.data; } }//储存数据列表
private List<byte[]> data = new List<byte[]>();
ByteBuffer dataBuffer = new ByteBuffer();
private void Receive(IAsyncResult ar)
{
int size = client.EndReceive(ar);
//Debug.Log("收到数据" + size);
if (size < 1)
{
Debug.Log("服务器关闭");
closeConnect();
return;
}
while (dataBuffer.Length > 4)//判断包大于4解包
{
if (dataBuffer.ReadShort > dataBuffer.Length - 4)//判断数据是否大于包头
break;
else
data.Add(dataBuffer.ReadByte());//将缓存的数据取出放入数据列表等待处理
}
client.BeginReceive(receiveData, 0, receiveData.Length, SocketFlags.None, new AsyncCallback(Receive), null);
}


这里,我新建了个List<byte[]>,我记得Unity好像不允许异步读取数据,所以就建了个List,然后通过Update方法,去判断里面是否有数据,有就执行。

至于属性为什么这么写,还记得我们用的是单例模式吗?

Receive里面加个while循环,每次接受到数据就会放如缓存,回调这个方法时,就会判断缓存中的数据,有没有包,如果有就取出来,直到取完为止,这样就解决粘包问题了,如果长度不够,就存在缓存中继续等待接受,这样我们就不需要new一个很大的byte[]来接受了,解决了分包问题。

好了,小伙伴们可以肆意的编写传递的数据了。

以上的内容都是自己瞎琢磨的,如果有问题,欢迎打脸。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  Unity Socket 通讯 粘包