java提高_netty

netty大名鼎鼎,现在应用广泛,本文主要介绍netty的概述,API以及几个小demo,希望能记录自己的学习笔记,分享大家,共同进步。

1.概述

Netty是由JBOSS提供的一个Java开源框架,提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性网络IO程序。

作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得广泛应用,Elasticsearch、Dubbo框架内部都采用Netty。

相关概念
异步

线程同步、异步都是相对的,请求或执行过程中,如果会阻塞等待,就是同步操作,反之就是异步。

核心架构

如下图所示,核心 和传输服务以及支持的各种协议

在这里插入图片描述

核心:

  • 可扩展的事件模型
  • 统一的通信api,简化通信编码
  • 零拷贝机制和丰富的字节缓冲区

传输服务

  • 支持socket和datagram(数据报)
  • http传输服务
  • in-VM Pipe(管道协议,是jvm的一种进程)

协议支持

  • http和websocket
  • SSL安全套接字协议支持
  • Google protobuf(序列化框架)
  • 支持zlib、gzip压缩
  • 支持大文件的传输
  • RTSP(实时流传输协议)
  • 支持二进制协议并提供完整的单元测试

2. Netty的整体设计

1. Reactor模型

Reactor模型,不是java或netty专属的,而是一种并发编程思想,具有指导意义。

该模型定义三种角色

  • Reactor: 负责监听和分配事件,将IO事件分派给对应的handler,新的事件包含连接建立就绪、读就绪、写就绪等。
  • Accept:处理客户端新连接,并分派请求到处理器链中
  • Handler:将自身与事件绑定,执行非阻塞读/写任务,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel

Nio下reactor单线程

所有的接收连接,处理数据的操作都在一个线程中完成,性能有瓶颈。
在这里插入图片描述

单reactor-单线程

随着业务量增大,演化出分工明确的reactor、handler、acceptor,但还是单reactor
在这里插入图片描述

单reactor-多线程

Nio下reactor多线程

将耗时的数据编解码、运算操作放入线程池,提升了性能
在这里插入图片描述
对应模型
在这里插入图片描述
缺点:仍无法应对大量客户端的请求,客户端请求仍需排队,导致部分请求的响应时间过长

主从reactor-多线程

主从多线程,对服务器来说,接收客户端的连接较为重要,因此,单独拆分个线程执行
在这里插入图片描述
在这里插入图片描述

主从reactor的工作模式

  1. reactor主线程MainReactor对象通过select监听客户端连接事件,收到事件,通过Acceptor处理client连接事件
  2. 当Acceptor处理完client连接事件后,MainReactor将连接分配给SubReactor,SubReactor监听后续IO事件
  3. SubReactor等连接加入到自己的连接队列后进行监听,并创建Handler对各种事件处理
  4. 当连接上有新的事件发生时,SubReactor调对应的Handler处理
  5. Handler通过read从连接上读取请求数据,将请求数据分发给worker线程池处理业务
  6. worker线程池分配独立线程完成业务处理,将处理结果返回Handler,Handler通过send向client发送响应数据
  7. 一个MainReactor可对应多个SubReactor

优势明显,可应对更高的并发请求,被nginx、memcached和netty等运用

也叫做服务器的1+M+N线程模式,即采用1个(或多个,1表示相对较少)连接建立线程+M个IO线程+N个业务处理线程。

netty中的Reactor实现

netty对三种模式都支持,一般在server端采用主从架构模型
在这里插入图片描述
在这里插入图片描述

另一种演进思路:单线程-线程池-netty的多路复用

单线程

在这里插入图片描述

服务器端用一个线程通过多路复用搞定所有的IO操作(包括连接、读写等),编码简单,清晰明了,如果客户端连接数量较多,无法支撑,之前的NIO案例就属于该模型。

线程池模型

在这里插入图片描述

服务器端采用一个线程专门处理客户端连接请求,采用一个线程池负责IO操作,在绝大多数场景中,该模型都能满足使用。

Netty模型

在这里插入图片描述

比较类似于上面的线程池模型,netty抽象出两组线程池,BossGroup专门负责接收客户端的链接,WorkerGroup专门负责网络读写操作,NioEventLoop表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket网络通道,NioEventLoop内部采用串行化设计(一条龙服务),从消息的读取->解码->处理->编码->发送,始终由IO线程NioEventLoop负责。

  • 一个NioEventLoopGroup下包含多个NioEventLoop
  • 每个NioEventLoop中包含一个Selector,一个taskQueue
  • 每个NioEventLoop的Selector上可以注册监听多个NioChannel
  • 每个NioChannel只会绑定在唯一的NioEventLoop上。
  • 每个NioChannel都绑定有一个自己的ChannelPipeline
2. 异步模型
Future Callback和Handler

Netty的异步模型是建立在future和callback之上的。callback我们都知道,future的核心思想是:假设一个方法fun,可能是个耗时操作,在调用fun时,立马返回一个future,后续通过future监控fun的处理过程。

小栗子:网上外卖,手机可以监控外卖小哥路径。

使用netty编程时,拦截操作和转换出入站数据只需要提供callback或者利用future即可。使得链式操作简单高效,利于编写可重用的、通用的代码,将业务逻辑从网络基础应用编码中分离出来、解脱出来。

在这里插入图片描述

3. 核心API

ChannelHandler及其实现类

channelHandler接口定义了许多事件处理的方法,通过重写这些方法实现具体业务逻辑。API关系如图

在这里插入图片描述

我们经常需要自定义一个Handler类去继承ChannelInboundHandlerAdapter,然后重写相应方法实现业务逻辑,下边看一般需要重写哪些方法:

  • public void channelActive(ChannelHandlerContext ctx):通道就绪事件
  • public void channelRead(ChannelHandlerContext ctx,Object msg):通道读取数据事件
  • public void channelReadComplete(ChannelHandlerContext ctx):数据读取完毕事件
  • public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause):通道发生异常事件
pipeline和ChannelPipeline

channelPipeline是一个Handler的集合,负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿Netty的链。

在这里插入图片描述

  • ChannelPipeline addFirst(ChannelHandler... handlers):把一个业务处理类(handler)添加到链中的第一个位置
  • ChannelPipeline addLast(ChannelHandler... handlers):把一个业务处理类(handler)添加到链中的最后一个位置
ChannelHandlerContext

这是事件处理器上下文对象,Pipeline链中的实际处理节点。每个处理节点ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用。常用方法如下

  • ChannelFuture close():关闭通道
  • ChannelOutboundInvoker flush():刷新
  • ChannelFuture writeAndFlush(Object msg):将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出站)
ChannelOption

netty在创建Channel实例后,一般都需要设置ChannelOption参数,ChannelOption是Socket的标准参数,非netty独创。常用参数配置:

  1. ChannelOption.SO_BACKLOG: 对应TCP/IP协议listen函数的backlog参数,初始化服务器可连接队列大小。服务器端处理客户端连接请求是顺序处理的,同一时间只能处理一个客户端连接。多个客户端来的时候,服务器端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定队列的大小
  2. ChannelOption.SO_KEEPALIVE:一直保持连接活动状态
ChannelFuture

表示Channel的异步IO操作的结果,在netty中所有的IO操作都是异步的,IO的调用会直接返回,调用者不能立刻获取结果,但可以通过ChannelFuture获取IO操作的处理状态。常用方法:

  • Channel channel():返回当前正在进行IO操作的通道
  • ChannelFuture sync():等待异步操作执行完毕
EventLoopGroup及其实现类NioEventLoopGroup

EventLoopGroup是一组EventLoop的抽象,netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护一个Selector实例。

EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务。在Netty服务器端编程中,一般都需要提供两个EventLoopGroup。例如:BossEventLoopGroup和WorkerEventLoopGroup

通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO操作。

在这里插入图片描述

BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护一个注册了ServerSocketChannel的Selector实例,BossEventLoop不断轮询Selector将连接事件分离出来,通常是OP_ACCEPT事件,然后将接收到的SocketChannel交给WorkerEventLoopGroup,WorkerEventLoopGroup会由next选择其中一个EventLoopGroup将这个SocketChannel注册到其维护的Selector并对后续的IO事件处理。

常用方法:

  • public NioEventLoopGroup():构造方法
  • public Future<?>shutdownGracefully():断开连接,关闭线程
ServerBootstrap和Bootstrap

ServerBootstrap是netty中的服务器端启动助手,通过它可以完成服务器端的各种配置,Bootstrap是netty中的客户端启动助手,通过它客户端的各种配置。常用方法:

  • public ServerBootstrap group(EventLoopGroup parentGroup,EventLoopGroup childGroup):该方法用于服务器端,用来设置两个EventLoop
  • public B group(EventLoopGroup group):用于客户端,用来设置一个EventLoop
  • public B channel(Class<? extends C> channelClass):用于设置一个服务器端的通道实现
  • public <T> B option(ChannelOption<T> option,T value):用来给ServerChannel添加配置
  • public <T> ServerBootstrap childOption(ChannelOption<T> childOption,T value):用来给接收到的通道添加配置
  • public ServerBoootstrap childHandler(ChannelHandler childHandler):用来设置业务处理类(早定义的handler)
  • public ChannelFuture bind(int inetPort):用于服务器端,用来设置占用的端口号
  • public ChannelFuture connect(String inetHost,int inetPort):该方法用于客户端,用来连接服务器端
Unpooled类

这是netty提供的一个专门用来操作缓冲区的工具类,常用方法:

  • public static ByteBuf copiedBuffer(ChanSequence string,Charset charset):通过给定的数据和字符编码返回一个ByteBuf对象(类似NIO的ByteBuffer对象)

4. 入门案例

0. pom文件的准备
...
<dependencies>
  <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.8.Final</version>
  </dependency>
</dependencies>
<build>
  <plugins>
    <plugin>
      <groupId>org.apache.maven.plugins</groupId>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.2</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <encoding>UTF-8</encoding>
        <showWarnings>true</showWarnings>
      </configuration>
    </plugin>
  </plugins>
 </build>
1. basic demo

netty一般需要4个类,NettyClient NettyClientHandler(业务处理类) NettyServer NettyServerHandler(业务处理类)

NettyServerHandler
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    // 读取数据事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Server: "+ctx);
        ByteBuf buf = (ByteBuf)msg; //需要强转为ByteBuf,缓冲区
        System.out.println("客户端发来的消息:"+buf.toString(CharsetUtil.UTF_8));
    }

    // 数据读取完毕事件
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer("就是没钱",CharsetUtil.UTF_8));
    }

    // 异常发生事件
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close(); //关闭上下文,上下文是所有信息的汇总
        super.exceptionCaught(ctx, cause);
    }
}
NettyServer
public class NettyServer {
    public static void main(String[] args) throws Exception {

        // 1. 创建一个线程组:接收客户端连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 2. 创建一个线程组:处理网络操作
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        // 3. 创建服务器端启动助手来配置参数
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup,workerGroup)//设置两个线程组
            .channel(NioServerSocketChannel.class)//5.使用NioServerSocketChannel作为服务器端通道的实现
            .option(ChannelOption.SO_BACKLOG,128)//6.设置线程队列中等待连接的个数
            .childOption(ChannelOption.SO_KEEPALIVE,true)//7.保持活动连接状态
            .childHandler(new ChannelInitializer<SocketChannel>() {//8.创建一个通道初始化对象
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {//9.往pipeline链添加自定义的handler类
                    socketChannel.pipeline().addLast(new NettyServerHandler());
                }
            });
        System.out.println(".......Server is ready......");
        ChannelFuture cf = b.bind(9999).sync();//绑定端口 非阻塞
        System.out.println(".......Server is starting.........");

        // 11.关闭通道,关闭线程组
        cf.channel().closeFuture().sync();//异步
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}
NettyClientHandler
public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    // 通道就绪事件
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client: "+ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("老板,还钱吧", CharsetUtil.UTF_8));
    }

    // 读取数据事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buf = (ByteBuf)msg;
        System.out.println("服务器端发来的消息:"+buf.toString(CharsetUtil.UTF_8));
    }

}
NettyClient
public class NettyClient {

    public static void main(String[] args) throws Exception {
        // 1. 创建一个线程组
        EventLoopGroup group = new NioEventLoopGroup();
        // 2. 创建客户端的启动助手,完成相关配置
        Bootstrap b = new Bootstrap();
        b.group(group) // 3.设置线程组
            .channel(NioSocketChannel.class) //4.设置客户端通道的实现类
            .handler(new ChannelInitializer<SocketChannel>() {// 5.创建一个通道初始化对象
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast(new NettyClientHandler());//6.往pipeline链中添加自定义的handler
                }
            });
        System.out.println("........Client is ready............");
        // 7.启动客户端去连接服务器端,connect方法是异步的 ,sync方法是同步阻塞的
        ChannelFuture cf = b.connect("127.0.0.1",9999).sync();
        // 8.关闭连接(异步非阻塞)
        cf.channel().closeFuture().sync();
    }
}
2. 网络聊天demo
ChatServerHandler

业务处理类,继承ChannelInboundHandler的子类SimpleChannelInboundHandler<String>

public class ChatServerHandler extends SimpleChannelInboundHandler<String> {

    public static List<Channel> channels = new ArrayList<>();

    // 通道就绪
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel inChannel = ctx.channel();
        channels.add(inChannel);
        System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"上线");
    }

    // 通道未就绪
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel inChannel = ctx.channel();
        channels.remove(inChannel);
        System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"离线");
    }

    // 读取数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
        Channel inChannel = ctx.channel();
        for (Channel channel : channels) {
            if(channel!=inChannel){
                channel.writeAndFlush("["+inChannel.remoteAddress().toString().substring(1)+"]说: "+s+"\n");
            }
        }
    }
}
ChatServer

注意:往pipeline链中添加处理字符串的编码器和解码器,加入到pipeline链会自动工作,不用人工处理ByteBuf

public class ChatServer {
    private int port; //服务器端端口号

    public ChatServer(int port){
        this.port = port;
    }

    public void run() throws Exception{
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {

                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 往pipeline链中添加一个解码器
                            pipeline.addLast("decoder",new StringDecoder());
                            // 往pipeline链中添加一个编码器,字符串->二进制
                            pipeline.addLast("encoder",new StringEncoder());
                            //  往pipeline链中添加自定义的handler业务处理类
                            pipeline.addLast(new ChatServerHandler());
                        }
                    });
            System.out.println("Netty Chat Server启动...");
            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        }finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
            System.out.println("Netty Chat Server关闭");
        }
    }

    public static void main(String[] args) throws Exception {
        new ChatServer(9999).run();
    }
}
ChatClientHandler
public class ChatClientHander extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
        System.out.println(s.trim());
    }
}
ChatClient

字符串的编解码同server

public class ChatClient {
    private final String host;//服务器端IP地址
    private final int port;//服务器端端口号

    public ChatClient(String host,int port){
        this.host = host;
        this.port = port;
    }

    public void run(){
        EventLoopGroup group = new NioEventLoopGroup();
        try{
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 往pipeline链中添加一个解码器
                            pipeline.addLast("decoder",new StringDecoder());
                            // 往pipeline链中添加一个编码器,字符串->二进制
                            pipeline.addLast("encoder",new StringEncoder());
                            //  往pipeline链中添加自定义的handler业务处理类
                            pipeline.addLast(new ChatClientHander());
                        }
                    });
            ChannelFuture cf = bootstrap.connect(host,port).sync();
            Channel channel = cf.channel();
            System.out.println("-------"+channel.localAddress().toString().substring(1)+"-----");
            Scanner sc = new Scanner(System.in);
            while(sc.hasNextLine()){
                String msg = sc.nextLine();
                channel.writeAndFlush(msg+"\r\n");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            group.shutdownGracefully();
        }
    }
    
    public static void main(String[] args) {
        new ChatClient("127.0.0.1",9999).run();
    }
}

5. 编解码

1. 概述

在编写网络应用程序时需要注意codec(编解码器),因为数据网络传输的时候是二进制字节码数据,而拿到的目标数据往往不是字节码数据,因此发送的时候要编码,接收的时候要解码。

codec的组成部分有两个:decoder和encoder。其实Java的序列化就可以作为codec,但是缺陷太多:

  • 无法跨语言
  • 序列化后体积太大,是二进制编码的5倍多。
  • 序列化性能太低。

netty提供的解码器:

  1. StringDecoder,对字符串数据进行解码
  2. ObjectDecoder,对java对象进行解码

编码器对应解码器

netty自带的ObjectDecoder和ObjectEncoder可以用来实现POJO对象或各种业务对象的编码和解码,但是内部使用的还是java序列化技术。

2. Google的Protobuf

谷歌开源项目,特点:

  1. 支持跨平台、多语言(支持绝大多数语言,如C++、C#、Java、python等)
  2. 高性能、高可靠性
  3. 使用protobuf编译器自动生成代码,Protobuf是将类的定义使用.proto文件进行描述,然后通过protoc.exe编译器根据.proto自动生成.java文件

目前使用netty开发时,经常会结合Protobuf作为codec去使用。可以用idea的插件,也可以在命令行直接编译器生成java文件,推荐后者。

具体用法:

1. pom文件引入坐标
<dependencies>
  <!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
  <dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.8.Final</version>
  </dependency>

  <dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.6.1</version>
  </dependency>
</dependencies>
2. 编写proto文件

假设处理的是图书信息

Book.proto

syntax = "proto3";
option java_outer_classname = "BookMessage";
message Book{
    int32 id = 1;
    string name = 2;
}
  1. 设置版本号
  2. 设置生成的Java类名
  3. 内部类的类名,真正的POJO
  4. 设置类中的属性,等号后是序号,不是属性值
3. 通过protoc.exe根据描述文件生成Java类

BookMessage.java

package cn.itcast.netty.codec;// Generated by the protocol buffer compiler.  DO NOT EDIT!
// source: Book.proto

public final class BookMessage {
  private BookMessage() {}
  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistryLite registry) {
  }

  public static void registerAllExtensions(
      com.google.protobuf.ExtensionRegistry registry) {
    registerAllExtensions(
        (com.google.protobuf.ExtensionRegistryLite) registry);
  }
  // ...
    private Book(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
      super(builder);
    }
    private Book() {
      id_ = 0;
      name_ = "";
    }

   // ...
}

将BookMessage复制到同一个包下。

5. netty中使用

客户端和服务器端添加业务处理类时使用

NettyClient

public class NettyClient {

    public static void main(String[] args) throws Exception {
        // 1. 创建一个线程组
        EventLoopGroup group = new NioEventLoopGroup();
        // 2. 创建客户端的启动助手,完成相关配置
        Bootstrap b = new Bootstrap();
        b.group(group) // 3.设置线程组
            .channel(NioSocketChannel.class) //4.设置客户端通道的实现类
            .handler(new ChannelInitializer<SocketChannel>() {// 5.创建一个通道初始化对象
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast("encoder",new ProtobufEncoder());
                    socketChannel.pipeline().addLast(new NettyClientHandler());//6.往pipeline链中添加自定义的handler
                }
            });
        System.out.println("........Client is ready............");
        // 7.启动客户端去连接服务器端,connect方法是异步的 ,sync方法是同步阻塞的
        ChannelFuture cf = b.connect("127.0.0.1",9999).sync();
        // 8.关闭连接(异步非阻塞)
        cf.channel().closeFuture().sync();
    }
}

NettyClientHandler

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    // 通道就绪事件
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        BookMessage.Book book = BookMessage.Book.newBuilder().setId(1).setName("Java23种设计模式").build();
        ctx.writeAndFlush(book);
    }
}

NettyServer

public class NettyServer {
    public static void main(String[] args) throws Exception {

        // 1. 创建一个线程组:接收客户端连接
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 2. 创建一个线程组:处理网络操作
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        // 3. 创建服务器端启动助手来配置参数
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup,workerGroup)//设置两个线程组
            .channel(NioServerSocketChannel.class)//5.使用NioServerSocketChannel作为服务器端通道的实现
            .option(ChannelOption.SO_BACKLOG,128)//6.设置线程队列中等待连接的个数
            .childOption(ChannelOption.SO_KEEPALIVE,true)//7.保持活动连接状态
            .childHandler(new ChannelInitializer<SocketChannel>() {//8.创建一个通道初始化对象
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {//9.往pipeline链添加自定义的handler类
                    socketChannel.pipeline().addLast("decoder",new ProtobufDecoder(BookMessage.Book.getDefaultInstance()));
                    socketChannel.pipeline().addLast(new NettyServerHandler());
                }
            });
        System.out.println(".......Server is ready......");
        ChannelFuture cf = b.bind(9999).sync();//绑定端口 非阻塞
        System.out.println(".......Server is starting.........");

        // 11.关闭通道,关闭线程组
        cf.channel().closeFuture().sync();//异步
        bossGroup.shutdownGracefully();
        workerGroup.shutdownGracefully();
    }
}

NettyServerHandler

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    // 读取数据事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        BookMessage.Book book = (BookMessage.Book)msg;
        System.out.println("客户端发来数据: "+book.getName());
    }
}

6. 自定义RPC

1. 概述

RPC(Remote Procedure Call),即远程过程调用。是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络实现的技术。常见RPC框架有:阿里的Dubbo,Spring的Spring Cloud,Google的grpc等。

在这里插入图片描述

实现步骤

  1. 服务消费方client以本地调用方式调用服务
  2. client stub接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
  3. client stub将消息进行编码并发送到服务端
  4. server stub收到消息后进行解码
  5. server stub根据解码结果调用本地的服务
  6. 本地服务server执行并将结果返回给本地的服务
  7. server stub将返回导入结果进行编码并发送到消费方
  8. client stub接收消息并进行解码
  9. client得到结果

RPC的目标就是讲2-8步骤封装,用户无需关心这些细节,可以像调用本地方法一样即可完成远程服务调用。

基于netty我们diy一个RPC

2. 结构设计

在这里插入图片描述

  • Client:两个接口+一个包含main方法的测试类
  • Client Stub:一个客户端代理类+一个客户端业务处理类
  • Server:两个接口+两个实现类
  • Server Stub:一个网络处理服务器+一个服务器业务处理类

注意:服务调用方的接口必须跟服务提供方的接口保持一致(包路径可以不一致)

最终要实现的目标:在TestNettyRPC中远程调用HelloRPCImpl或HelloNettyImpl中的方法

3. 代码实现
Client部分
public interface HelloNetty {
    String hello();
}
public interface HelloRPC {
    String hello(String name);
}
public class TestNettyRPC {
    public static void main(String[] args) {
        // 1.第一次远程调用
        HelloNetty helloNetty = (HelloNetty) NettyRPCProxy.create(HelloNetty.class);
        System.out.println(helloNetty.hello());

        // 2. 第二次远程调用
        HelloRPC helloRPC = (HelloRPC)NettyRPCProxy.create(HelloRPC.class);
        System.out.println(helloRPC.hello("RPC"));
    }
}
Client Stub

客户端代理类

public class NettyRPCProxy {
    // 根据接口创建代理对象
    public static Object create(Class target){
        return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 封装ClassInfo
                ClassInfo classInfo = new ClassInfo();
                classInfo.setClassName(target.getName());
                classInfo.setMethodName(method.getName());
                classInfo.setObjects(args);
                classInfo.setTypes(method.getParameterTypes());

                // 开始用Netty发送数据
                EventLoopGroup group = new NioEventLoopGroup();
                ResultHandler resultHandler = new ResultHandler();
                try{
                    Bootstrap b = new Bootstrap();
                    b.group(group)
                            .channel(NioSocketChannel.class)
                            .handler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                protected void initChannel(SocketChannel socketChannel) throws Exception {
                                    ChannelPipeline pipeline = socketChannel.pipeline();
                                    // 编码器
                                    pipeline.addLast("encoder",new ObjectEncoder());
                                    // 解码器 构造方法第一个参数设置二进制数据最大字节数;第二个设置具体使用哪个类解析器
                                    pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                                    // 客户端业务处理类
                                    pipeline.addLast("handler",resultHandler);
                                }
                            });
                    ChannelFuture future = b.connect("127.0.0.1",9999).sync();
                    future.channel().writeAndFlush(classInfo).sync();
                    future.channel().closeFuture().sync();
                }finally {
                    group.shutdownGracefully();
                }
                return resultHandler.getResponse();
            }
        });
    }
}

客户端业务处理类

public class ResultHandler extends ChannelInboundHandlerAdapter {
    private Object response;
    public Object getResponse(){
        return response;
    }

    // 读取完服务器端返回的数据(远程调用的结果)
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        response = msg;
        ctx.close();
    }
}
Server部分
public interface HelloNetty {
    String hello();
}
public class HelloNettyImpl implements HelloNetty {
    @Override
    public String hello() {
        return "hello,netty";
    }
}
public interface HelloRPC {
    String hello(String name);
}
public class HelloRPCImpl implements HelloRPC {
    @Override
    public String hello(String name) {
        return "hello,"+name;
    }
}
Server Stub

封装类信息

public class ClassInfo implements Serializable {
    private static final long serialVersionUID = 1L;

    private String className;//类名
    private String methodName;//方法名
    private Class<?>[] types;//参数类型
    private Object[] objects;//参数列表

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }
  // ... 各种getter/setter

网络处理服务器

public class NettyRPCServer {
    private int port;
    public NettyRPCServer(int port){
        this.port = port;
    }

    public void start(){
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try{
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup,workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG,128)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .localAddress(port).childHandler(
                    new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            // 编码器
                            pipeline.addLast("encoder",new ObjectEncoder());
                            // 解码器
                            pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            // 服务器端业务处理类
                            pipeline.addLast(new InvokeHandler());
                        }
                    });
            ChannelFuture future = serverBootstrap.bind(port).sync();
            System.out.println("........server is ready.........");
        }catch (Exception e){
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new NettyRPCServer(9999).start();
    }
}

服务器端业务处理类

public class InvokeHandler extends ChannelInboundHandlerAdapter {
    // 得到某接口下某个实现类的名字
    private String getImplClassName(ClassInfo classInfo) throws Exception{
        // 服务方接口和实现类的包路径
        String interfacePath = "cn.itcast.rpc.server";
        int lastDot = classInfo.getClassName().lastIndexOf(".");
        String interfaceName = classInfo.getClassName().substring(lastDot);
        Class superClass = Class.forName(interfacePath+interfaceName);
        Reflections reflections = new Reflections(interfacePath);
        //得到某接口下的所有实现类
        Set<Class> ImplClassSet = reflections.getSubTypesOf(superClass);
        if(ImplClassSet.size()==0){
            System.out.println("未找到实现类");
            return null;
        }else if(ImplClassSet.size()>1){
            System.out.println("找到多个实现类,未明确使用哪一个");
            return null;
        }else{
            // 把集合转换为数组
            Class[] classes = ImplClassSet.toArray(new Class[0]);
            return classes[0].getName();//得到实现类的名字
        }
    }

    // 读取客户端发来的数据并通过反射调用实现类的方法
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ClassInfo classInfo = (ClassInfo)msg;
        Object clazz = Class.forName(getImplClassName(classInfo)).newInstance();
        Method method = clazz.getClass().getMethod(classInfo.getMethodName(),classInfo.getTypes());
        // 通过反射调用实现类的方法
        Object result = method.invoke(clazz,classInfo.getObjects());
        ctx.writeAndFlush(result);
    }
}
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值