Netty黏包半包解决方案

目录

目标

Netty版本

Netty官方API

概述

复现黏包半包现象

黏包案例

半包案例

解决方案

短连接(能解决黏包问题,不能解决半包问题。不推荐。)

定长帧解码器(能解决黏包半包问题。适用于业务数据长度固定的场景。)

分隔符解码器(能解决黏包半包问题。适用于业务数据有固定分隔符的场景。)

基于长度字段的帧解码器(也叫LTC解码器,能解决黏包半包问题。需要将数据的长度写入到ByteBuf。)


目标

了解黏包半包发生的原因,了解各个解决方案适用的场景。


Netty版本

        
			io.netty
			netty-all
			4.1.87.Final
		

Netty官方API

Netty API Reference (4.1.90.Final)https://netty.io/4.1/api/index.html


概述

黏包(粘包)原因

原因一:业务上,接收方的ByteBuf设置过大(Netty默认1024字节),发送方向接收方发送多条数据,如果数据较小,此时就会出现黏包现象。

原因二:传输层中,TCP采用滑动窗口来进行传输控制,接收方会告诉发送方在某一时刻能发送多少数据(窗口尺寸),当滑动窗口为0时,一般情况下(两种特殊情况这里不做考虑)不能发送数据。实质上就是通过缓冲区达到流量控制的目的。如果滑动窗口远大于发送数据的大小,且接收方有没有及时处理,则缓冲区内就有多个报文,从而产生黏包现象。

原因三:TCP段和IP数据报对会给报文添加首部,如果业务报文较小,首部比业务报文大,此时发送数据会造成流量浪费,因此Nagle算法尽可能多地攒够报文再发送,从而产生黏包现象。

半包原因

原因一:业务上,接收方的ByteBuf设置过小(Netty默认1024字节),发送地数据大于ByteBuf的阈值,则产生半包现象。

原因二:传输层中,TCP传输报文。滑动窗口小于发送数据的大小,则只能截断发送,从而产生半包现象。

原因三:最大报文段长度(MSS)是TCP协议的一个选项,用于在TCP连接建立时,收发双方协商通信时每一个报文段所能承载的最大数据长度(不包括文段头)。一旦报文超过了MSS,则产生半包现象。


复现黏包半包现象

黏包案例

服务端

package com.ctx.sticky;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyServer {
    public static void main(String[] args) {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }
}

客户端

package com.ctx.sticky;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;

@Slf4j
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        new NettyClient().nb();
    }
    
    public void nb() throws InterruptedException {
        //启动Netty客户端
        Bootstrap bootstrap = new Bootstrap();
        //选择EventLoop
        bootstrap.group(new NioEventLoopGroup());
        //选择客户端Channel实现,Channel是数据的传输通道。
        bootstrap.channel(NioSocketChannel.class);
        //添加处理器,
        bootstrap.handler(new ChannelInitializer() {
            //连接建立后初始化Channel
            @Override
            public void initChannel(NioSocketChannel channel) throws Exception {
                channel.pipeline().addLast(
                        new ChannelInboundHandlerAdapter(){
                            //与服务端建立连接以后会调用该方法
                            @Override
                            public void channelActive(ChannelHandlerContext ctx){
                                for(int i=0 ;i<3; i++){
                                    ByteBuf bb=ctx.alloc().buffer(16);
                                    byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
                                    bb.writeBytes(bytes);
                                    //写入数据
                                    ctx.writeAndFlush(bb);
                                }
                            }
                        }
                );
            }
        });
        ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
        channelFuture.sync();
    }
}

服务端打印的日志


半包案例

服务端

package com.ctx.sticky;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;

@Slf4j
public class NettyServer {
    public static void main(String[] args) {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置系统的接收缓冲区10字节大小(滑动窗口)
        serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }
}

客户端

package com.ctx.sticky;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;

@Slf4j
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        new NettyClient().nb();
    }

    public void nb() throws InterruptedException {
        //启动Netty客户端
        Bootstrap bootstrap = new Bootstrap();
        //选择EventLoop
        bootstrap.group(new NioEventLoopGroup());
        //选择客户端Channel实现,Channel是数据的传输通道。
        bootstrap.channel(NioSocketChannel.class);
        //添加处理器,
        bootstrap.handler(new ChannelInitializer() {
            //连接建立后初始化Channel
            @Override
            public void initChannel(NioSocketChannel channel) throws Exception {
                channel.pipeline().addLast(
                        new ChannelInboundHandlerAdapter(){
                            //与服务端建立连接以后会调用该方法
                            @Override
                            public void channelActive(ChannelHandlerContext ctx){
                                for(int i=0 ;i<3; i++){
                                    ByteBuf bb=ctx.alloc().buffer(16);
                                    byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
                                    bb.writeBytes(bytes);
                                    //写入数据
                                    ctx.writeAndFlush(bb);
                                }
                            }
                        }
                );
            }
        });
        ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
        channelFuture.sync();
    }
}

服务端打印的日志


解决方案

短连接(能解决黏包问题,不能解决半包问题。不推荐。)

服务端

package com.ctx.sticky;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyServer {
    public static void main(String[] args) {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置接收缓冲区10字节大小,短链接发送方式无法解决半包问题。
        //serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }
}

客户端

package com.ctx.sticky;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new NettyClient().nb();
        }
    }

    public void nb() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //启动Netty客户端
            Bootstrap bootstrap = new Bootstrap();
            //选择EventLoop
            bootstrap.group(eventExecutors);
            //选择客户端Channel实现,Channel是数据的传输通道。
            bootstrap.channel(NioSocketChannel.class);
            //添加处理器,
            bootstrap.handler(new ChannelInitializer() {
                //连接建立后初始化Channel
                @Override
                public void initChannel(NioSocketChannel channel) throws Exception {
                    channel.pipeline().addLast(
                            new ChannelInboundHandlerAdapter() {
                                //与服务端建立连接以后会调用该方法
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    ByteBuf bb = ctx.alloc().buffer(16);
                                    byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
                                    bb.writeBytes(bytes);
                                    //写入数据
                                    ctx.writeAndFlush(bb);
                                    ctx.channel().close();
                                }
                            }
                    );
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
            channelFuture.sync();
        } finally {
            //关闭连接
            eventExecutors.shutdownGracefully();
        }
    }
}

服务端打印日志结果


定长帧解码器(能解决黏包半包问题。适用于业务数据长度固定的场景。)

服务端

package com.ctx.sticky;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyServer {
    public static void main(String[] args) {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(),new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置接收缓冲区10字节大小。
        //serverBootstrap.option(ChannelOption.SO_RCVBUF,10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                //数据的固定长度为12个字节。
                nioSocketChannel.pipeline().addLast(new FixedLengthFrameDecoder(12));
                nioSocketChannel.pipeline().addLast("Handler",new ChannelInboundHandlerAdapter(){
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }
}

客户端

package com.ctx.sticky;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        new NettyClient().nb();
    }

    public void nb() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //启动Netty客户端
            Bootstrap bootstrap = new Bootstrap();
            //选择EventLoop
            bootstrap.group(eventExecutors);
            //选择客户端Channel实现,Channel是数据的传输通道。
            bootstrap.channel(NioSocketChannel.class);
            //添加处理器,
            bootstrap.handler(new ChannelInitializer() {
                //连接建立后初始化Channel
                @Override
                public void initChannel(NioSocketChannel channel) throws Exception {
                    channel.pipeline().addLast(
                            new ChannelInboundHandlerAdapter() {
                                //与服务端建立连接以后会调用该方法
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    for (int i = 0; i < 3; i++) {
                                        ByteBuf bb = ctx.alloc().buffer(16);
                                        byte[] bytes = "Hello World!".getBytes(Charset.forName("UTF-8"));
                                        bb.writeBytes(bytes);
                                        //写入数据
                                        ctx.writeAndFlush(bb);
                                    }
                                    ctx.channel().close();
                                }
                            }
                    );
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
            channelFuture.sync();
        } finally {
            //关闭连接
            eventExecutors.shutdownGracefully();
        }
    }
}

服务端打印日志结果


分隔符解码器(能解决黏包半包问题。适用于业务数据有固定分隔符的场景。)

服务端

package com.ctx.sticky;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyServer {
    public static void main(String[] args) {
        new NettyServer().zdyDecoder();
    }

    /**
     * 自定义分隔符解码器
     */
    public void zdyDecoder() {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置接收缓冲区10字节大小。
        serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                //数据超过20个字节仍然没有遇到##,则报错:io.netty.handler.codec.TooLongFrameException
                ByteBuf bb = nioSocketChannel.alloc().buffer();
                bb.writeBytes("##".getBytes(Charset.forName("UTF-8")));
                nioSocketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(20,bb));
                nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }

    /**
     * 行解码器:即以换行符为分隔符来分割消息。
     */
    public void lineDecoder() {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置接收缓冲区10字节大小。
        serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                //数据超过20个字节仍然没有遇到换行符,则报错:io.netty.handler.codec.TooLongFrameException
                nioSocketChannel.pipeline().addLast(new LineBasedFrameDecoder(20));
                nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }
}

客户端

package com.ctx.sticky;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        new NettyClient().nb();
    }

    public void nb() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //启动Netty客户端
            Bootstrap bootstrap = new Bootstrap();
            //选择EventLoop
            bootstrap.group(eventExecutors);
            //选择客户端Channel实现,Channel是数据的传输通道。
            bootstrap.channel(NioSocketChannel.class);
            //添加处理器,
            bootstrap.handler(new ChannelInitializer() {
                //连接建立后初始化Channel
                @Override
                public void initChannel(NioSocketChannel channel) throws Exception {
                    channel.pipeline().addLast(
                            new ChannelInboundHandlerAdapter() {
                                //与服务端建立连接以后会调用该方法
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    for (int i = 0; i < 3; i++) {
                                        ByteBuf bb = ctx.alloc().buffer(16);
                                        byte[] bytes = "Hello World!##".getBytes(Charset.forName("UTF-8"));
                                        bb.writeBytes(bytes);
                                        //写入数据
                                        ctx.writeAndFlush(bb);
                                    }
                                    ctx.channel().close();
                                }
                            }
                    );
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
            channelFuture.sync();
        } finally {
            //关闭连接
            eventExecutors.shutdownGracefully();
        }
    }
}

服务端打印日志结果


基于长度字段的帧解码器(也叫LTC解码器,能解决黏包半包问题。需要将数据的长度写入到ByteBuf。)

服务端

package com.ctx.sticky;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyServer {
    public static void main(String[] args) {
        new NettyServer().lengthFieldOffsetTest();
    }

    /**
     * 基于长度字段的帧解码器
     * 该方法演示:initialBytesToStrip字段作用。
     * initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
     */
    public void initialBytesToStripTest() {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置接收缓冲区10字节大小。
        serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                /**
                 * maxFrameLength:最大的消息长度
                 * lengthFieldOffset:
                 * lengthFieldLength:设定消息长度所占用的字节数,比如:int类型占用四个字节。
                 * lengthAdjustment:
                 * initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
                 *
                 */
                nioSocketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(
                        512,0,4,0,4
                ));
                nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }

    /**
     * 基于长度字段的帧解码器
     * 该方法演示:lengthAdjustment字段作用。
     * lengthAdjustment:调整长度,比如在ByteBuf加入消息之前加入了其他数据,如:版本信息等。此时可以通过该参数设定附加数据的长度,达到调整整体数据长度的目的。
     */
    public void lengthAdjustmentTest() {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置接收缓冲区10字节大小。
        serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                /**
                 * maxFrameLength:最大的消息长度
                 * lengthFieldOffset:
                 * lengthFieldLength:设定消息长度所占用的字节数,比如:int类型占用四个字节。
                 * lengthAdjustment:调整长度,比如在ByteBuf加入消息之前加入了其他数据,如:版本信息等。此时可以通过该参数设定附加数据的长度,达到调整整体数据长度的目的。
                 * initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
                 *
                 */
                nioSocketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(
                        512,0,4,3,7
                ));
                nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }

    /**
     * 基于长度字段的帧解码器
     * 该方法演示:lengthAdjustment字段作用。
     * lengthFieldOffset:长度字段偏移。如果消息长度内容前面有其他内容,可以通过该参数设置截取消息长度的起始位置。
     */
    public void lengthFieldOffsetTest() {
        //Netty服务端启动器
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(new NioEventLoopGroup(), new NioEventLoopGroup(2));
        //选择服务器的ServerSocketChannel实现,这里我选择NIO。
        serverBootstrap.channel(NioServerSocketChannel.class);
        //设置接收缓冲区10字节大小。
        serverBootstrap.option(ChannelOption.SO_RCVBUF, 10);
        //设置事件类型
        serverBootstrap.childHandler(new ChannelInitializer() {
            @Override
            protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
                /**
                 * maxFrameLength:最大的消息长度
                 * lengthFieldOffset:长度字段偏移。如果消息长度内容前面有其他内容,可以通过该参数设置截取消息长度的起始位置。
                 * lengthFieldLength:设定消息长度所占用的字节数,比如:int类型占用四个字节。
                 * lengthAdjustment:调整长度,比如在ByteBuf加入消息之前加入了其他数据,如:版本信息等。此时可以通过该参数设定附加数据的长度,达到调整整体数据长度的目的。
                 * initialBytesToStrip:要剥离的初始字节,比如:设置的数据长度用的是int,则可以填写4表示剥离4个字节的长度。设置为0表示不剥离。
                 *
                 */
                nioSocketChannel.pipeline().addLast(new LengthFieldBasedFrameDecoder(
                        512,4,4,3,11
                ));
                nioSocketChannel.pipeline().addLast("Handler", new ChannelInboundHandlerAdapter() {
                    //处理读事件
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                        String s = ((ByteBuf) msg).toString(Charset.forName("UTF-8"));
                        log.info(s);
                    }
                });
            }
        });
        //设置服务器的监听端口
        serverBootstrap.bind(8999);
    }
}

客户端

package com.ctx.sticky;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.Charset;

@Slf4j
public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        new NettyClient().lengthFieldOffsetTestTest();
    }

    public void initialBytesToStripTest() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //启动Netty客户端
            Bootstrap bootstrap = new Bootstrap();
            //选择EventLoop
            bootstrap.group(eventExecutors);
            //选择客户端Channel实现,Channel是数据的传输通道。
            bootstrap.channel(NioSocketChannel.class);
            //添加处理器,
            bootstrap.handler(new ChannelInitializer() {
                //连接建立后初始化Channel
                @Override
                public void initChannel(NioSocketChannel channel) throws Exception {
                    channel.pipeline().addLast(
                            new ChannelInboundHandlerAdapter() {
                                //与服务端建立连接以后会调用该方法
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    ByteBuf bb = ctx.alloc().buffer(1024);
                                    byte[] bytes = "I love you three thousand times a day.".getBytes(Charset.forName("UTF-8"));
                                    //写入这条完整数据的长度。
                                    bb.writeInt(bytes.length);
                                    //写入这条消息的内容。
                                    bb.writeBytes(bytes);

                                    byte[] bytes2 = "Avengers Alliance Rally.".getBytes(Charset.forName("UTF-8"));
                                    //写入这条完整数据的长度。
                                    bb.writeInt(bytes2.length);
                                    //写入这条消息的内容。
                                    bb.writeBytes(bytes2);

                                    //写入数据
                                    ctx.writeAndFlush(bb);

                                    ctx.channel().close();
                                }
                            }
                    );
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
            channelFuture.sync();
        } finally {
            //关闭连接
            eventExecutors.shutdownGracefully();
        }
    }

    public void lengthAdjustmentTest() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //启动Netty客户端
            Bootstrap bootstrap = new Bootstrap();
            //选择EventLoop
            bootstrap.group(eventExecutors);
            //选择客户端Channel实现,Channel是数据的传输通道。
            bootstrap.channel(NioSocketChannel.class);
            //添加处理器,
            bootstrap.handler(new ChannelInitializer() {
                //连接建立后初始化Channel
                @Override
                public void initChannel(NioSocketChannel channel) throws Exception {
                    channel.pipeline().addLast(
                            new ChannelInboundHandlerAdapter() {
                                //与服务端建立连接以后会调用该方法
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    ByteBuf bb = ctx.alloc().buffer(1024);
                                    byte[] bytes = "I love you three thousand times a day.".getBytes(Charset.forName("UTF-8"));
                                    //写入这条完整数据的长度。
                                    bb.writeInt(bytes.length);
                                    //版本号
                                    bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
                                    //写入这条消息的内容。
                                    bb.writeBytes(bytes);

                                    byte[] bytes2 = "Avengers Alliance Rally.".getBytes(Charset.forName("UTF-8"));
                                    //写入这条完整数据的长度。
                                    bb.writeInt(bytes2.length);
                                    //版本号
                                    bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
                                    System.out.println("1.0".getBytes(Charset.forName("UTF-8")).length);
                                    //写入这条消息的内容。
                                    bb.writeBytes(bytes2);

                                    //写入数据
                                    ctx.writeAndFlush(bb);

                                    ctx.channel().close();
                                }
                            }
                    );
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
            channelFuture.sync();
        } finally {
            //关闭连接
            eventExecutors.shutdownGracefully();
        }
    }

    public void lengthFieldOffsetTestTest() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        try {
            //启动Netty客户端
            Bootstrap bootstrap = new Bootstrap();
            //选择EventLoop
            bootstrap.group(eventExecutors);
            //选择客户端Channel实现,Channel是数据的传输通道。
            bootstrap.channel(NioSocketChannel.class);
            //添加处理器,
            bootstrap.handler(new ChannelInitializer() {
                //连接建立后初始化Channel
                @Override
                public void initChannel(NioSocketChannel channel) throws Exception {
                    channel.pipeline().addLast(
                            new ChannelInboundHandlerAdapter() {
                                //与服务端建立连接以后会调用该方法
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) {
                                    ByteBuf bb = ctx.alloc().buffer(1024);
                                    byte[] bytes = "I love you three thousand times a day.".getBytes(Charset.forName("UTF-8"));
                                    //加入额外的信息
                                    bb.writeBytes("cafe".getBytes(Charset.forName("UTF-8")));
                                    //写入这条完整数据的长度。
                                    bb.writeInt(bytes.length);
                                    //版本号
                                    bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
                                    //写入这条消息的内容。
                                    bb.writeBytes(bytes);

                                    byte[] bytes2 = "Avengers Alliance Rally.".getBytes(Charset.forName("UTF-8"));
                                    //加入额外的信息
                                    bb.writeBytes("cafe".getBytes(Charset.forName("UTF-8")));
                                    //写入这条完整数据的长度。
                                    bb.writeInt(bytes2.length);
                                    //版本号
                                    bb.writeBytes("1.0".getBytes(Charset.forName("UTF-8")));
                                    System.out.println("1.0".getBytes(Charset.forName("UTF-8")).length);
                                    //写入这条消息的内容。
                                    bb.writeBytes(bytes2);

                                    //写入数据
                                    ctx.writeAndFlush(bb);

                                    ctx.channel().close();
                                }
                            }
                    );
                }
            });
            ChannelFuture channelFuture = bootstrap.connect("localhost", 8999);
            channelFuture.sync();
        } finally {
            //关闭连接
            eventExecutors.shutdownGracefully();
        }
    }
}

你可能感兴趣的:(Netty,黏包半包,Netty解决黏包半包)