Netty 解决粘包/半包问题

本篇文章的详细源码地址:https://github.com/suzhe2018/netty-item,可以下载下来,源码与文章相结合着学习。

6bbeaed906d2f38f74fd3eb9c9ed5389b42.jpg

1、拆包粘包

改造netty入门里的程序,循环连续发送100条消息。理想情况下服务端应该接受到100条消息,客户端收到100条的响应。

b4ef2f152dd3f41bae3ee2ffdbd3b86965a.jpg

运行服务端,然后在运行客户端,发现服务端只收到了一条消息,客户端也只收到一条响应。

862ab40efbe3cd0eb0fe06dd29714044303.jpg

81827ae1a59daea9a1ae586b35192b5d8aa.jpg

以上问题就是由于tcp粘包导致的,tcp除了粘包还存在拆包问题。

 

什么是TCP粘包半包?

TCP是个“流”协议,所谓流,就是没有界限的一串数据。大家可以想想河里的流水,是连成一片的,其间并没有分界线。TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。

注意:UCP是基于报文发送的,UDP报文的首部会有16bit来表现UDP数据的长度,所以不同的报文之间是可以区别隔离出来的,所以应用层接收传输层的报文时,不会存在拆包和粘包的问题;

TCP粘包/拆包问题说明

1f2aee3cb6a27736ad63e58a387eb4f68e0.jpg

假设客户端分别发送了两个数据包D1和D2给服务端,由于服务端一次读取到的字节数是不确定的,故可能存在以下4种情况。

(1)服务端分两次读取到了两个独立的数据包,分别是D1和D2,没有粘包和拆包;

(2)服务端一次接收到了两个数据包,D1和D2粘合在一起,被称为TCP粘包;

(3)服务端分两次读取到了两个数据包,第一次读取到了完整的D1包和D2包的部分内容,第二次读取到了D2包的剩余内容,这被称为TCP拆包;

(4)服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。

如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。

 

解决粘包半包问题:

(1)在包尾增加分割符,比如回车换行符进行分割。

(2)消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格。

(3)将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度。

为了解决TCP粘包/拆包导致的半包读写问题,Netty默认提供了多种编解码处理器用于处理粘包/拆包问题。

 

2、分割符

2.1  DelimiterBasedFrameDecoder

依据入门程序,定义四个类

47374d4d7190838856ac72dd9fce083dc56.jpg

更改DelimiterEchoServer,添加定长解码处理器

bcfb367c7daec864a0a73933f90b2284efa.jpg

DelimiterEchoServerInHandler 里发送消息,要添加分隔符。

4987432dd3acc3ce4e5b416086aceda0e1f.jpg

DelimiterEchoClient同样的要添加解码处理器,因为要接收来自服务端的消息。其次在消息发送的时候也要添加分隔符。

170f35810792ef720950ce8434dd68def2f.jpg

67f64140f6877ae62bbdd2880df80ba32cb.jpg

运行服务端 ,运行客户端 ,测试。

8d7498f21f02457712c0b6a42ea76c5c9dc.jpg 

45ae3bf032c9e4a99e2ed56dd18e42d60ca.jpg

 

2.2 LineBasedFrameDecoder

发送消息是给每个消息添加了回车换行符。

使用只需要在服务端和客户端分别添加LineBasedFrameDecoder解码处理器。

ch.pipeline().addLast(new LineBasedFrameDecoder(1024));

其次发送消息的时候要加上换行符

fad61071275da3a8a6bff5160a205022150.jpg

 

3、定长FixedLengthFrameDecoder

FixedLengthFrameDecoder 固定长度解码处理器,它能够按照指定的长度对消息进行自动解码。无论一次接收到多少数据报,它都会按照构造器中设置的固定长度进行解码,如果是半包消息,FixedLengthFrameDecoder  会缓存半包消息并等待下个包到达之后进行拼包合并,直到读取一个完整的消息包。

在服务端和客户端分别添加FixedLengthFrameDecoder,可以根据实际情况设置一个长度。

d53c7c6af7d2888bd5e12862ffd1e04093a.jpg

bf35e425bfe288cd48a0a5d8ee865f8d15d.jpg

其次发送消息的时候,不足长度,要用空格补齐

public class FixedUtils {
    public static String getBlank(int num){
        String blank = "";
        for (int i = 0;i<num; i ++){
            blank = blank + " ";
        }
        return blank;
    }
}

8f6f0cdd29cb85375c5acce596951c9ef87.jpg

d1828406c93e2f1b68fef909bf0d15ac938.jpg

 

3、通过LengthFieldBasedFrameDecoder 自定义长度解码处理器

LengthFieldBasedFrameDecoder通过指定长度来标识整包消息,这样就可以自动的处理黏包和半包消息,只要传入正确的参数,就可以轻松解决“读半包”的问题。

使用只需要在server和client分别加上下面的编解码处理器

                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535,
                                0,2,0,2));
                        /*给发送出去的消息增加长度字段*/
                        ch.pipeline().addLast(new LengthFieldPrepender(2));

通过LengthFieldPrepender可以将待发送消息的长度写入到ByteBuf的前2个字节,编码后的消息组成为长度字段+原消息的方式。

a714404fe11852d169c75704feab543f483.jpg

LengthFieldBasedFrameDecoder进行解码,相关参数如下

maxFrameLength:表示的是包的最大长度,

lengthFieldOffset:指的是长度域的偏移量,表示跳过指定个数字节之后的才是长度域;

lengthFieldLength:记录该帧数据长度的字段本身的长度;

lengthAdjustment:长度的一个修正值,可正可负;

initialBytesToStrip:从数据帧中跳过的字节数,表示得到一个完整的数据包之后,忽略多少字节,开始读取实际我要的数据

公式: 实际数据包长度 = 长度域中记录的数据长度 + lengthFieldOffset + lengthFieldLength + lengthAdjustment

71c58e8284dde76293ad8ad6dc22a662ba4.jpg

解码后,希望丢弃长度域2B字段,所以,只要initialBytesToStrip = 2即可。

1. lengthFieldOffset = 0

2. lengthFieldLength = 2

3. lengthAdjustment = 0 = 数据包长度(14) - lengthFieldOffset - lengthFieldLength - 长度域的值(12) 

4. initialBytesToStrip = 2 跳过长度域2B字段开始读取。

 

 

 

 

转载于:https://my.oschina.net/suzheworld/blog/3006320

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值