通讯数据处理规则定义加字节数据处理
大家好,我是你们的好朋友程序员:铭文
先简单说下具体的这篇文章我会详细的讲解下我们通信中数据为了防止粘包,分包,粘包的话存在的问题,解决思路
,和一些源码,需要所以源码的可以私聊我。我准备基于socket通信来讲,Socket通信这个不必再说了,不会的朋友
可以看我以前博客的地址:https://blog.csdn.net/weixin_44957831/article/details/106197901
一:需要交互数据格式的环境
我们上位机和下位机做数据的交互:
1.下位机提交数据 出现问题了,数据不完整了。怎么解决判断这个数据到底完整与否呢。这个时候就需要传输下这个数据的长度了。这个就是一个关键的点了。
2.下位机提交的数据:因为是单片机程序,提交的数据大小也会有问题,可以服务器上面开辟的缓存空间:[1024 * 1024 * 3] 很大了,接受没有问题,但是下位机提交的数据,受限制于框架和单片机程序的负载能力,数据提交的时候,很多情况下会出现分包的情况。先解决这个分包的情况:数据分包了绝对会有第一条数据第二条数据的情况吧。这个时候我们就要再次定义一个关键点,指针标识这条紧跟的数据是不是第一条数据。
二:具体的定义模式:
直接上干货,不墨迹直接上处理思路和处理模式,有不明白的地方可以给我留言,
1.我们的数据必须设计成包头加包体的模式。
比如:
包头:@@@@1234
包体:具体提交过来的数据Data;
包头里面我们要加一层业务:@@@@说明的是这个数据的开头。1234代表的是包体数据的字节长度。这样就可以很简单的来实现我们的分包操作了。
2.分包算法:基本思路就是:我们创建一个缓存来存放拼接的数据可以用字节数据来操作。结合并发字典(ConcurrentDictionary:key:来存放socket客户端的句柄,value:来存放当前客户端提交的数据就可以了)来实现。用的方法也是比较常用的具体的用法可以看看官方文档地址:
链接: ConcurrentDictionary官方文档详细解释.
写一个方法来处理我上面说的那些操作,可以用Buffer.BlockCopy方法来操作字节对象。
Buffer.BlockCopy用法(我摸索出来的,没找到文档):
Buffer.BlockCopy(操作的数据源,偏移量从第几个开始截取,剪切到的目标地方
,开始的下坐标,结束的下坐标不包含这个坐标)
这样基本就可以实现分包合包等一系列的操作了。非常的简单希望可以帮到大家不明白的地方可以给我留言。
直接上代码了关键的地方我会加一些注释希望可以帮到大家。
下面展示一些 代码
。
调取方法:
//操作句柄和数据源结合。outputb就是分包的数据
OnReceive(token.Socket.Handle, outputb)
//包头长度 固定4
static int headSize = 4;
//定义并发字典
static ConcurrentDictionary<IntPtr, byte[]> dic = new ConcurrentDictionary<IntPtr, byte[]>();
//下面直接上方法 有注释的
/// <summary>
/// 接收客户端发来的数据
/// </summary>
/// <param name="connId">每个客户的会话ID</param>
/// <param name="bytes">缓冲区数据</param>
/// <returns></returns>
public static string OnReceive(IntPtr connId, byte[] bytesin)
{
//提交的用户内存数据
byte[] surplusBuffer = null;
int totalLen = 0;
string DataLengStr = "";
表头数据
//byte[] Headerbyte = new byte[8];
//前四条是否为第一条数据 就是$的过滤
byte[] HeaderIn = new byte[4];
//数据长度
byte[] DataLeng = new byte[4];
byte[] bytes;
if (bytesin.Length > 4)
{
bytes = new byte[bytesin.Length - 4];
}
else
{
bytes = new byte[bytesin.Length];
}
if (bytesin.Length > 8)
{
byte[] DataSouLeng = new byte[bytesin.Length - 4];
//Buffer.BlockCopy(bytesin, 0, Headerbyte, 0, 8);//从缓冲区里读取包头的字节
Buffer.BlockCopy(bytesin, 0, HeaderIn, 0, 4);//从缓冲区里读取包头的字节
Buffer.BlockCopy(bytesin, 4, DataLeng, 0, 4);//从缓冲区里读取包头的字节
string HeaderInString = Encoding.Default.GetString(HeaderIn);
DataLengStr = Encoding.Default.GetString(DataLeng);
//如果是第一条数据
if (HeaderInString == "@@@@")
{
byte[] xbtye = null;
dic.TryRemove(connId, out xbtye);
surplusBuffer = null; //设置空 回到原始状态
totalLen = 0; //清0
//重新拼接数据
Buffer.BlockCopy(bytesin, 4, DataSouLeng, 0, bytesin.Length - 4);
bytes = DataSouLeng;
}
else
{
bytes = bytesin;
}
}
String strc = "";
//bytes 为系统缓冲区数据
//bytesRead为系统缓冲区长度
int bytesRead = bytes.Length;
if (bytesRead > 0)
{
if (dic.TryGetValue(connId, out surplusBuffer))
{
byte[] curBuffer = surplusBuffer.Concat(bytes).ToArray();//拼接上一次剩余的包
//更新会话ID 的最新字节
dic.TryUpdate(connId, curBuffer, surplusBuffer);
surplusBuffer = curBuffer;//同步
}
else
{
//添加会话ID的bytes
dic.TryAdd(connId, bytes);
surplusBuffer = bytes;//同步
}
//已经完成读取每个数据包长度
int haveRead = 0;
//这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
totalLen = surplusBuffer.Length;
while (haveRead <= totalLen)
{
//如果在N此拆解后剩余的数据包连一个包头的长度都不够
//说明是上次读取N个完整数据包后,剩下的最后一个非完整的数据包
if (totalLen - haveRead < headSize)
{
byte[] byteSub = new byte[totalLen - haveRead];
//把剩下不够一个完整的数据包存起来
Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
dic.TryUpdate(connId, byteSub, surplusBuffer);
surplusBuffer = byteSub;
totalLen = 0;
break;
}
//如果够了一个完整包,则读取包头的数据
byte[] headByte = new byte[headSize];
Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节
int bodySize = Convert.ToInt32(Encoding.Default.GetString(headByte));//从包头里面分析出包体的长度
//BitConverter.GetBytes();
//byte[] headByte3 = StringToByte("11");
//这里的 haveRead=等于N个数据包的长度 从0开始;0,1,2,3....N
//如果自定义缓冲区拆解N个包后的长度 大于 总长度,说最后一段数据不够一个完整的包了,拆出来保存
if (haveRead + headSize + bodySize > totalLen)
{
byte[] byteSub = new byte[totalLen - haveRead];
Buffer.BlockCopy(surplusBuffer, haveRead, byteSub, 0, totalLen - haveRead);
dic.TryUpdate(connId, byteSub, surplusBuffer);
surplusBuffer = byteSub;
break;
}
else
{
//挨个分解每个包,解析成实际文字
//Encoding.Default.GetString,UTF8
strc = Encoding.Default.GetString(surplusBuffer, haveRead + headSize, bodySize);
//strc = (string.Format(" > {0}[OnReceive] -> {1}", connId, strc));
//依次累加当前的数据包的长度
haveRead = haveRead + headSize + bodySize;
//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
if (headSize + bodySize == surplusBuffer.Length)
{
byte[] xbtye = null;
dic.TryRemove(connId, out xbtye);
surplusBuffer = null;//设置空 回到原始状态
totalLen = 0;//清0
}
}
}
}
return strc;
}
上面就是实现了数据的分包粘包等操作。操作的也是原始的字节数据。
调取的话就非常简单了。直接传输数据源和socket指针便可。
调取方法:
//操作句柄和数据源结合
OnReceive(token.Socket.Handle, outputb)