当前位置: 首页 > 科技观察

粘袋和半袋你真的了解吗?三分钟搞懂

时间:2023-03-19 16:59:30 科技观察

通俗例子下面举个可能不太恰当,但很容易理解的例子。比如平时我们要发快递,如果物品太大,那么我们就需要拆分成几个包裹邮寄。当收件人只收到一个包裹时,物品不完整,对应网络传输,这种情况称为半包。只有收到所有的包,这个东??西(传递的信息)才算完整,所以在半个包的情况下,无法解析完整的数据,需要等到所有的包都收到。那么问题来了,怎么知道所有的包裹都收到了呢?我们稍后再分析。再比如,快过年了,我打算给家里的亲戚送点礼物,给每个长辈一块手表。礼品包装在一个包裹中邮寄,这样可以节省运费。这种将本应在多个数据包中传输的数据组合成一个数据包的情况,对应于网络传输,称为粘性数据包。看完这个例子,你应该对粘包和半包有感觉了。接下来,让我们看看网络中的实际情况。现实中stickypackets和half-packets只会在TCP传输时出现,UDP不会有这种情况。原因是TCP是面向流的,数据之间是没有边界的,而UDP是有边界的。的。熟悉TCP和UDP包格式的同学一定知道,TCP包是没有包长的,而UDP包是有包长的,这也解释了为什么TCP是流式的。那我为什么说上面的例子不合适呢,因为在现实生活中,快递包裹之间其实是有界限的,而TCP就像流水一样,没有明确的界限。然后TCP有发送缓冲区的概念,UDP其实没有这个概念。假设TCP传输的数据大小一旦超过了发送缓冲区的大小,就需要将一个完整的数据包拆分成两个或多个小数据包,这就可能出现半包的情况。完整数据无法解析成功。如果TCP一次发送的数据大小小于发送缓冲区,则可能与其他数据包合并在一起发送,这就是粘性数据包。此时接收端无法正常解析数据包,需要拆分成多个正确的数据包才能正常解析。关于stickypackets和halfpackets,我也看到MTU(最大传输单元)被用作问题。如果发送的数据大于MTU,就会发生拆包,导致半包。我个人认为这有点不对。简单的理解,UDP也一定要遵循MTU吧?那为什么不发生半包呢?让我们看看如何解决粘包和半包。那么怎么解决粘包和半包的问题呢?粘包:思路其实很清晰,拆开就行了,具体怎么拆,比如我们可以固定长度,我们规定每个包是10个字符的Section,然后切10个字节,这样就可以了拆解分析。半包:半包其实就是信息不全的意思。我们需要等所有的信息都处理完了再处理。当我们认识到这是一个不完整的包时,我们首先持有它,不对其进行处理。我们在处理之前等待数据完成。.这里的重点是,我们怎么知道这个时候已经完成了呢?上面说的定长其实就是一点。当然,还有更多更好的解决方案。我们往下看。实践中,针对粘包和半包问题的常见解决方案有3种:定长定界符定长字段+内容为了说明方便,以下不再以二进制为单位进行描述。定长其实很简单。比如现在要发送ABC和EF这两个数据包,如果不做任何处理,接收端可能会收到AB,CEF或者ABCE,F等,这时候我们固定长度,我们规定每条消息的长度为3。如果消息的实际数据小于3,则用空字符填充。所以我们发送的消息是:接收到的情况可能是:但是我们是按照3位来处理的,所以我们一次只会按照3位进行解析,所以虽然第一次接收到的数据是ABCE,但是我们只是Parse3位,即解析出ABC,保留一个E。当我们要继续解析3位时,发现长度小于3,暂时忽略,稍等片刻。等待F""后,发现当前数据再次满足3位,于是继续解析EF""。这样就解决了粘连和半包的问题。Netty对应的实现是FixedLengthFrameDecoder,实现定长解码。核心逻辑就是我上面说的,我们看源码,很简单:定长的优点:简单。缺点:固定长度非常死板,不易扩展,如果设置过大不能满足业务场景,会导致空间浪费,因为长度不足需要补齐。分隔符应该易于理解。我们以ABC和EF这两个包为例。写完ABC后,我插入一个分号组成ABC;,EF也是一样:以定界符为界,进行无界分割。解决粘包和半包的问题,??这个应该很容易理解。既然你的TCP没有边界,那我就给你在业务上划一个边界。Netty对应的实现是DelimiterBasedFrameDecoder。具体源码就不贴了,有点长,但道理很简单。继续解析,识别到分隔符后,就说明前面的数据完整了,所以解析前面的数据,然后继续向后扫描分析。分隔符的优点:简单,不浪费空间。缺点:内容本身需要做处理,防止内容中出现分隔符,导致混淆,所以传输的数据需要扫描进行转义,或者可以对数据进行base64编码,用非字符分隔64个字符即??可。分隔符的处理方式也是业界常用的。例如,Redis使用换行符来分隔。定长字段+内容也很好理解。例如,协议规定4位存储的内容长度是固定的,这样内容就可以缩放:我们以ABC和EF这两个包为例:解析过程是:先获取4位,如果当前如果接收到的数据不够4位,那就再等一会。4位就够了,解析后长度为3,所以后面取3位。如果数据不够3位,请再次等待。如果足够,解析它。这将得到一个完整的包。然后取4位后,分析得到2,同理取2后的2位,分析得到EF。该方法是先解析定长字段,获取后续内容的长度,根据内容长度获取内容,从而获取完整的报文。Netty对应的实现是LengthFieldBasedFrameDecoder,具体源码就不贴了,有点长,定长字段+内容的优点:可以根据固定字段准确定位,不需要扫描转义字符。缺点:定长字段的设计难度较大,比较浪费空间。毕竟每条消息都有这个长度,小了可能不够用。总结好吧,让我们总结一下。因为TCP是面向流的协议,使用缓冲区来提高发送效率,所以会导致粘包/半包。这种情况下,我们可以对消息进行操作,我们可以约定一个定长消息,或者嵌入定界符,或者使用定长字段+内容等三种常用的方法来解决粘包和半包的问题。以上三种在Netty中都有现成的实现类,可以直接使用:FixedLengthFrameDecoder、定长DelimiterBasedFrameDecoder、定界符LengthFieldBasedFrameDecoder、定长字段+内容,建议自己实验一下,有更清晰的认识。