一、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,
最新的代码
//线程平安的字典 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粘包涌现的题目处置惩罚的示例的细致内容,更多请关注ki4网别的相干文章!