Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

分包、黏包

一、什么是分包、黏包

分包、黏包指在网络通信中由于各种因素(网络环境、API规则等)造成的消息与消息之间出现的两种状态 - 分包:一个消息分成了多个消息进行发送 - 黏包:一个消息和另一个消息黏在了一起 - 注意:分包和黏包可能同时发生 分包、黏包示意图

二、解决方向

  • 思考:
    那么通过接收到的字节数组我们应该如何判断收到的字节数组处于以下状态
    1. 正常
    2. 分包
    3. 黏包
  • 如何判断一个消息没有出现分包或者黏包呢?
    突破点:消息长度
    我们可以如同处理 区分消息类型 的逻辑一样
    为消息添加头部,头部记录消息的长度
    当我们接收到消息时,通过消息长度来判断是否分包、黏包,对消息进行拆分处理、合并处理
    我们每次只处理完整的消息
分包、黏包解决方向示意图

三、实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//用于处理分包时 缓存的字节数组和字节数组长度
private byte[] cacheBytes = new byte[1024 * 1024];
private int cacheNum;

//处理接收消息 分包黏包的方法
private void HandleReceiveMsg(byte[] receiveBytes,int receiveNum)
{
int msgID = 0;
int msgLength = 0;
int nowIndex = 0;

//收到消息是应该 判断缓存容器中是否有内容 如果有就拼接到后面
receiveBytes.CopyTo(cacheBytes,cacheNum);
cacheNum += receiveNum;

while (true)
{
//每次将消息长度设置为-1 是为了避免上一次解析的数据 影响这一次的判断
msgLength = -1;
//处理解析一条消息
if (cacheNum - nowIndex >= 8)
{
//解析Id
msgID = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
//解析长度
msgLength = BitConverter.ToInt32(cacheBytes, nowIndex);
nowIndex += 4;
}
//只有后面的字节数大于消息体的时候才能解析
if (cacheNum - nowIndex >= msgLength && msgLength != -1)
{
//解析消息体
BaseMsg baseMsg = null;
switch (msgID)
{
case 1001:
PlayerMsg msg = new PlayerMsg();
msg.Reading(cacheBytes, nowIndex);
baseMsg = msg;
break;
}
if(baseMsg != null)
//收到消息 解析消息为字符串 并放入公共容器
receiveQueue.Enqueue(baseMsg);
nowIndex += msgLength;
if(nowIndex == cacheNum)
{
cacheNum = 0;
break;
}
}
else
{
//如果不满足 证明有分包
//那么我们就需要把当前收到的内容 记录下来
//有待下次接收到消息后 再做处理
/*receiveBytes.CopyTo(cacheBytes,0);
cacheNum = receiveNum;*/
//如果进行了id和长度的解析,但是没有成功解析消息体 那么我们需要去减去nowIndex的位置
if (msgLength != -1)
nowIndex -= 8;
//把剩余没有解析的字节数组 移到前面来 用于缓存 下次继续解析
Array.Copy(cacheBytes,nowIndex,cacheBytes,0,cacheNum-nowIndex);
cacheNum = cacheNum - nowIndex;
break;
}
}
}

评论