Netty使用篇:半包&粘包

简介: Netty使用篇:半包&粘包


第一章:半包粘包

一:相关概念

程序处理过程过程中我们会通过缓冲区接收数据,接收的过程中可能会出现所谓的半包和粘包。当我们的数据量超过我们的ByteBuf缓冲区大小时才会发生半包和粘包问题,当我们数据量恰好等于或者小于缓冲区的时候是不可能发生这种情况的。

半包就是数据量过大一次没接完,粘包就是数据量比较小,一次性接了好几个:

半包和粘包在TCP协议下的是无法避免的,因为Tcp协议是流式的协议基于流处理,消息无边界。所以我们使用Tcp协议就得对消息做边界化的处理,才能达到正确的消息,才能正确解析到正确的信息。另外一种网络通信协议UDP是否会发生这种情况,不会的。

本质:接收数据时数据可能接受不完整,或者接收过多。

二:网络发送接收全过程

1:经典过程

通信过程有一个客户端和一个接收端,站在客户端角度来讲,我们可以分为这样的几个层次,用户区或者叫用户态,系统区或者内核区或者叫内核态,再往下是硬件或则叫网卡,Java程序是在用户区,socketChnnel.write(byte)这个时候就是把数据复制到我们的内核态当中,然后操作系统将数据再次拷贝给网卡,网卡操作数据可以传递出去。真正发送的时候会以数据包的形式进行发送,当然数据包的这个概念是不准确的,应该叫做数据帧。

接收方数据拿到之后,由网卡拿到数据之后将数据拷贝到操作系统的数据的缓冲区,也就是socket的缓冲区,当我们调用一些读的方法的时候,Socket缓冲区的数据会拷贝到我们的用户区里边。

用户内存和我们的Socket的缓冲区这两个都有可能产生一个半包粘包的问题(数据要么存少了,要么存多了)

2:经典问题

用户的缓冲区大还是Socket缓冲区比较大?

一般情况下都是Socket缓冲区大,很有可能他为了通信效率问题,Socket缓冲区会将数据存一批之后一次性写出。所以一般是Socket缓冲区比较大,

都是Socket缓冲区,接收数据的缓冲区和发送数据的缓冲区是不是同一个?

不是的。Socket缓冲区是分为接收和发送的,发送的缓冲区我们叫做SNDSocketBuffer,接收的是REVSocketBuffer

NIO在通讯的时候需要分包写数据,往往需要流量控制,流量控制是通过滑动窗口的方式控制流量的,那么什么叫做滑动窗口?

客户端和服务端发送数据的时候,是使用帧的方式发送数据的,当服务端接收到数据之后,服务器端会回馈一个ACK码,当接收到了Ack之后认为接收到了,就发一个新的帧过去,然后在发送一个Ack码,这是一个串行的过程,效率很慢,因为接收到了Ack码之后才会发送下一个, 属于串行发送。

所以在真正传输过程中肯定有优化策略,比方一次性的发3个帧的数据,三个帧的数据你再给我发Ack的信息。接收到一个Ack就在发一个数据帧过去,速度够快的话基本等于一次性发三个过去。现在有了问题了,到底一次性发多少个数据帧过去呢?一次性能发送过去多少个数据量的数据就叫做滑动窗口。这里的滑动窗口看似是通过控制帧实际上是通过控制数据量来的,所以,这个滑动窗口也有可能发生半包粘包的问题。

滑动窗口大小是多大呢?

我们这里说的是默认大小,这是由算法规定的,滑动窗口等同于Socket缓冲区(剩余空间)的大小,会随着Socket剩余大小动态变化。LInux操作系统一定是这样的,其他操作系统有可能不是这样,我们设置了Socket缓冲区的大小就等同于设置了滑动窗口的大小。接收方的滑动窗口跟接收方的Socket缓冲区一样,发送方的滑动窗口跟发送方的Socket的缓冲区大小相等。

总结:

以上我们知道很多地方都有可能发生半包和粘包的问题,Buffer和Socket缓冲区都存数据,只要存数据就有可能多存或者少存,这样就有可能发生半包和粘包。滑动窗口,对数据量进行控制也有可能发生半包和粘包的问题。

我们数据是怎么由Socket缓冲区拷贝给网卡的

这里是通过DMA技术完成的,数据拷贝的时候一定需要CPU接入,所有数据操作都需要Cpu接入,DMA可以完成Socket到网卡的数据的拷贝。

write方法的真正含义

我们写数据write方法,write方法会有一个返回值,这个返回值返回的是写出了多少个字节,这个写出了多少字节我们会发生写出很多0的情况,那么返回write的返回值的真正含义是什么?返回了,真正意味着100数据发送给了服务器了吗?不是的,这个返回值的含义是往Socket缓冲区里边写出的数据,为什么会返回0呢?是因为我们往里边写数据(拷贝)的时候,还没有进行flush操作,他满了,满了之后我们只能写0,也就是说write方法调用并不因为这方法进行了网络传输,等我们的Socket缓冲区去刷新数据才会发送,返回0的含义代表了这个Socket缓冲区暂时是满了。满了之后就得等,(Socket缓冲区是一个栈)

Socket缓冲区默认大小是多大?

默认是:65535Byte,我们可以去设置。

在Netty中我不需要心跳,我只需要关注write方法的返回值,这样是一个错误的概念,write可以写,不代表网络是通着的。

拷贝–存储–拷贝–存储

2: 七层网络协议

应用层(http/Ftp)
表示层
会话层
传输层(TCP协议)
网络层(ip层)
链路层
物理层

在传输层协议当中引入了一个段Segment的概念,这个段就是在原有数据之上加上了Tcp的头信息。数据起初的时候经过七层网络协议层层往下传输,经过传输层的时候,还会加上TCP的头信息,这个时候数据会封装成段。然后数据交给网络层,网络层在这个基础之上呢,会再加入一个Ip头信息,这个时候会形成一个新的组合叫做包也就是package,然后数据再往下传输到链路层,这个时候这个数据已经叫做帧了。然后数据在传输到物理层会将我们的帧转换成二进制,也就是bit。以上的处理过程中,我们完全可以把帧就理解为包,头信息就是一堆描述信息。Netty当中的解决半包粘包的API都是Frame因为本质解决的是帧的问题。

传输信息不论是哪个层次在处理的时候本之上都是分为头信息和体信息,我们的Http协议也是要遵从这个规律的。(wireshark非常好的抓包工具)

第二章:Netty粘包半包策略

一:ByteBuf获取和默认大小

ByteBuf是Netty帮我们创建这个ByteBuf创建的,这个代码都是Netty帮我们做的。我们是不需要做这个东西,接收数据和发送数据的ByteBuf都是Netty帮我们做的。

Netty接收数据默认帮我们创建的ByteBuf的大小是1024B,ByteBuf一定要经过生产的检验。我们可以指定一下

最小就是16个字节,最大也可以指定(最小值,初始值,最大值)

我们编程的时候如何获取这个Netty帮我们创建的ByteBuf呢?通过Handler。Netty接收客户端传过来的数据帮我我们只需要在Handler当中处理数据就行了。(第一个非head的InboundHandler获取的参数就是Netty获取的数据)

Socket缓冲区和ByteBuf缓冲区大小都是可以设置的。我们当时讲过为什么Netty要封装Channel,第一Netty要统一编程模型,可以非常容易的设置TCP相关的设置参数。Socket的缓冲区的大小是系统的,默认大小是65535

public class MyNettyServer {
    public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,16,16));
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new FixedLengthFrameDecoder(10));
                pipeline.addLast(new LoggingHandler());
            }
        });
        //
        serverBootstrap.bind(8000);
    }
}

Soket缓冲区的改变是一个全局的概念。ChannelOption.SO_REVBUF代表的是接收缓冲区的大小。channelOption.SO_SNDBUF代表的是发送缓冲区的大小。SO代表的是Socket,通过这种方式所做的修改是在应用程序层面做出的修改,不会上升到全局的角度,这样不会影响到操作系统下其他的TCP协议通信的程序。

二:Netty半包粘包的解决方式 思路分析

ByteBuf在handler当中获取到了之后,也就是第一个非head的Handler,这个处理的过程在Netty当中叫做Decoder,当然这里的Decoder和encoder会涉及到很多的类型。 在我们的Netty里面,原始的字节称之为Byte,当数据转换好之后称之为Message 半包 粘包都解决完成了,这是Netty设计代码的一个特别的思路。例如:

Netty当中的ByteToMessageDecoder就是继承了一个ChannelInboundHandlerAdaper可以将字节转换为Message。这个类是所有的解决半包粘包的父类。Netty处理半包粘包的过程中实际上他就是通过ByteToMessageDecoder这样的一个类型来解决的,将ByteBuf解决到一个Message的对象,到Message这里已经不存在半包和粘包的问题了。当然有很多的半包和粘包的问题,所以他有很多的实现类。

1:FixedLengthFrameDecoder(固定长度帧解码器)

帧就是对方发过来的一个单位的数据。这个是固定长度的解码器。解决的是什么问题,解决的是固定长度的数据的半包和粘包问题,现在的问题是如果说我们发送的数据不足10怎么半?需要加一下特殊的字符给凑够这个10.这个不好的地方在于,占用空间。

一个解码器,将收到的{@link ByteBuf}s按固定的字节数分割成固定的字节数。例如,如果你收到以下四个碎片化的数据包。
+---+----+------+----+
|a | bc | defg | hi |
+---+----+------+----+
一个{@link FixedLengthFrameDecoder}{@code (3)}将把它们解码成
以下三个具有固定长度的数据包。
+-----+-----+-----+
| abc | def | ghi |
+-----+-----+-----+

服务端代码:

{
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
        serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,16,16));
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new FixedLengthFrameDecoder(10));
                pipeline.addLast(new LoggingHandler());
            }
        });
        //
        serverBootstrap.bind(8000);
    }

客户端代码:

public static void main(String[] args) throws InterruptedException {
        log.debug("myNettyClientStarter------");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });
        Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
        //半包?粘包?  1  0
        //粘包 --->ByteBuf(1024) ---> socket 65535 --- server
        channel.writeAndFlush("aaaaaaaaaabbbbbb____c__________");
    }

服务端控制台结果:

2022-11-13 17:00:52.784 [nioEventLoopGroup-2-2] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@16885921
2022-11-13 17:00:52.793 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744] READ: 10B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 61 61 61 61 61 61 61 61 61                   |aaaaaaaaaa      |
+--------+-------------------------------------------------+----------------+
2022-11-13 17:00:52.793 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 10, widx: 16, cap: 16)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 17:00:52.793 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [FixedLengthFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744].
2022-11-13 17:00:52.794 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744] READ COMPLETE
2022-11-13 17:00:52.794 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744] READ: 10B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 62 62 62 62 62 62 5f 5f 5f 5f                   |bbbbbb____      |
+--------+-------------------------------------------------+----------------+
2022-11-13 17:00:52.794 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 20, widx: 31, cap: 64)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 17:00:52.794 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [FixedLengthFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744].
2022-11-13 17:00:52.794 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744] READ: 10B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 63 5f 5f 5f 5f 5f 5f 5f 5f 5f                   |c_________      |
+--------+-------------------------------------------------+----------------+
2022-11-13 17:00:52.794 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 10, cap: 10/10, unwrapped: PooledUnsafeDirectByteBuf(ridx: 30, widx: 31, cap: 64)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 17:00:52.795 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [FixedLengthFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744].
2022-11-13 17:00:52.795 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7f2169d1, L:/192.168.1.3:8000 - R:/192.168.1.3:51744] READ COMPLETE

从客户端角度来讲,我们发送了30个字节的数据,我们的ByteBuf缓冲区默认长度1024这种情况下不存在所谓的半包和粘包。Socket缓冲区和滑动窗口默认是65535这个时候更没有半包和粘包的情况,数据发送过来之后,服务端接收数据到ByteBuf,我们ByteBuf设置的为16,我们FixedLengthFrameDecoder设置的是10这个时候会发生半包和粘包都有。但是Netty基于FixedLengthFrameDecoder帮我们处理成了10B一组的数据。这不就是他处理的半包和粘包的效果吗。这种就是局限性比较小

2:LineBasedFrameDecoder(基于线的帧解码器)

在Netty当中也有基于分隔符这种形式做的解码器。通过特殊的分分隔符来进行

一个解码器,它将接收到的{@link ByteBuf}s在行尾处分割。
{@code "\n"}和{@code "\r\n"}都可以处理。
字节流预计是UTF-8字符编码或ASCII。目前的实现
使用直接{@code byte}到{@code char}的转换,然后将{@code char}与一些低范围的
ASCII字符,如{@code '\n'}或{@code '\r'}。UTF-8不使用低范围的[0...0x7F]。
字节值的多字节代码点表示法,因此该实现完全支持。
对于一个更通用的基于分隔符的解码器,请参见{@link DelimiterBasedFrameDecoder}。

这个解码器的狗子构造方法当中需要提供一个最大值,这是因为我们可能发生一个这样的情况,这个消息太长了导致都没有这个\n我就不处理了。

客户端代码:

public static void main(String[] args) throws InterruptedException {
        log.debug("myNettyClientStarter------");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });
        Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
        //半包?粘包?  1  0
        //粘包 --->ByteBuf(1024) ---> socket 65535 --- server
        channel.writeAndFlush("sunshuai\nxiaohei\nxiaojr\n");
    }

服务端代码:

public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
        //serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,16,16));
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //sunshuai\ni love you\n
                //xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n
                //最大长度 指的是 如果超过最大长度,还没有发现分隔符,不处理。
                pipeline.addLast(new LineBasedFrameDecoder(1024));
                pipeline.addLast(new LoggingHandler());
            }
        });
        //
        serverBootstrap.bind(8000);
    }

客户端执行结果:

2022-11-13 18:31:01.799 [nioEventLoopGroup-2-2] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@242a4acc
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7ab3494a, L:/192.168.1.3:8000 - R:/192.168.1.3:60447] READ: 8B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 73 75 6e 73 68 75 61 69                         |sunshuai        |
+--------+-------------------------------------------------+----------------+
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 8, cap: 8/8, unwrapped: PooledUnsafeDirectByteBuf(ridx: 9, widx: 24, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LineBasedFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7ab3494a, L:/192.168.1.3:8000 - R:/192.168.1.3:60447].
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7ab3494a, L:/192.168.1.3:8000 - R:/192.168.1.3:60447] READ: 7B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 78 69 61 6f 68 65 69                            |xiaohei         |
+--------+-------------------------------------------------+----------------+
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 7, cap: 7/7, unwrapped: PooledUnsafeDirectByteBuf(ridx: 17, widx: 24, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LineBasedFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7ab3494a, L:/192.168.1.3:8000 - R:/192.168.1.3:60447].
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7ab3494a, L:/192.168.1.3:8000 - R:/192.168.1.3:60447] READ: 6B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 78 69 61 6f 6a 72                               |xiaojr          |
+--------+-------------------------------------------------+----------------+
2022-11-13 18:31:01.807 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 6, cap: 6/6, unwrapped: PooledUnsafeDirectByteBuf(ridx: 24, widx: 24, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 18:31:01.808 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LineBasedFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7ab3494a, L:/192.168.1.3:8000 - R:/192.168.1.3:60447].
2022-11-13 18:31:01.808 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7ab3494a, L:/192.168.1.3:8000 - R:/192.168.1.3:60447] READ COMPLETE

这是第二种解决半包和粘包的方式。也支持我们定制分隔符。

3:DelimiterBasedFrameDecoder(基于分隔符的帧解码器)

这个支持自定义分隔符。

以上就是Netty解决半包和粘包的长度的两种方式,一种是基于定长的方式一种是基于特殊分隔符的方式。

4:LengthFieldBasedFrameDecoder(基于长度字段的帧解码器)

基于头题分离的方式解决这个半包和粘包的方式。

这个类构造的时候需要我们填写4个参数,这些参数都是有特殊的含义:

lengthFieldOffset:= 0 :偏移量(长度内容所在位置的距离开始处的偏移量的大小)

lengthFieldLength:= 2:代表的是标识长度的这个数据所占用的数据长度。

lengthAdjustment: = 0:长度内容到具体消息内容的偏移量。

initialBytesToStrip:= 0:需要剥离头信息的长度。(是不是需要剥离头,自定)一般我们是要头的。

把一个完整的消息分成两个部分。消息头当中通过length记录这个消息实际的长度。有了头之后,我们就可以通过这信息去解析我们的消息体。

在16进制当中,0X后边每两位代表一个字节。

这个必须要掌握的,因为将来我们要自定义我们的协议内容。

协议当中设计了很多的头信息,包括我们Http协议当中就有很多的头信息:Content-type、agent、Content-Length、Reference、Host等等这些头信息。以后小溪里边加什么头,是协议定的。

客户端代码:

public static void main(String[] args) throws InterruptedException {
        log.debug("myNettyClientStarter------");
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.channel(NioSocketChannel.class);
        Bootstrap group = bootstrap.group(eventLoopGroup);//32 ---> 1 IO操作 31线程
        bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler());
                ch.pipeline().addLast(new StringEncoder());
            }
        });
        Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
        ByteBuf buf = ByteBufAllocator.DEFAULT.buffer();
        String message = "hello sunshuai";
        // length 0 4
        buf.writeInt(message.length());
        // 特殊的头 1 1
        buf.writeByte(1);
        buf.writeBytes(message.getBytes());
        String message1 = "hello xiaohei";
        // length 0 4
        buf.writeInt(message1.length());
        // 特殊的头 1 1
        buf.writeByte(2);
        buf.writeBytes(message1.getBytes());
        channel.writeAndFlush(buf);
    }

服务端代码:

public static void main(String[] args) {
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.channel(NioServerSocketChannel.class);
        serverBootstrap.group(new NioEventLoopGroup());
        //接受socket缓冲区大小 等同于 滑动窗口的初始值 65535
        //serverBootstrap.option(ChannelOption.SO_RCVBUF, 100);
        //netty创建ByteBuf时 执行大小 默认1024 child ScoketChannel相关
        //serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(16,16,16));
        serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            //
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ChannelPipeline pipeline = ch.pipeline();
                //sunshuai\ni love you\n
                //xxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n
                //最大长度 指的是 如果超过最大长度,还没有发现分隔符,不处理。
                //pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,1,0));
                pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,1,5));
                pipeline.addLast(new LoggingHandler());
            }
        });
        //
        serverBootstrap.bind(8000);
    }

服务端结果展示:

2022-11-13 19:21:37.941 [nioEventLoopGroup-2-2] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.checkBounds: true
2022-11-13 19:21:37.942 [nioEventLoopGroup-2-2] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@1a1a7022
2022-11-13 19:21:37.948 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7a539a8c, L:/192.168.1.3:8000 - R:/192.168.1.3:62601] READ: 14B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f 20 73 75 6e 73 68 75 61 69       |hello sunshuai  |
+--------+-------------------------------------------------+----------------+
2022-11-13 19:21:37.949 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 14, cap: 14/14, unwrapped: PooledUnsafeDirectByteBuf(ridx: 19, widx: 37, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 19:21:37.949 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LengthFieldBasedFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7a539a8c, L:/192.168.1.3:8000 - R:/192.168.1.3:62601].
2022-11-13 19:21:37.949 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7a539a8c, L:/192.168.1.3:8000 - R:/192.168.1.3:62601] READ: 13B
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c 6c 6f 20 78 69 61 6f 68 65 69          |hello xiaohei   |
+--------+-------------------------------------------------+----------------+
2022-11-13 19:21:37.949 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded inbound message PooledSlicedByteBuf(ridx: 0, widx: 13, cap: 13/13, unwrapped: PooledUnsafeDirectByteBuf(ridx: 37, widx: 37, cap: 1024)) that reached at the tail of the pipeline. Please check your pipeline configuration.
2022-11-13 19:21:37.949 [nioEventLoopGroup-2-2] DEBUG io.netty.channel.DefaultChannelPipeline - Discarded message pipeline : [LengthFieldBasedFrameDecoder#0, LoggingHandler#0, DefaultChannelPipeline$TailContext#0]. Channel : [id: 0x7a539a8c, L:/192.168.1.3:8000 - R:/192.168.1.3:62601].
2022-11-13 19:21:37.949 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x7a539a8c, L:/192.168.1.3:8000 - R:/192.168.1.3:62601] READ COMPLETE


相关文章
|
4月前
|
编解码 缓存 移动开发
TCP粘包/拆包与Netty解决方案
TCP粘包/拆包与Netty解决方案
46 0
|
7月前
|
移动开发 网络协议 算法
由浅入深Netty粘包与半包解决方案
由浅入深Netty粘包与半包解决方案
52 0
|
19天前
|
网络协议 Java 物联网
Spring Boot与Netty打造TCP服务端(解决粘包问题)
Spring Boot与Netty打造TCP服务端(解决粘包问题)
27 1
|
3月前
|
编解码
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战_自定义长度分包编解码码器
48 0
|
3月前
|
网络协议
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战
Netty Review - 优化Netty通信:如何应对粘包和拆包挑战
53 0
|
4月前
|
消息中间件 Oracle Dubbo
Netty 源码共读(一)如何阅读JDK下sun包的源码
Netty 源码共读(一)如何阅读JDK下sun包的源码
43 1
|
7月前
|
监控 Java Linux
由浅入深Netty基础知识NIO网络编程1
由浅入深Netty基础知识NIO网络编程
40 0
|
7月前
|
缓存 安全 Java
由浅入深Netty基础知识NIO三大组件原理实战 2
由浅入深Netty基础知识NIO三大组件原理实战
48 0
|
7月前
|
Java
由浅入深Netty基础知识NIO三大组件原理实战 1
由浅入深Netty基础知识NIO三大组件原理实战
61 0
|
2月前
|
移动开发 编解码 网络协议
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现
用Java的BIO和NIO、Netty来实现HTTP服务器(三) 用Netty实现