赞
踩
粘包(Packet Concatenation)通常发生在基于流式传输协议(如 TCP)的通信中,因为 TCP 是面向流的传输协议,它不保证数据包的边界,而是将数据视为连续的字节流,它表示客户端发送多条消息,服务端只收到了一条消息
半包(Half Packet)与粘包问题相反。在半包问题中,接收端接收到的数据包不完整,即接收到的数据包只是完整数据包的一部分,无法完整地解析和处理。
3:原因
①网络延迟/阻塞
②发送方连续发送数据
③接收端缓冲区大小限制
④数据包丢失
在每个数据包前面加上长度信息,每次接收数据后,先读取长度,如果缓冲区数据长度大于要取的字节数,则取出相应字节,否则等待下一次接收,举个例子
①客户端第一次发送包含长度信息的内容
②客户端第二次发送包含长度信息的内容
③服务端第一次接收到了4个字节,存入缓冲区,但是这时候并不处理,因为收到了10,所以要等到11个字节完整再处理
④服务端等到客户端发送剩下的7个字节,但是第二次接收到了9个字节,服务端把之前的6个字节再读取,然后拼接,把10helloworld进行处理,读取到标志长度4,等待下次处理
⑤服务端最后一次收到ove,就把之前的l一起拼接,返回完整的4love
*一般的游戏是16位的整型数来存放长度信息
每次都发送相同长度的数据,一次不足的数据用.来补充,.位补充字符,没有实际意义
如果接收到的字符数大于10,就只提取前10个字符
规定一个结束符号作为消息的分隔符,比如Hello$World就是两条信息
- byte[] bodyBytes = System.Text.Encoding.Default.GetBytes(SendStr);
- Int16 len = (Int16)bodyBytes.Length;
- //把长度转化为Int16
- byte[] lenBytes = BitConverter.GetBytes(len);
- //此时SendBytes包含长度字符串和内容字符串
- byte[] sendBytes = lenBytes.Concat(bodyBytes).ToArray();
定义一个接收缓冲区和接收缓冲区长度。缓冲区会保存尚未处理的数据
- //接收缓冲区
- byte[] readBuff = new byte[1024];
- //接收缓冲区长度
- int buffCount = 0;
之前的BeginReceive函数原型如下:
public IAsyncResult BeginReceive ( byte[] buffer, int offset, int size, SocketFlags socketFlags, AsyncCallback callback, object state )
现在的参数应该写成这个样子:
socket.BeginReceive(readBuff, buffCount 1024-buffCount, 0, ReceiveCallback, socket);
readBuff 是缓冲区
buffCount 是开始读取的位置
1024 - buffCount 是剩余多少可读取的大小
- public void OnReceiveData(){
-
- //消息长度小于2,直接返回等待下一次接收
- if(buffCount <= 2)
- return;
-
- //消息的长度
- Int16 bodyLength = BitConverter.ToInt16(readBuff, 0);
- //消息体
-
- //如果消息长度小于我消息内容和长度的字节,就返回继续读取
- if(buffCount < 2+bodyLength)
- return;
-
- //如果长度够用,就转化为string类型
- string s = System.Text.Encoding.UTF8.GetString(readBuff, 2,buffCount);
-
- //更新缓冲区
- int start = 2 + bodyLength;
- int count = buffCount - start;
-
- //Copy函数把缓冲区后面的内容提到前面
- Array.Copy(readBuff, start, readBuff, 0, count);
- buffCount -= start;
-
- //继续读取消息
- if(readBuff.length > 2){
- OnReceiveData();
- }
- }
Copy的原型函数如下:
- public static void Copy(
- Array sourceArray,//源数组
- long sourceIndex,//目标数据
- Array destinationArray,//目标数组
- long destinationIndex,//目标数组起始位置
- long length//复制消息的长度
- )
- using System;
- using System.Linq;
- using System.Net.Sockets;
- using UnityEngine;
- using UnityEngine.UI;
-
- public class SendScr : MonoBehaviour
- {
- Socket socket;
- public InputField inputField;
- public Text text;
- byte[] readBuff = new byte[1024];
- int buffCount = 0;//缓冲区数据长度
- string recvStr = "";
- public void Connection()
- {
- socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- socket.Connect("127.0.0.1", 8888);
- socket.BeginReceive(readBuff, buffCount, 1024 - buffCount, 0, ReceiveCallback, socket);
- }
-
- private void ReceiveCallback(IAsyncResult ar)
- {
- try
- {
- Socket socket = (Socket)ar.AsyncState;
- //获取接收数据长度
- int count = socket.EndReceive(ar);
- buffCount += count;
- //处理二进制消息
- OnReceiveData();
- //继续接收数据
- socket.BeginReceive(readBuff, buffCount, 1024 - buffCount, 0, ReceiveCallback, socket);
- }
- catch (SocketException ex)
- {
- Debug.Log("Socket Receive fail" + ex.ToString());
- }
- }
-
- private void OnReceiveData()
- {
- Debug.Log("[Recv 1] buffCount = " + buffCount);
- Debug.Log("[Recv 2] readbuff = " + BitConverter.ToString(readBuff));
-
- if (buffCount <= 2) return;
-
- Int16 bodyLength = BitConverter.ToInt16(readBuff, 0);
- Debug.Log("[Recv 3] bodyLength=" + bodyLength);
-
- //消息体
- if (buffCount < 2 + bodyLength) return;
-
- string s = System.Text.Encoding.UTF8.GetString(readBuff, 2, buffCount);
- Debug.Log("[Recv 4] s=" + s);
-
- //更新缓冲区
- int start = 2 + bodyLength;
- int count = buffCount - start;
- Array.Copy(readBuff, start, readBuff, 0, count);
- buffCount -= start;
- Debug.Log("[Recv 5] buffCount=" + buffCount);
-
- //消息处理
- recvStr = s + "\n" + recvStr;
-
- //继续读取消息
- OnReceiveData();
- }
-
- public void Send()
- {
- string sendStr = inputField.text;
- byte[] bodyBytes = System.Text.Encoding.Default.GetBytes(sendStr);
- Int16 len = (Int16)bodyBytes.Length;
- byte[] lenBytes = BitConverter.GetBytes(len);
- //此时SendBytes包含长度字符串和内容字符串
- byte[] sendBytes = lenBytes.Concat(bodyBytes).ToArray();
- socket.Send(sendBytes);
- Debug.Log("[Send]" + BitConverter.ToString(sendBytes));
- }
- private void Update()
- {
- text.text = recvStr;
- }
-
- }
捋一下客户端的功能:
当进入场景点击Connection,创建新的Socket连接,绑定服务端口,开始接收信息
这里的BeginReceive就是我们接受消息的函数,它传入我们的缓冲区readBuff,缓冲区长度buffCount,1024 - buffCount表示我们还剩多少字节数据,然后进入回调函数
Receive回调函数中创建新的Socket对象来解析获取到的socket对象,count用于跟踪每次异步接收操作实际接收到的数据长度,然后把count加到buffCount上,这样更新了缓冲区的长度
然后使用OnReceiveData处理数据,继续读取数据,点击发送就调用Send()
- using System.Net.Sockets;
- using System.Net;
- internal class ClientState
- {
- public Socket socket;
- public byte[] readBuff = new byte[1024];
- }
- class Class
- {
- static Socket listenfd;
- static Dictionary<Socket, ClientState> clients = new Dictionary<Socket, ClientState>();
- public static void Main(string[] args)
- {
- listenfd = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- IPAddress ipAdr = IPAddress.Parse("127.0.0.1");
- IPEndPoint ipEp = new IPEndPoint(ipAdr, 8888);
- listenfd.Bind(ipEp);
- listenfd.Listen(0);
- Console.WriteLine("[服务器]启动成功");
-
- List<Socket> checkRead = new List<Socket>();
-
- while (true)
- {
- //填充checkRead列表
- checkRead.Clear();
- checkRead.Add(listenfd);
- foreach (ClientState s in clients.Values)
- {
- checkRead.Add(s.socket);
- }
- //select
- Socket.Select(checkRead, null, null, 1000);
- //检查可读对象
- foreach (Socket s in checkRead)
- {
- if (s == listenfd)
- {
- ReadListenfd(s);
- }
- else
- {
- ReadClientfd(s);
- }
- }
- }
- }
- public static void ReadListenfd(Socket listenfd)
- {
- Console.WriteLine("Accept");
- Socket clientfd = listenfd.Accept();
- ClientState state = new ClientState();
- state.socket = clientfd;
- clients.Add(clientfd, state);
- }
- public static bool ReadClientfd(Socket clientfd)
- {
- ClientState state = clients[clientfd];
- //接收
- int count = clientfd.Receive(state.readBuff);
- if (count == 0)
- {
- clientfd.Close();
- clients.Remove(clientfd);
- Console.WriteLine("Socket Close");
- return false;
- }
- //广播
- string recvStr = System.Text.Encoding.Default.GetString(state.readBuff, 2, count - 2);
- Console.WriteLine("Receive" + recvStr);
- byte[] sendBytes = new byte[count];
-
- Array.Copy(state.readBuff, 0, sendBytes, 0, count);
-
- foreach (ClientState cs in clients.Values)
- {
- cs.socket.Send(sendBytes);
- }
- return true;
- }
- }
这里的服务端代码和Select部分的代码基本相同
打开服务器,客户端连接后输入UNITY
①客户端反馈
Recv1的buffCount就是接收到服务端所返回的消息,服务端所返回的信息是客户端发送的拼接数组,就是UNITY+lenBytes,就是7
Rec2的readbuff就是缓冲区存储的内容
Recv3是当消息没有接受完整的时候的消息本体长度
Recv4是完整的消息内容
Recv5是接受完成UNITY后更新后的新buffCount新长度,因为没有后续的,所以是0
②服务端反馈
服务端接受到的消息
- using System;
- using System.Linq;
- using System.Net.Sockets;
- using UnityEngine;
- using UnityEngine.UI;
-
- public class SendScr : MonoBehaviour
- {
- Socket socket;
- public InputField inputField;
- public Text text;
- byte[] readBuff = new byte[1024];
- int buffCount = 0;//缓冲区数据长度
- string recvStr = "";
- public void Connection()
- {
- socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
- socket.Connect("127.0.0.1", 8888);
- socket.BeginReceive(readBuff, buffCount, 1024 - buffCount, 0, ReceiveCallback, socket);
- }
-
- private void ReceiveCallback(IAsyncResult ar)
- {
- try
- {
- Socket socket = (Socket)ar.AsyncState;
- //获取接收数据长度
- int count = socket.EndReceive(ar);
- buffCount += count;
- //处理二进制消息
- OnReceiveData();
-
- System.Threading.Thread.Sleep(1000*30);
-
- //继续接收数据
- socket.BeginReceive(readBuff, buffCount, 1024 - buffCount, 0, ReceiveCallback, socket);
- }
- catch (SocketException ex)
- {
- Debug.Log("Socket Receive fail" + ex.ToString());
- }
- }
-
- private void OnReceiveData()
- {
- Debug.Log("[Recv 1] buffCount = " + buffCount);
- Debug.Log("[Recv 2] readbuff = " + BitConverter.ToString(readBuff));
-
- if (buffCount <= 2) return;
-
- Int16 bodyLength = BitConverter.ToInt16(readBuff, 0);
- Debug.Log("[Recv 3] bodyLength=" + bodyLength);
-
- //消息体
- if (buffCount < 2 + bodyLength) return;
-
- string s = System.Text.Encoding.UTF8.GetString(readBuff, 2, buffCount);
- Debug.Log("[Recv 4] s=" + s);
-
- //更新缓冲区
- int start = 2 + bodyLength;
- int count = buffCount - start;
- Array.Copy(readBuff, start, readBuff, 0, count);
- buffCount -= start;
- Debug.Log("[Recv 5] buffCount=" + buffCount);
-
- //消息处理
- recvStr = s + "\n" + recvStr;
-
- //继续读取消息
- OnReceiveData();
- }
-
- public void Send()
- {
- string sendStr = inputField.text;
- byte[] bodyBytes = System.Text.Encoding.Default.GetBytes(sendStr);
- Int16 len = (Int16)bodyBytes.Length;
- byte[] lenBytes = BitConverter.GetBytes(len);
- //此时SendBytes包含长度字符串和内容字符串
- byte[] sendBytes = lenBytes.Concat(bodyBytes).ToArray();
- socket.Send(sendBytes);
- Debug.Log("[Send]" + BitConverter.ToString(sendBytes));
- }
- private void Update()
- {
- text.text = recvStr;
- }
-
- }
*在接收数据的时候,强制等待30s再进行下一次接收,ReceiveCallback是在子线程执行,调用Sleep函数并不会卡住主线程,客户端不会被卡住,在30s内多次发送数据,经由服务端转发,再次调用BeginReceive的时候,缓冲区有很多数据,会产生粘包。
客户端快速发送三条消息,发送不会堵塞,每次接收都会等待30s,但是接收到的消息不是在一起的,而是分开的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。