针对网上的一份netty的面试题之(netty的粘包和拆包)

什么是粘包与半包问题?

针对网上的一份netty的面试题之(netty的粘包和拆包)_第1张图片

解决粘包半包问题
由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下。
(1)在包尾增加分割符,比如回车换行符进行分割,例如FTP协议;linebase包和delimiter包下,分别使用LineBasedFrameDecoder和DelimiterBasedFrameDecoder,如果超过规定字节长度,会报错。
(2)消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格;fixed包下,使用FixedLengthFrameDecoder
(3)将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第 一个字段使用int32来表示消息的总长度,LengthFieldBasedFrameDecoder;。

粘包与半包为何会出现?

TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。

  出现粘包现象的原因既可能由发送方造成,也可能由接收方造成。
发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
      接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。

粘包与半包解决方案

  1. 消息定长,每发送一次消息,在接收消息的同时截取固定长度的字节。
  2. 以某种分隔符进行分割。
  3. 把消息封装成消息头和消息体。

通过添加DelimiterBasedFrameDecoder来解决粘包半包问题

b.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .handler(new LoggingHandler())
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                        socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                        //socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(10));
                        socketChannel.pipeline().addLast(new StringDecoder());
                        socketChannel.pipeline().addLast(new EchoServerHandler());
                    }
                });

通过添加FixedLengthFrameDecoder获取固定长度字符来解决粘包半包问题

 b.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .handler(new LoggingHandler())
                .childHandler(new ChannelInitializer() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        //ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
                        //socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, delimiter));
                        socketChannel.pipeline().addLast(new FixedLengthFrameDecoder(10));
                        socketChannel.pipeline().addLast(new StringDecoder());
                        socketChannel.pipeline().addLast(new EchoServerHandler());
                    }
                });

通过ObjectDecoder和ObjectEncoder来消息头和消息体来解决粘包和半包问题。

 

原理介绍:

解决方法

  1. 固定长度,例如每次只发200个字节,也只读200个字节,度不够就等待
  2. 使用分隔符,例如回车换行
  3. 用消息头和消息体

不知不觉使用的处理方式

其实很多人说自己压根没有专门做过粘包和拆包的处理,程序也没问题。我们仔细回想一下,如果自己写个socket的demo的时候,是不是还用的是包装流的做法,例如使用了BufferedReader,并且使用了readLine方法,每次取一行。这里就是上面所说的使用了分隔符的解决方法。

解决方式的对比

  1. 固定长度的方法麻烦的是长度固定,不够要补充空,浪费资源,而且不够的话,还得修改长度,扩展不好。
  2. 分隔符的方式比较简单,就是怕输入的内容也有分割符。
  3. 自定义消息这种是比较常用的,麻烦的是需要自己定义消息规则,在处理简单的情况的时候,上面两种完全可以够用。

 

 

你可能感兴趣的:(【netty】)