• netty为啥要二次开发


    很早之前就看过李林峰写的netty的书,但是感觉没有直接用到还是理解不够深入,现在的公司有两套自己基于Netty开发的系统,感觉才真正理解为啥要这么做

    借用别人文章回顾下 https://www.cnblogs.com/carl10086/p/6183030.html

    健壮性、功能、性能(预置了选多的编码功能,支持多种主流协议)、可定制性(通过ChannelHandler对通信框架进行灵活的扩展)和可扩展性

    由于一个完整的包会被TCP拆分为多个包进行发送,也有可能将多个小的包封装成一个大包进行发送,所以会出现粘包、拆包的问题

    例子将msg转换为Netty的ByteBuf对象,通过ByteBuf的readableBytes获取缓冲区可读的字节数,根据可读的字节数创建byte数组。将缓冲区的内容读取到byte数组中

    问题的解决方法:定长,加开始结束符号,消息头包含长度消息体存储消息等

    下面是解决了和未解决的代码,注释掉的是有包问题的代码,解决问题的主要是增加了两个Handler如下描述

    package com.netty.helloword;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.LineBasedFrameDecoder;
    import io.netty.handler.codec.string.StringDecoder;
    
    public class Server {
        public void bind(int port) throws Exception {
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            EventLoopGroup workerGroup = new NioEventLoopGroup();
            try {
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                        .option(ChannelOption.SO_BACKLOG, 1024).option(ChannelOption.SO_KEEPALIVE, true)
                        .childHandler(new ChildChannelHandler());
                ChannelFuture f = serverBootstrap.bind(port).sync();
                System.out.println("Server start");
                f.channel().closeFuture().sync();
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
                System.out.println("release...");
            }
        }
    
        private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                ch.pipeline().addLast(new StringDecoder());
                ch.pipeline().addLast(new ServerHandler());
            }
        }
    
        public static void main(String[] args) throws Exception {
            int port = 9998;
            new Server().bind(port);
        }
    }
    package com.netty.helloword;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerAdapter;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    
    public class ServerHandler extends ChannelInboundHandlerAdapter {
        private int counter;
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //        ByteBuf buf = (ByteBuf) msg;
            //        byte[] req = new byte[buf.readableBytes()];
            //        buf.readBytes(req);
            //        String body = new String(req, "UTF-8").substring(0, req.length
            //                - System.getProperty("line.separator").length());
            String body = (String) msg;
            System.out.println("The time server receive order : " + body + " ; the counter is : " + ++counter);
            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body)
                    ? new java.util.Date(System.currentTimeMillis()).toString()
                    : "BAD ORDER";
            currentTime = currentTime + System.getProperty("line.separator");
            ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
            ctx.writeAndFlush(resp);
        }
    
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
            ctx.flush();
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            ctx.close();
        }
    }
    package com.netty.helloword;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.ChannelOption;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.SocketChannel;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.LineBasedFrameDecoder;
    import io.netty.handler.codec.string.StringDecoder;
    
    public class Client {
        public void connect(int port, String host) throws Exception {
            EventLoopGroup group = new NioEventLoopGroup();
            try {
                Bootstrap b = new Bootstrap();
                b.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            public void initChannel(SocketChannel ch) throws Exception {
                                ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                                ch.pipeline().addLast(new StringDecoder());
                                ch.pipeline().addLast(new ClientChannelHandler());
                            }
                        });
                ChannelFuture f = b.connect(host, port).sync();
                f.channel().closeFuture().sync();
            } finally {
                group.shutdownGracefully();
            }
        }
    
        private class ClientChannelHandler extends ChannelInitializer<SocketChannel> {
            @Override
            protected void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new ClientHandler());
            }
        }
    
        public static void main(String[] args) throws Exception {
            new Client().connect(9998, "127.0.0.1");
        }
    }
    package com.netty.helloword;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerAdapter;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.util.ReferenceCountUtil;
    
    public class ClientHandler extends ChannelInboundHandlerAdapter {
    
        //    private final ByteBuf firstMessage;
        private int    counter;
    
        private byte[] req;
    
        public ClientHandler() {
            //        byte[] req = "QUERY TIME ORDER".getBytes();
            //        firstMessage = Unpooled.buffer(req.length);
            //        firstMessage.writeBytes(req);
            req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
        }
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) {
            //        ctx.writeAndFlush(firstMessage);
            ByteBuf message = null;
            for (int i = 0; i < 100; i++) {
                message = Unpooled.buffer(req.length);
                message.writeBytes(req);
                ctx.writeAndFlush(message);
            }
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //        ByteBuf buf = (ByteBuf) msg;
            //        byte[] req = new byte[buf.readableBytes()];
            //        buf.readBytes(req);
            //        String body = new String(req, "UTF-8");
            String body = (String) msg;
            System.out.println("Now is : " + body);
        }
    
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
            ctx.close();
        }
    }

    LineBasedFrameDecoder

    (1) 遍历ByteBuf中的可读字节,判断看是否有" "或者" ",如果有,就以此为结束位置,从可读索引到结束位置区间的字节就组成了一行

    (2) 是一个以换行符为结束标志的解码器,支持携带结束符或者不携带结束符2种方式,同时支持配置单行的最大长度。

    (3) 超过单行最大长度直接抛异常

    StringDecoder

    (1) 将接收的对象转换为字符串

    (2) 继续调用后面的Handler

    DelimiterBasedFrameDecoder:

    自定义特殊符号分割,有2个参数,一个为单行最大长度,一个为自定义符号对象

    FixedLengthFrameDecoder:

    定长切分

    ------------------------------------------------------------------------------------------------------------------------------------------

    提供了对于序列化反序列化的各种支持,因为各种序列化方式的速度和大小差距较大,所以可以根据需要提高性能

    ch.pipeline().addLast("frameDecoder", new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2));
    ch.pipeline().addLast("msgpack decoder", new MsgpackDecoder());
    
    ch.pipeline().addLast("frameEncoder", new LengthFieldPrepender(2));
    ch.pipeline().addLast("msgpack encoder", new MsgpackEncoder());
    ch.pipeline().addLast(new ProtobufVarint32FrameDecoder());
    ch.pipeline().addLast(new ProtobufDecoder(SubscribeReqProto.SubscribeReq.getDefaultInstance()));
    ch.pipeline().addLast(new ProtobufVarint32LengthFieldPrepender());
    ch.pipeline().addLast(new ProtobufEncoder());
    ch.pipeline().addLast(new SubReqServerHandler());

    ------------------------------------------------------------------------------------------------------------------------------------------

    Http/JSON/Websocket服务器相关支持,上面文章中的例子已经非常清楚了

    ch.pipeline().addLast("http-decoder", new HttpRequestDecoder()); // 请求消息解码器
    ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));// 目的是将多个消息转换为单一的request或者response对象
    ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());//响应解码器
    ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());//目的是支持异步大文件传输()

    ------------------------------------------------------------------------------------------------------------------------------------------

    为什么要开发私有协议栈

    由于现代软件的复杂性,一个大型软件系统往往会被人为地拆分称为多个模块,另外随着移动互联网的兴起,网站的规模越来越大,业务功能越来越多,往往需要集群和分布式部署。模块之间的通信就需要进行跨节点通信。
    传统的Java应用中节点通信的常用方式:

    • rmi远程服务调用
    • Java Socket + Java序列化
    • RPC框架 Thrift、Apache的Avro等
    • 利用标准的公有协议进行跨节点调用,例如HTTP+XML,Restful+JSON或WebService

    下面使用Netty设计私有协议

    除了链路层的物理连接外,还需要对请求和响应消息进行编解码。 在请求和应答之外,还需要控制和管理类指令,例如链路建立的握手信息,链路检测的心跳信息。这些功能组合到一起后,就会形成私有协议。

      • 每个Netty节点(Netty进程)之间建立长连接,使用Netty协议进行通信。
      • Netty节点没有客户端和服务端的区别,谁首先发起连接,谁就是客户端。

    协议栈功能描述:

    1. 基于Netty的NIO通信框架,提供高性能的异步通信能力;
    2. 提供消息的编解码框架,实现POJO的序列化和反序列化
    3. 提供基于IP地址的白名单接入认证机制;
    4. 链路的有效性校验机制;
    5. 链路的断线重连机制;

    具体步骤:

    1. Netty协议栈客户端发送握手请求信息,携带节点ID等有效身份认证信息;
    2. Netty协议服务端对握手请求消息进行合法性校验,包括节点ID有效性校验、节点重复登录校验和IP地址合法性校验,校验通过后,返回登录成功的握手应答消息;
    3. 链路建立成功之后,客户端发送业务消息;
    4. 链路成功之后,服务端发送心跳消息;
    5. 链路建立成功之后,客户端发送心跳消息;
    6. 链路建立成功之后,服务端发送业务消息;
    7. 服务端退出时,服务端关闭连接,客户端感知对方关闭连接后,被动关闭客户端连接。
  • 相关阅读:
    Scrapy+Scrapy-redis+Scrapyd+Gerapy 分布式爬虫框架整合
    centos7 安装软件指南
    Kafka--消费者
    Kafka--生产者
    Kafka--初识Kafka
    Kafka--Kafka简述
    NetWork--记一次Http和TLS抓包
    JVM--a == (a = b)基于栈的解释器执行过程
    Java容器--Queue
    Idea--使用Idea调试设置
  • 原文地址:https://www.cnblogs.com/it-worker365/p/9969195.html
Copyright © 2020-2023  润新知