一、TCP粘包产生的原理

1.TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。
2.发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。
3.这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。、
二、解决原理及代码实现
1.采用包头(固定长度,里面存着包体的长度,发送时动态获取)+包体的传输机制。如图
HeaderSize 存放着包体的长度,其HeaderSize本身是定长4字节;
一个完整的数据包(L)=HeaderSize+BodySize;
2.分包算法
其基本思路是首先将待处理的接收数据流即系统缓冲区数据(长度设为M)强行转换成预定的结构数据形式,并从中取出结构数据长度字段L,而后根据包头计算得到第一包数据长度。
M=系统缓冲区大小;L=用户发送的数据包=HeaderSize+BodySize;
1)若L<M,则表明数据流包含多包数据,从其头部截取若干个字节存入临时缓冲区,剩余部分数据依此继续循环处理,直至结束。
2)若L=M,则表明数据流内容恰好是一完整结构数据(即用户自定义缓冲区等于系统接收缓冲区大小),直接将其存入临时缓冲区即可。
3)若L>M,则表明数据流内容尚不够构成一完整结构数据,需留待与下一包数据合并后再行处理。
4)下面是代码代码实现(HP-SOCKET框架的服务器端来接收数据)
int headSize = 4;//包头长度 固定4
byte[] surplusBuffer = null;//不完整的数据包,即用户自定义缓冲区
/// <summary>
/// 接收客户端发来的数据
/// </summary>
/// <param name="connId">每个客户的会话ID</param>
/// <param name="bytes">缓冲区数据</param>
/// <returns></returns>
private HandleResult OnReceive(IntPtr connId, byte[] bytes)
{
//bytes 为系统缓冲区数据
//bytesRead为系统缓冲区长度
int bytesRead = bytes.Length;
if (bytesRead > 0)
{
if (surplusBuffer == null)//判断是不是第一次接收,为空说是第一次
surplusBuffer = bytes;//把系统缓冲区数据放在自定义缓冲区里面
else
surplusBuffer = surplusBuffer.Concat(bytes).ToArray();//拼接上一次剩余的包
//已经完成读取每个数据包长度
int haveRead = 0;
//这里totalLen的长度有可能大于缓冲区大小的(因为 这里的surplusBuffer 是系统缓冲区+不完整的数据包)
int 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);
surplusBuffer = byteSub;
totalLen = 0;
break;
}
//如果够了一个完整包,则读取包头的数据
byte[] headByte = new byte[headSize];
Buffer.BlockCopy(surplusBuffer, haveRead, headByte, 0, headSize);//从缓冲区里读取包头的字节
int bodySize = BitConverter.ToInt32(headByte, 0);//从包头里面分析出包体的长度
//这里的 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);
surplusBuffer = byteSub;
break;
}
else
{
//挨个分解每个包,解析成实际文字
String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
//AddMsg(string.Format(" > [OnReceive] -> {0}", strc));
//依次累加当前的数据包的长度
haveRead = haveRead + headSize + bodySize;
if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
{
surplusBuffer = null;//设置空 回到原始状态
totalLen = 0;//清0
}
}
}
}
return HandleResult.Ok;
}
值此完成拆包解析文字工作。但实际上还没完成,如果这段代码是客户端接收来自服务器的数据的话就没问题了。
仔细看IntPtr connId 每个连接的会话ID
private HandleResult OnReceive(IntPtr connId, byte[] bytes)
{
}
但是服务器端还要分辨出 每个数据包是哪个会话产生的,因为服务器端是多线程,多用户的模式,第一个数据包和第二个可能来自不同会话的数据,所以上面的代码只适用于单会话模式。
下面我要解决这个问题。
采用c#安全的ConcurrentDictionary,具体参考 https://msdn.microsoft.com/zh-cn/library/dd287191(v=vs.110).aspx
最新的代码
//线程安全的字典
ConcurrentDictionary<IntPtr, byte[]> dic = new ConcurrentDictionary<IntPtr, byte[]>();
int headSize = 4;//包头长度 固定4
/// <summary>
/// 接收客户端发来的数据
/// </summary>
/// <param name="connId">每个客户的会话ID</param>
/// <param name="bytes">缓冲区数据</param>
/// <returns></returns>
private HandleResult OnReceive(IntPtr connId, byte[] bytes)
{
//bytes 为系统缓冲区数据
//bytesRead为系统缓冲区长度
int bytesRead = bytes.Length;
if (bytesRead > 0)
{
byte[] surplusBuffer = null;
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 是系统缓冲区+不完整的数据包)
int 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 = BitConverter.ToInt32(headByte, 0);//从包头里面分析出包体的长度
//这里的 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
{
//挨个分解每个包,解析成实际文字
String strc = Encoding.UTF8.GetString(surplusBuffer, haveRead + headSize, bodySize);
AddMsg(string.Format(" > {0}[OnReceive] -> {1}", connId, strc));
//依次累加当前的数据包的长度
haveRead = haveRead + headSize + bodySize;
if (headSize + bodySize == bytesRead)//如果当前接收的数据包长度正好等于缓冲区长度,则待拼接的不规则数据长度归0
{
byte[] xbtye=null;
dic.TryRemove(connId, out xbtye);
surplusBuffer = null;//设置空 回到原始状态
totalLen = 0;//清0
}
}
}
}
return HandleResult.Ok;
}
这样就解决了,多客户端会话造成的接收混乱。至此所有工作完成。以上代码就是为了参考学习,如果实在不想这么麻烦。可以直接使用HP-SOCKET通信框架的PACK模型,里面自动实现了解决粘包的问题。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# C#
# TCP
# 粘包
# TCP关闭问题详细介绍
# 详细讲解计算机网络——应用层
# 实例解说TCP连接建立及结束过程详解
# 使用Netty解决TCP粘包和拆包问题过程详解
# TCP窗口被填满问题的排查实践
# 数据包
# 自定义
# 客户端
# 放在
# 不完整
# 有可能
# 包了
# 定长
# 总长度
# 我要
# 还没
# 是由
# 是指
# 几次
# 设为
# 适用于
# 将其
# 这段
# 就没
# 第二个
相关文章:
,想在网上投简历,哪几个网站比较好?
如何通过远程VPS快速搭建个人网站?
网站制作的软件有哪些,制作微信公众号除了秀米还有哪些比较好用的平台?
测试制作网站有哪些,测试性取向的权威测试或者网站?
,如何利用word制作宣传手册?
建站之星如何实现网站加密操作?
c++ stringstream用法详解_c++字符串与数字转换利器
无锡制作网站公司有哪些,无锡优八网络科技有限公司介绍?
如何用西部建站助手快速创建专业网站?
湖州网站制作公司有哪些,浙江中蓝新能源公司官网?
实例解析Array和String方法
专业网站制作企业网站,如何制作一个企业网站,建设网站的基本步骤有哪些?
可靠的网站设计制作软件,做网站设计需要什么样的电脑配置?
宝塔建站助手安装配置与建站模板使用全流程解析
如何通过FTP服务器快速搭建网站?
怎么将XML数据可视化 D3.js加载XML
c# 在高并发下使用反射发射(Reflection.Emit)的性能
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
如何用wdcp快速搭建高效网站?
,制作一个手机app网站要多少钱?
如何在阿里云通过域名搭建网站?
成都网站制作公司哪家好,四川省职工服务网是做什么用?
建站ABC备案流程中有哪些关键注意事项?
盘锦网站制作公司,盘锦大洼有多少5G网站?
mc皮肤壁纸制作器,苹果平板怎么设置自己想要的壁纸我的世界?
Python路径拼接规范_跨平台处理说明【指导】
网站制作与设计教程,如何制作一个企业网站,建设网站的基本步骤有哪些?
台州网站建设制作公司,浙江手机无犯罪记录证明怎么开?
成都品牌网站制作公司,成都营业执照年报网上怎么办理?
惠州网站建设制作推广,惠州市华视达文化传媒有限公司怎么样?
c++怎么实现高并发下的无锁队列_c++ std::atomic原子变量与CAS操作【详解】
攀枝花网站建设,攀枝花营业执照网上怎么年审?
北京网站制作公司哪家好一点,北京租房网站有哪些?
如何在橙子建站中快速调整背景颜色?
官网网站制作腾讯审核要多久,联想路由器newifi官网
免费公司网站制作软件,如何申请免费主页空间做自己的网站?
建站VPS选购需注意哪些关键参数?
保定网站制作方案定制,保定招聘的渠道有哪些?找工作的人一般都去哪里看招聘信息?
建站主机选择指南:服务器配置与SEO优化实战技巧
如何自定义建站之星网站的导航菜单样式?
个人网站制作流程图片大全,个人网站如何注销?
建站之星IIS配置教程:代码生成技巧与站点搭建指南
如何快速搭建虚拟主机网站?新手必看指南
建站之星CMS建站配置指南:模板选择与SEO优化技巧
Thinkphp 中 distinct 的用法解析
如何通过NAT技术实现内网高效建站?
如何快速搭建高效香港服务器网站?
赚钱网站制作软件,建一个网站怎样才能赚钱?是如何盈利的?
制作网站的软件下载免费,今日头条开宝箱老是需要下载怎么回事?
深圳 网站制作,深圳招聘网站哪个比较好一点啊?
*请认真填写需求信息,我们会在24小时内与您取得联系。