netty编码解码及粘包拆包处理

netty编码解码及粘包拆包处理

编码解码器

当你通过Netty发送或者接受一个消息的时候,就将会发生一次数据转换。入站消息会被解码:从字节转换为另一种格式(比如java对象);如果是出站消息,它会被编码成字节
Netty提供了一系列实用的编码解码器,他们都实现了ChannelInboundHadnler或者ChannelOutboundHandler接口。在这些类中,channelRead方法已经被重写了。以入站为例,对于每个从入站Channel读取的消息,这个方法会被调用。随后,它将调用由已知解码器所提供的decode()方法进行解码,并将已经解码的字节转发给ChannelPipeline中的下一个ChannelInboundHandler。

如果要实现高效的编解码可以用protobuf,但是protobuf需要维护大量的proto文件比较麻烦,现在一般可以使用protostuff。
protostuff是一个基于protobuf实现的序列化方法,它较于protobuf最明显的好处是,在几乎不损耗性能的情况下做到了不用我们写.proto文件来实现序列化。

粘包拆包

TCP是一个流协议,就是没有界限的一长串二进制数据。TCP作为传输层协议并不不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行数据包的划分,所以在业务上认为是一个完整的包,可能会被TCP拆分成多个包进行发送,也有可能把多个小的包封装成一个大的数据包发送,这就是所谓的TCP粘包和拆包问题。面向流的通信是无消息保护边界的。

<!-- 引入netty包-->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.35.Final</version>
        </dependency>

        <!--使用protostuff实现序列化-->
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-api</artifactId>
            <version>1.0.10</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-core</artifactId>
            <version>1.0.10</version>
        </dependency>
        <dependency>
            <groupId>com.dyuproject.protostuff</groupId>
            <artifactId>protostuff-runtime</artifactId>
            <version>1.0.10</version>
        </dependency>

使用LengthFieldBasedFrameDecoder作为拆包器

具体可参考这篇文章 netty源码分析之LengthFieldBasedFrameDecoder

拆包器

public class PackageSplitUtil extends LengthFieldBasedFrameDecoder {

    public PackageSplitUtil() {
        super(Integer.MAX_VALUE, Constant.LENGTH_OFFECT, Constant.LENGTH_BYTES_COUNT, 0, Constant.LENGTH_BYTES_COUNT);
    }

    @Override
    protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception{
        return super.decode(ctx, in);
    }
}

编码器

public class MyNettyEncoder extends MessageToByteEncoder<NettyMessage> {

    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, NettyMessage msg, ByteBuf out) throws Exception {
        byte[] bytes = ProtostuffUtil.serializer(msg);
        // 获取消息的长度
        int length = bytes.length;
        ByteBuf buf = Unpooled.buffer(Constant.HEAD_LEAGTH + length);
        // 写入协议版本
        buf.writeShort(Constant.PROTOCOL_VERSION);
        // 写入消息长度
        buf.writeShort(length);
        // 写入消息体
        buf.writeBytes(bytes);
        out.writeBytes(buf);

    }
}

解码器

public class MyNettyDecoder extends ByteToMessageDecoder {

    private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class);
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

        // 读取传送过来的消息的长度。
        int length = in.readUnsignedShort();
        // 长度如果小于0,非法数据,关闭连接
        if (length < 0) {
            ctx.close();
        }
        // 读到的消息体长度如果小于传送过来的消息长度
        if (length > in.readableBytes()) {
            log.info("读到的消息长度小于传送过来的消息长度!");
            return;
        }
        ByteBuf frame = Unpooled.buffer(length);
        in.readBytes(frame);
        try {
            byte[] inByte = frame.array();
            // 字节转成对象
            NettyMessage msg = ProtostuffUtil.deserializer(inByte, NettyMessage.class);

            if (msg != null) {
                // 获取业务消息头
                out.add(msg);
            }
        } catch (Exception e) {
            log.error("====" + ctx.channel().remoteAddress() + ",decode failed====");
        }
    }
}

服务端使用

public class NettyServer {

    private static Logger log = LoggerFactory.getLogger(MyNettyDecoder.class);

    private static Channel serverChannel;

    private static final NettyServer nettyServer = new NettyServer();

    public static NettyServer getInstance(){ return nettyServer; }

    private NettyServer(){
        start();
    }

    public static void closeServer(){
        if (nettyServer != null){
            serverChannel.close();
            serverChannel = null;
        }
    }
    private static void start(){
        //创建线程处理连接请求
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        //创建线程和客户端进行交互
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //创建服务端的启动对象
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(new ChannelInitializer<SocketChannel>() {//创建通道初始化对象,设置初始化参数
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            log.info("[ 客户端 ]" + ch.remoteAddress() + " 已连接");
                            //对workGroup的socketChannel设置处理器
                            ChannelPipeline pipeline = ch.pipeline();

                            //心跳机制
                            pipeline.addLast(new IdleStateHandler(Constant.HEART_TIMEOUT, 0, 0, TimeUnit.SECONDS));
                            pipeline.addLast(new PackageSplitUtil());
                            //解码器
                            pipeline.addLast(new MyNettyEncoder());
                            //编码器
                            pipeline.addLast(new MyNettyDecoder());
                            //自定义的处理器
                            pipeline.addLast(new NettyServerHandler());

                        }
                    });
            ChannelFuture channelFuture = bootstrap.bind(Constant.NETTY_SERVER_PORT).sync();
            serverChannel = channelFuture.channel();
            log.info("====netty服务器已启动成功==== 端口:" + Constant.NETTY_SERVER_PORT);
            //对通道关闭进行监听,closeFuture是异步操作,监听通道关闭
            serverChannel.closeFuture().addListener((ChannelFutureListener) future -> {
                log.info("====优雅的关闭了EventLoopGroup====");
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
        }
    }
}

Constant部分参数

/**
     * 约定的包头长度
     */
    public static int HEAD_LEAGTH = 2;

    /**
     * 约定的版本号
     */
    public static int PROTOCOL_VERSION = 1;

    /**
     * 约定的长度偏移(2字节)
     */
    public static int LENGTH_OFFECT = 2;

    /**
     * 约定的长度字节数(short类型)
     */
    public static int LENGTH_BYTES_COUNT = 2;

    /**
     * 端口
     */
    public static int NETTY_SERVER_PORT = 9000 ;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值