• 《精通并发与Netty》学习笔记(14


    一、Netty粘包和拆包解决方案

    Netty提供了多个解码器,可以进行分包的操作,分别是: 
    * LineBasedFrameDecoder (换行)
       LineBasedFrameDecoder是回车换行解码器,如果用户发送的消息以回车换行符作为消息结束的标识,则可以直接使用Netty的LineBasedFrameDecoder对消息进行解码,只需要在初始化Netty服务端或者客户端时将LineBasedFrameDecoder正确的添加到ChannelPipeline中即可,不需要自己重新实现一套换行解码器。
       LineBasedFrameDecoder的工作原理是它依次遍历ByteBuf中的可读字节,判断看是否有“ ”或者“ ”,如果有,就以此位置为结束位置,从可读索引到结束位置区间的字节就组成了一行。它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。防止由于数据报没有携带换行符导致接收到ByteBuf无限制积压,引起系统内存溢出。

    * DelimiterBasedFrameDecoder(添加特殊分隔符报文来分包) 
       DelimiterBasedFrameDecoder是分隔符解码器,用户可以指定消息结束的分隔符,它可以自动完成以分隔符作为码流结束标识的消息的解码。
       回车换行解码器实际上是一种特殊的DelimiterBasedFrameDecoder解码器。

    * FixedLengthFrameDecoder(使用定长的报文来分包) 
        FixedLengthFrameDecoder是固定长度解码器,它能够按照指定的长度对消息进行自动解码,开发者不需要考虑TCP的粘包/拆包等问题,非常实用。
        对于定长消息,如果消息实际长度小于定长,则往往会进行补位操作,它在一定程度上导致了空间和资源的浪费。但是它的优点也是非常明显的,编解码比较简单,因此在实际项目中仍然有一定的应用场景。

    * LengthFieldBasedFrameDecoder (自定义解码器跟编码器)

       本文介绍的重点LengthFieldBasedFrameDecoder,一般包含了消息头(head)、消息体(body):消息头是固定的长度,一般有有以下信息 -> 是否压缩(zip)、消息类型(type or cmdid)、消息体长度(body length);消息体长度不是固定的,其大小由消息头记载,一般记载业务交互信息。

      netty对应来说就是编码器(Encoder)跟解码器(Decoder),一般其中会有一个基本消息类对外输出

    二、实例演示

    首先编写自定义协议类:

    package com.spring.netty.handler2;
    
    /**
     * 自定义Person协议
     */
    public class PersonProtocol {
        private int length;
        private byte[] content;
    
        public int getLength() {
            return length;
        }
        public void setLength(int length) {
            this.length = length;
        }
        public byte[] getContent() {
            return content;
        }
        public void setContent(byte[] content) {
            this.content = content;
        }
    }

    新建服务端代码:

    package com.spring.netty.handler2;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    
    public class MyServer {
        public static void main(String[] args) throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup(1);
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).
                        childHandler(new MyServerInitializer());
    
                ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
                channelFuture.channel().closeFuture().sync();
            }finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    }
    package com.spring.netty.handler2;
    
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.socket.SocketChannel;
    
    public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new MyPersonDecoder());
            pipeline.addLast(new MyPersonEncoder());
    
            pipeline.addLast(new MyServerHandler());
        }
    }
    package com.spring.netty.handler2;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.ReplayingDecoder;
    
    import java.util.List;
    
    /**
     * Person解码器
     */
    public class MyPersonDecoder extends ReplayingDecoder<Void> {
        @Override
        protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
            System.out.println("MyPersonDecoder decode invoked!");
    
            int length = in.readInt();
            byte[] content = new byte[length];
            in.readBytes(content);
    
            PersonProtocol personProtocol = new PersonProtocol();
            personProtocol.setLength(length);
            personProtocol.setContent(content);
            out.add(personProtocol);
        }
    }
    package com.spring.netty.handler2;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.handler.codec.MessageToByteEncoder;
    
    /**
     * 编码器
     */
    public class MyPersonEncoder extends MessageToByteEncoder<PersonProtocol> {
        @Override
        protected void encode(ChannelHandlerContext ctx, PersonProtocol msg, ByteBuf out) throws Exception {
            System.out.println("MyPersonEncoder encode invoked!");
    
            out.writeInt(msg.getLength());
            out.writeBytes(msg.getContent());
        }
    }
    package com.spring.netty.handler2;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    
    import java.nio.charset.Charset;
    import java.util.UUID;
    
    public class MyServerHandler extends SimpleChannelInboundHandler<PersonProtocol> {
        private int count;
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, PersonProtocol msg) throws Exception {
            int length = msg.getLength();
            byte[] content = msg.getContent();
            System.out.println("服务端接收到的数据:");
            System.out.println("长度:"+length);
            System.out.println("内容:"+new String(content, Charset.forName("utf-8")));
            System.out.println("服务端接收到的消息数量:"+(++this.count));
            //服务端向客户端返回uuid
            String responseMessage = UUID.randomUUID().toString();
            int responseLength = responseMessage.getBytes("utf-8").length;
            byte[] responseContent = responseMessage.getBytes("utf-8");
    
            PersonProtocol personProtocol = new PersonProtocol();
            personProtocol.setLength(length);
            personProtocol.setContent(content);
            ctx.writeAndFlush(responseContent);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

    编写客户端程序:

    package com.spring.netty.handler2;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelPipeline;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    
    public class MyClient {
        public static void main(String[] args) throws Exception {
            EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
            try {
                Bootstrap bootstrap = new Bootstrap();
                bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel ch) throws Exception {
                                ChannelPipeline pipeline = ch.pipeline();
                                pipeline.addLast(new MyPersonDecoder());
                                pipeline.addLast(new MyPersonEncoder());
    
                                pipeline.addLast(new MyClientHandler());
                            }
                        });
                ChannelFuture channelFuture = bootstrap.connect("localhost",8899).sync();
                channelFuture.channel().closeFuture().sync();
            }finally {
                eventLoopGroup.shutdownGracefully();
            }
        }
    }
    package com.spring.netty.handler2;
    
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    
    import java.nio.charset.Charset;
    
    public class MyClientHandler extends SimpleChannelInboundHandler<PersonProtocol> {
        private int count;
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            for(int i=0;i<10;++i){
                String messageToBeSent = "sent from client";
                byte[] content = messageToBeSent.getBytes(Charset.forName("utf-8"));
                int length = messageToBeSent.getBytes(Charset.forName("utf-8")).length;
    
                PersonProtocol personProtocol = new PersonProtocol();
                personProtocol.setLength(length);
                personProtocol.setContent(content);
    
                ctx.writeAndFlush(personProtocol);
            }
        }
    
        //从服务器端接收数据
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, PersonProtocol msg) throws Exception {
            int length = msg.getLength();
            byte[] content = msg.getContent();
            System.out.println("客户端接收到的数据:");
            System.out.println("长度:"+length);
            System.out.println("内容:"+new String(content, Charset.forName("utf-8")));
            System.out.println("客户端接收到的消息数量:"+(++this.count));
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            ctx.close();
        }
    }

    分别启动服务端和客户端查看效果:

    服务端效果如图:

    客户端效果如图:

  • 相关阅读:
    layui穿梭框内容溢出解决办法
    location之alias浅析
    Semaphore和SemaphoreSlim实现并发同步
    Barrier实现并发同步
    CountdownEvent实现并发同步
    AutoResetEvent实现并发同步
    TrieTree树
    EncryptByPassPhrase与DecryptByPassPhrase的浅说
    一个mp4转gif的网站
    vue轮播图(可随父元素高宽自适应)
  • 原文地址:https://www.cnblogs.com/happy2010/p/10905038.html
Copyright © 2020-2023  润新知