一般所谓的TCP粘包是在一次接收数据不能完全地体现一个完整的消息数据。TCP通讯为何存在粘包呢?主要原因是TCP是以流的方式来处理数据,再加上网络上MTU的往往小于在应用处理的消息数据,所以就会引发一次接收的数据无法满足消息的需要,导致粘包的存在。处理粘包的唯一方法就是制定应用层的数据通讯协议,通过协议来规范现有接收的数据是否满足消息数据的需要。在应用中处理粘包的基础方法主要有两种分别是以4节字描述消息大小或以结束符,实际上也有两者相结合的如HTTP,redis的通讯协议等。
在平时交流过程发现一些朋友即使做了这些协议的处理,但有时在处理数据的时候也会出现数据不对的情况。这主要原因他们在一些个别情况下没有处理好。因为当一系列的消息发送过来的时候,对于4节字头或结束符分布位置都是不确定的。一种简单的情况就是当前消息处理完成后,紧接着就是处理一下个消息的4节字描述,但在实际情况下当前接收的buffer剩下的内容有可能不足4节字的。如果你想通过通讯的程序来测这情况相对来说触发的机率性不高,所以对于协议分析的功能最好通过单元测试来模拟。
通过下面这个图可以更清晰地了解协议标记数据分布的情况
下面简单地介绍一下4字节描述大小和结束符和处理方式。
4字节大小描述方式
1 public void Import(byte[] data, int start, int count) 2 { 3 while (count > 0) 4 { 5 if (!mLoading) 6 { 7 mCheckSize.Reset(); 8 mStream.SetLength(0); 9 mStream.Position = 0; 10 mLoading = true; 11 } 12 if (mCheckSize.Length == -1) 13 { 14 while (count > 0 && mCheckSize.Length == -1) 15 { 16 mCheckSize.Import(data[start]); 17 start++; 18 count--; 19 } 20 } 21 else 22 { 23 if (OnImport(data, ref start, ref count)) 24 { 25 mLoading = false; 26 if (Receive != null) 27 { 28 mStream.Position = 0; 29 Receive(mStream); 30 } 31 } 32 } 33 } 34 } 35 36 37 public void Import(byte value) 38 { 39 LengthData[mIndex] = value; 40 if (mIndex == 3) 41 { 42 Length = BitConverter.ToInt32(LengthData, 0); 43 if (!LittleEndian) 44 Length = Endian.SwapInt32(Length); 45 } 46 else 47 { 48 mIndex++; 49 } 50 }
代码很简单如果没有长度描述的情况就把数据导入到消息长度描述的buffer中,如果当前buffer满足4位的情况直接得到相应长度。后面的工作就是获取相应长度的buffer即可。
结束符方式
1 public void Import(byte[] data, int start, int count) 2 { 3 while (count > 0) 4 { 5 if (!mLoading) 6 { 7 mStream.SetLength(0); 8 mStream.Position = 0; 9 mLoading = true; 10 } 11 if (data[x] == mEof[0]) 12 { 13 start += mEof.Length; 14 count -= mEof.Length; 15 mLoading = false; 16 if (Receive != null) 17 { 18 mStream.Position = 0; 19 Receive(mStream); 20 } 21 } 22 else 23 { 24 mStream.Write(data[start]); 25 start++; 26 count--; 27 } 28 } 29 }
结束符的处理方式就相对来说简单多了。
以上就是两种TCP数据处理粘包的情况,相关代码紧供参考。