[网络]Unity的Socket通讯_3_粘包丢包
2015-10-08 01:52
489 查看
好了,现在啥数据都能通讯了,功能也都能实现了。。
可是,为毛有时候服务端发了2条数据客户端只收到了一个?有时候服务器发送一条数据客户端居然没反映。
等等,之前咱们定义了byte[] receiveData=new byte[1024],是不是太小了,咱们给他变成2048,4096…………
好吧,现在服务器发送一条数据基本都有反映了,可是为什么有时发两条,却只收一条呢?
来检测下数据包吧,还记得第一篇写的Receive方法吗?
通过Debug,我们发现有的时候收到的是512,有的时候是1024,有时候甚至是1536,好奇怪啊,有些跟每次服务器发送的包显示的大小不一样啊。
好吧,原来Socket有时候会把几个包打包成一个包发送了,有时候会把一个包拆成几个包发送(这个根据byte[]定义的大小决定)
关于Socket 分包,粘包的问题,可以自行百度。
这怎么办,而且万一传一个几G的数据呢,我总不能预先定义个空byte[]吧。
看来我们得给数据包添加点东西,也是通用的公认的解决办法,那就是包头。
我们来改下上一篇的转换代码,看看如何加入包头。
这样,我们就能准确的知道每个数据包中数据的大小,准确的取出对应的数据了。
等等,,我们只是知道了数据包的大小,但没有解决粘包的问题啊。而且万一服务端要发送大包,把包拆开发过来了,怎么办?
看来我们得写个缓存,每当有数据进来了,就放进这个缓存里面,然后我们就判断包头,如果包头的长度小于缓存了,我们就取出一个数据包,如果小于了,就继续等待接受。
事实上,聪明的人应该已经知道了,网络通讯本身就是以流的形式这样发送的,可不是一下给你发送一个完整的包,只不过Socket帮咱们简化了这其中的步骤而已。
那我们就封装一个简单的缓存吧。
这样我们在Receive方法中略作更改即可。
这里,我新建了个List<byte[]>,我记得Unity好像不允许异步读取数据,所以就建了个List,然后通过Update方法,去判断里面是否有数据,有就执行。
至于属性为什么这么写,还记得我们用的是单例模式吗?
Receive里面加个while循环,每次接受到数据就会放如缓存,回调这个方法时,就会判断缓存中的数据,有没有包,如果有就取出来,直到取完为止,这样就解决粘包问题了,如果长度不够,就存在缓存中继续等待接受,这样我们就不需要new一个很大的byte[]来接受了,解决了分包问题。
好了,小伙伴们可以肆意的编写传递的数据了。
以上的内容都是自己瞎琢磨的,如果有问题,欢迎打脸。
可是,为毛有时候服务端发了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[]来接受了,解决了分包问题。
好了,小伙伴们可以肆意的编写传递的数据了。
以上的内容都是自己瞎琢磨的,如果有问题,欢迎打脸。
相关文章推荐
- java-模拟tomcat服务器
- Linux socket 初步
- Android IPC进程间通讯机制
- java socket 注意的地方
- java socket 注意的地方
- C#基于socket模拟http请求的方法
- 简单的Ruby中的Socket编程教程
- Socket不能选择本地IP连接问题如何解决
- C#之Socket操作类实例解析
- 使用C#来编写一个异步的Socket服务器
- C#使用Socket快速判断数据库连接是否正常的方法
- 科学知识:理解socket
- php与flash as3 socket通信传送文件实现代码
- 解决time_wait强制关闭socket
- C#使用Socket上传并保存图片的方法
- 深入php socket的讲解与实例分析
- Linux网络编程之UDP Socket程序示例
- Linux网络编程之socket文件传输示例
- filezilla Failed to create listen socket on port 21 for IPv4 解决办法
- php socket方式提交的post详解