• Netty实例


      本文netty的实例,主要包括几个知识点

      1 如何拿到channel (网上的大多数例子都是写在handler 的 channelActive 方法中的)

      2  自定义协议格式,使用 LengthFieldBasedFrameDecoder 解码

      3  使用CompleteFuture 模拟同步发送(一次发送收到回复后才进行下一次发送)

       协议体 

    package com.chinaunicom.asset.server.assetpublish.Netty;
    
    import lombok.Builder;
    import lombok.Data;
    
    @Data
    @Builder
    public class RpcMessage {
        //请求id
        private Integer requestId;
        // body长度
        private Integer length;
    
        private String body;
    
        public RpcMessage(Integer requestId, Integer length, String body) {
            this.requestId = requestId;
            this.body = body;
            this.length = length;
        }
    
    }

      客户端 

    public class FirstNettyClient {
        private final String host;
        private final int port;
    
        public static Map<Integer, CompletableFuture> getFutureMap() {
            return futureMap;
        }
    
        public void setFutureMap(Map<Integer, CompletableFuture> futureMap) {
            this.futureMap = futureMap;
        }
    
        private static Map<Integer, CompletableFuture> futureMap;
    
        public Channel getChannel() {
            return channel;
        }
    
        public void setChannel(Channel channel) {
            this.channel = channel;
        }
    
        private Channel channel;
    
        public FirstNettyClient(String host, int port) {
            this.host = host;
            this.port = port;
        }
    
        public void start() throws Exception {
    
            futureMap = new HashMap<>();
    
            Runnable runnable = new Runnable() {
                @Override
                public void run() {
                    EventLoopGroup group = new NioEventLoopGroup();
                    try {
                        //创建Bootstrap
                        Bootstrap b = new Bootstrap();
                        //指定EventLoopGroup以处理客户端事件;需要适用于NIO的实现
                        b.group(group)
                                //适用于NIO传输的Channel类型
                                .channel(NioSocketChannel.class)
                                //设置服务器的连接地址
                                .remoteAddress(new InetSocketAddress(host, port))
                                .handler(new ChannelInitializer<SocketChannel>() {
                                    //在创建Channel时,向ChannelPipeline中添加一个FirstNettyClientHandler实例
                                    @Override
                                    protected void initChannel(SocketChannel ch) throws Exception {
    
                                        ch.pipeline().addLast(new MyClientProtocolDecoder(16384,4,4, 0,0, true));
                                        ch.pipeline().addLast(new FirstNettyClientHandler());
                                    }
                                });
                        //连接到远程节点,阻塞等待直到连接完成
                        ChannelFuture f = b.connect().sync();
                        //阻塞,直到Channel关闭
                        channel = f.channel();
    
                        f.channel().closeFuture().sync();
                    } catch (Exception e) {
    
                    } finally {
                        //关闭线程池并且释放所有的资源
                        try {
                            group.shutdownGracefully().sync();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            };
            Thread startup = new Thread(runnable);
            startup.start();
    
        }
    
        public static void main(String[] args) throws Exception {
            FirstNettyClient nettyClient = new FirstNettyClient("127.0.0.1",6666);
            nettyClient.start();
            Thread.sleep(2000);
    
            byte[] requestArray = int2Bytes(110808);
    
            byte[] content = "int2Bytes(110808)".getBytes();
    
            byte[] lengthArray = int2Bytes(content.length);
    
            CompletableFuture completableFuture = new CompletableFuture();
            nettyClient.getFutureMap().put(110808, completableFuture);
    
            nettyClient.getChannel().writeAndFlush(Unpooled.copiedBuffer(requestArray, lengthArray, content));
    
            completableFuture.get(5, TimeUnit.SECONDS);//1 发送后通过completefuture的get阻塞,之后收到新消息再放行,代码2处解除阻塞
    
            System.out.println("next is ready");
        }
    
        public static byte[] int2Bytes(int integer) {
            byte[] targets = new byte[4];
            targets[3] = (byte) (integer & 0xFF);
            targets[2] = (byte) (integer >> 8 & 0xFF);
            targets[1] = (byte) (integer >> 16 & 0xFF);
            targets[0] = (byte) (integer >> 24 & 0xFF);
            return targets;
        }
    }

      其中main也可以单独提出来成为一个方法

      FirstNettyClientHandler 

    public class FirstNettyClientHandler extends SimpleChannelInboundHandler<RpcMessage> {
        /**
         * 和服务器连接建立后将被调用
         */
        public void channelActive(ChannelHandlerContext ctx) {
            //当被通知Channel是活跃的时候,发送一条消息
            System.out.println("client active");
    //        ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
            // ctx.writeAndFlush("Netty rocks!");
    
        }
        /**
         * 当从服务器接收到一条消息时被调用
         */
        @Override
        public void channelRead0(ChannelHandlerContext ctx, RpcMessage in) throws Exception {
    
            CompletableFuture completableFuture = FirstNettyClient.getFutureMap().get(in.getRequestId());
    
            System.out.println("Client received:" + in.getRequestId() + " " + in.getBody());
    
            System.out.println("Client future done:");
            completableFuture.complete(null);//2 阻塞解除
        }
        /**
         * 引发异常时会被调用
         */
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            System.out.println("Can't connect server");
            //发生异常时,记录错误并关闭Channel
            cause.printStackTrace();
            ctx.close();
        }
    }

      解码器 MyClientProtocolDecoder 

    public class MyClientProtocolDecoder extends LengthFieldBasedFrameDecoder {
    
        /**
         * @param maxFrameLength  帧的最大长度
         * @param lengthFieldOffset length字段偏移的地址
         * @param lengthFieldLength length字段所占的字节长
         * @param lengthAdjustment 修改帧数据长度字段中定义的值,可以为负数 因为有时候我们习惯把头部记入长度,若为负数,则说明要推后多少个字段
         * @param initialBytesToStrip 解析时候跳过多少个长度
         * @param failFast 为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异
         */
    
        public MyClientProtocolDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
    
            super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
    
        }
    
        @Override
        protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
            //在这里调用父类的方法,实现指得到想要的部分,我在这里全部都要,也可以只要body部分
            in = (ByteBuf) super.decode(ctx,in);
    
            if(in == null){
                return null;
            }
    //        if(in.readableBytes()<HEADER_SIZE){
    //            throw new Exception("字节数不足");
    //        }
            //读取requestId字段
            int requestId = in.readInt();
            //读取length字段
            int length = in.readInt();
    
            if(in.readableBytes()!=length){
                throw new Exception("标记的长度不符合实际长度");
            }
            //读取body
            byte[] bytes = new byte[in.readableBytes()];
            in.readBytes(bytes);
    
            return new RpcMessage(requestId, length, new String(bytes,"UTF-8"));
    
        }
    }

      服务端

    public class FirstNettyServer {
        private final int port;
    
        public FirstNettyServer(int port) {
            this.port = port;
        }
    
        @SneakyThrows
        public static void main(String[] args) {
            //启动服务器
            new FirstNettyServer(6666).start();
        }
    
        public void start() throws Exception {
            final FirstNettyServerHandler serverHandler = new FirstNettyServerHandler();
            final MyProtocolDecoder myProtocolDecoder = new MyProtocolDecoder(16384,4,4, 0,0, true);
    
            //1. 创建EventLoopGroup
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                //2.创建Server-Bootstrap
                ServerBootstrap b = new ServerBootstrap();
                b.group(group)
                        //3.指定所使用的NIO传输Channel
                        .channel(NioServerSocketChannel.class)
                        //4.使用指定的端口设置套接字地址
                        .localAddress(new InetSocketAddress(port))
                        //5.添加一个FirstNettyServerHandler到子Channel的ChannelPipeline
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel channel) throws Exception {
                                channel.pipeline().addLast(myProtocolDecoder);
                                channel.pipeline().addLast(serverHandler);
                            }
                        });
                //6. 异步绑定服务器,调用sync()方法阻塞等待直到绑定完成
                ChannelFuture f = b.bind().sync();
                //7. 获取Channel的CloseFuture,并且阻塞当前线程直到它完成
                f.channel().closeFuture().sync();
            } finally {
                //8. 关闭EventLoopGroup,释放所有资源
                group.shutdownGracefully().sync();
            }
        }
    }

      FirstNettyServerHandler 

    public class FirstNettyServerHandler extends ChannelInboundHandlerAdapter {
        /**
         * 每个传入的消息都会调用该方法
         *
         * @param ctx
         * @param msg
         */
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
    //        ByteBuf in = (ByteBuf) msg;
            RpcMessage rpcMessage = (RpcMessage)msg;
            //将接收到的消息进行打印输出
            System.out.println("Server received:" + rpcMessage.getBody());
            //将接收到的消息写给发送者
            byte[] requestArray = int2Bytes(rpcMessage.getRequestId());
    
            byte[] content = rpcMessage.getBody().getBytes();
    
            byte[] lengthArray = int2Bytes(rpcMessage.getLength());
    
            ByteBuf buf = Unpooled.buffer(requestArray.length + content.length + lengthArray.length);
            buf.writeBytes(requestArray);
            buf.writeBytes(lengthArray);
            buf.writeBytes(content);
    
            ctx.writeAndFlush(buf);
        }
    
        /*
         *通知ChannelInboundHander最后一次对channelRead()的调用是当前批量读取中的最后一条消息
         */
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            /**
             * 将目前暂存于ChannelOutboundBuffer中的消息
             *冲刷到远程节点,并且关闭该Channel
             */
    //        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
    //                .addListener(ChannelFutureListener.CLOSE);
        }
    
    
        /*
         * 在读取期间,有异常抛出时会调用
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            // 打印异常栈跟踪
            cause.printStackTrace();
            //关闭该channel
            ctx.close();
        }
    
        public static byte[] int2Bytes(int integer) {
            byte[] targets = new byte[4];
            targets[3] = (byte) (integer & 0xFF);
            targets[2] = (byte) (integer >> 8 & 0xFF);
            targets[1] = (byte) (integer >> 16 & 0xFF);
            targets[0] = (byte) (integer >> 24 & 0xFF);
            return targets;
        }
    }

      MyProtocolDecoder 

    public class MyProtocolDecoder extends LengthFieldBasedFrameDecoder {
    
        private static final int HEADER_SIZE = 6;
    
        /**
         * @param maxFrameLength  帧的最大长度
         * @param lengthFieldOffset length字段偏移的地址
         * @param lengthFieldLength length字段所占的字节长
         * @param lengthAdjustment 修改帧数据长度字段中定义的值,可以为负数 因为有时候我们习惯把头部记入长度,若为负数,则说明要推后多少个字段
         * @param initialBytesToStrip 解析时候跳过多少个长度
         * @param failFast 为true,当frame长度超过maxFrameLength时立即报TooLongFrameException异常,为false,读取完整个帧再报异
         */
    
        public MyProtocolDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment, int initialBytesToStrip, boolean failFast) {
    
            super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip, failFast);
    
        }
    
        @Override
        protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
            //在这里调用父类的方法,实现指得到想要的部分,我在这里全部都要,也可以只要body部分
            in = (ByteBuf) super.decode(ctx,in);
    
            if(in == null){
                return null;
            }
    //        if(in.readableBytes()<HEADER_SIZE){
    //            throw new Exception("字节数不足");
    //        }
            //读取requestId字段
            int requestId = in.readInt();
            //读取length字段
            int length = in.readInt();
    
            if(in.readableBytes()!=length){
                throw new Exception("标记的长度不符合实际长度");
            }
            //读取body
            byte[] bytes = new byte[in.readableBytes()];
            in.readBytes(bytes);
    
            return new RpcMessage(requestId, length, new String(bytes,"UTF-8"));
    
        }
    }
  • 相关阅读:
    虎年工作的 3 点战略思考
    AlphaCode能写代码了,人工智能会取代人吗
    取出GridView中的值
    Ubuntu 安装mysql
    k8s 更改pod数量限制(默认每个节点最多110组pod)
    linux 设置开机自动挂载文件系统
    windows U盘启动装机工具(Ventoy)
    ubuntu 系统增加源和删除源文件
    ubuntu 20.04 更新镜像源
    小游戏“程序猿大战产品狗”
  • 原文地址:https://www.cnblogs.com/juniorMa/p/16562709.html
Copyright © 2020-2023  润新知