Netty框架用法介绍&&RPC应用实践

一、介绍Netty用法之前,先聊一下I/O:


       1)计算机中的信息交换机制
             网络I/O  、本地I/O  

       2)IO的演变历史:
             BIO、NIO、AIO  


       3)目前大家熟知的,哪些框架的底层通信在用Netty:

           Dubbo、RocketMQ、Spring、ElasticSearch、GRPC
           Spark、Hadoop、Flink

二、环境准备:

      <dependency>
          <groupId>io.netty</groupId>
          <artifactId>netty-all</artifactId>
          <version>4.1.36.Final</version>
      </dependency>

三、demo示范,相关API参考代码注释

server代码:

public class MyServer {
    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup wokerGroup = new NioEventLoopGroup();
        //epoll Only supported on Linux
        //EventLoopGroup bossGroup = new EpollEventLoopGroup();
        //EventLoopGroup wokerGroup = new EpollEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //在服务器端的handler()方法表示对bossGroup起作用,而childHandler表示对wokerGroup起作用
            serverBootstrap.group(bossGroup,wokerGroup) //绑定线程池
                    .channel(NioServerSocketChannel.class) //指定使用的channel
                    .childHandler(new MyServerInitializer())//绑定客户端连接时触发的操作
                    .localAddress(8899) //绑定端口
                    .option(ChannelOption.SO_BACKLOG,128)//配置主线程分配的最大线程数
                    .childOption(ChannelOption.SO_KEEPALIVE,true);//保持长连接
            ChannelFuture channelFuture = serverBootstrap.bind().sync();//服务器异步创建绑定
            channelFuture.channel().closeFuture().sync();//关闭服务器通道
        }finally {
            bossGroup.shutdownGracefully();// 释放线程池资源
            wokerGroup.shutdownGracefully();// 释放线程池资源
        }
    }
}

public class MyServerHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        //打印出客户端地址
        System.out.println(ctx.channel().remoteAddress()+", "+msg);
        //TODO 业务逻辑处理 query(msg)
        Student student = ServiceUtil.query(msg);
        //TODO 返回结果做编码处理
        IJacksonSerializer serializer= new JacksonSerializer();
        byte[] serialize = serializer.serialize(student);
        //TODO 处理结果返回给客户端
        ctx.channel().writeAndFlush(serialize);
    }
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().localAddress().toString() + " 通道已激活!");
        super.channelActive(ctx);
    }
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().localAddress().toString() + " 通道不活跃!");
        super.channelInactive(ctx);
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//cause.printStackTrace();
        System.out.println(ctx.channel().localAddress().toString() + " 连接断开或者异常!");
        ctx.close();
    }
}
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
        pipeline.addLast(new LengthFieldPrepender(4));
        //字符串解码
        // pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
        // //字符串编码
        // pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
        pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));//字符串解码
        pipeline.addLast(new ByteArrayEncoder());//字节数组编码
        //自己定义的处理器
        pipeline.addLast(new MyServerHandler());// 客户端触发操作
    }
}

Client代码:

public class MyClient {
    public static void main(String[] args) throws Exception{
        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        //epoll Only supported on Linux
        //EventLoopGroup eventLoopGroup = new EpollEventLoopGroup();
        try{
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(eventLoopGroup)
                    .channel(NioSocketChannel.class) // 使用NioSocketChannel来作为连接用的channel类
                    .remoteAddress(new InetSocketAddress("localhost",8899)) // 绑定连接端口和host信息
                    .handler(new MyClientInitializer());
            ChannelFuture channelFuture = bootstrap.connect().sync();// 异步连接服务器
            channelFuture.channel().closeFuture().sync();//异步等待关闭连接channel
        }finally {
            eventLoopGroup.shutdownGracefully().sync();// 释放线程池资源
        }
    }
}
public class MyClientHandler extends SimpleChannelInboundHandler<Object> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
//TODO 调用服务端,C可以是服务名称
        ctx.writeAndFlush("C");
//服务端的远程地址
//System.out.println(ctx.channel().remoteAddress());
        System.out.println("client output: "+msg.toString()+"time"+System.currentTimeMillis());//解码器,JacksonDecoder,已经将消息转成对象
    }
    /**
     * 当服务器端与客户端进行建立连接的时候会触发,如果没有触发读写操作,则客户端和客户端之间不会进行数据通信,也就是channelRead0不会执行,
     * 当通道连接的时候,触发channelActive方法向服务端发送数据触发服务器端的handler的channelRead0回调,然后
     * 服务端向客户端发送数据触发客户端的channelRead0,依次触发。
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush("客户端与服务端通道-关闭:"+ ctx.channel().localAddress() + "channelInactive");
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
public class MyClientInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
        pipeline.addLast(new LengthFieldPrepender(4));
        // pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));//字符串
        // pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));//字符串
        // pipeline.addLast(new ByteArrayDecoder());//字节码
        // pipeline.addLast(new ByteArrayEncoder());//字节码
        pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));//字符串
        pipeline.addLast(new JacksonDecoder(Student.class));//Jackson
        pipeline.addLast(new MyClientHandler());
    }
}

四、Netty  RPC支持的编解码组件很多,也可以自定义扩展:

RPC通信编解码,Netty框架支持很多种:

自定义编解码:

public class JacksonDecoder extends MessageToMessageDecoder<ByteBuf> {
    private Class classtype;
    public JacksonDecoder(Class classtype) {
        this.classtype=classtype;
    }
    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        byte[] array = new byte[byteBuf.readableBytes()];
        byteBuf.getBytes(0, array);
        IJacksonSerializer serializer= new JacksonSerializer();
        Object deserialize = serializer.deserialize(array, classtype);
        list.add(deserialize);
    }
}


public class JacksonEncoder extends MessageToMessageEncoder<byte[]> {
    public JacksonEncoder() {
    }
    @Override
    protected void encode(ChannelHandlerContext channelHandlerContext, byte[] bytes, 
          List<Object> list) throws Exception {
         //TODO 待实现
    }
}

五、ByteBuf介绍:

       Netty框架自带的组件,以下面的一段代码为例,这一块涉及零拷贝相关知识点,本处不做详细展开,后续单独详解。

    byte[] array = new byte[byteBuf.readableBytes()];
    byteBuf.getBytes(0, array);

六、线程池:

         NioEventLoopGroup   底层采用socket采用ServerSocketChannel,链接请求会注册到selector选择器:

    EpollEventLoopGroup  只支持在Linux下使用,底层socket采用epoll,如果在windows下运行,服务的代码运行直接报“epoll Only supported on Linux” 错误:

        

        底层根据实际JDK的版本,如果是Linux,则会采用epoll模型。

     

     当前本地是WINDOWS: 

猜你喜欢

转载自blog.csdn.net/jason_jiahongfei/article/details/113281138