十六.Netty实现RPC框架

1.RPC是什么?

RPC,远程过程调用,可以做到像本地调用一样调用远程服务,是一种进程间的通信方式,概念想必大家都很清楚,可以换一种思考方式去理解RPC,也就是从本地调用出发,进而去推导RPC调用
在这里插入图片描述
1.本地函数调用
本地函数是我们经常碰到的,比如下面示例:

public String sayHello(String name) {
    return "hello, " + name;
}

我们只需要传入一个参数,调用sayHello方法就可以得到一个输出,也就是输入参数——>方法体——>输出,入参、出参以及方法体都在同一个进程空间中,这就是本地函数调用
2. Socket通信
那有没有办法实现不同进程之间通信呢?调用方在进程A,需要调用方法A,但是方法A在进程B中
在这里插入图片描述
最容易想到的方式就是使用Socket通信,使用Socket可以完成跨进程调用,我们需要约定一个进程通信协议,来进行传参,调用函数,出参。写过Socket应该都知道,Socket是比较原始的方式,我们需要更多的去关注一些细节问题,比如参数和函数需要转换成字节流进行网络传输,也就是序列化操作,然后出参时需要反序列化;使用socket进行底层通讯,代码编程也比较容易出错。

如果一个调用方需要关注这么多问题,那无疑是个灾难,所以有没有什么简单方法,让我们的调用方不需要关注细节问题,让调用方像调用本地函数一样,只要传入参数,调用方法,然后坐等返回结果就可以了呢?

3. RPC框架
RPC框架就是用来解决上面的问题的,它能够让调用方像调用本地函数一样调用远程服务,底层通讯细节对调用方是透明的,将各种复杂性都给屏蔽掉,给予调用方极致体验。
在这里插入图片描述

RPC 核心功能
前面说了这么多,再次总结下一个RPC框架需要重点关注哪几个点:

  • 代理 (动态代理)
  • 通讯协议
  • 序列化
  • 网络传输

在这里插入图片描述
一个 RPC 的核心功能主要有 5 个部分组成,分别是:客户端、客户端 Stub、网络传输模块、服务端 Stub、服务端等
在这里插入图片描述
PRC调用流程说明

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

需求说明

  1. dubbo 底层使用了 Netty 作为网络通讯框架,要求用 Netty 实现一个简单的 RPC 框架
  2. 模仿dubbo,消费者和提供者约定接口和协议,消费者远程调用提供者的服务,提供者返回一个字符串,消费者打印提供者返回的数据。底层网络通信使用Netty 4.1.20

公共接口
HelloService

public interface HelloService {

    String hello(String msg);
}

HelloServiceImpl

public class HelloServiceImpl implements HelloService {

  //当有消费方调用该方法时, 就返回一个结果
  @Override
  public String hello(String msg) {
    System.out.println("收到客户端消息=" + msg);
    //根据mes 返回不同的结果
    if (msg!= null) {
      return "你好客户端, 我已经收到你的消息 [" + msg+ "]";
    } else {
      return "你好客户端, 我已经收到你的消息 ";
    }
  }
}

服务端
NettyServer

public class NettyServer {

  public static void startServer(String hostName, int port) {
    startServer0(hostName, port);
  }

  //编写一个方法,完成对NettyServer的初始化和启动

  private static void startServer0(String hostname, int port) {

    EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    EventLoopGroup workerGroup = new NioEventLoopGroup();

    try {

      ServerBootstrap serverBootstrap = new ServerBootstrap();

      serverBootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
          .childHandler(new ChannelInitializer<SocketChannel>() {
                          @Override
                          protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyServerHandler()); //业务处理器

                          }
                        }

          );

      ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync();
      System.out.println("服务提供方开始提供服务~~");
      channelFuture.channel().closeFuture().sync();

    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      bossGroup.shutdownGracefully();
      workerGroup.shutdownGracefully();
    }

  }
}

NettyServerHandler

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

  @Override
  public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
    //获取客户端发送的消息,并调用服务
    System.out.println("msg=" + msg);
    //客户端在调用服务器的api 时,我们需要定义一个协议
    //比如我们要求 每次发消息是都必须以某个字符串开头 "HelloService#hello#你好"
    if (msg.toString().startsWith(ClientBootstrap.providerName)) {

      String result = new HelloServiceImpl().hello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1));
      ctx.writeAndFlush(result);
    }
  }

  @Override
  public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    ctx.close();
  }
}

ServerBootstrap

//ServerBootstrap 会启动一个服务提供者,就是 NettyServer
public class ServerBootstrap {
  public static void main(String[] args) {

    NettyServer.startServer("127.0.0.1", 7000);
  }

客户端
NettyClient

public class NettyClient {

  //创建线程池
  private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

  private static NettyClientHandler client;

  //编写方法使用代理模式,获取一个代理对象

  public Object getBean(final Class<?> serviceClass, final String providerName) {

    return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] { serviceClass },
        new InvocationHandler() {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //{}  部分的代码,客户端每调用一次 hello, 就会进入到该代码
            if (client == null) {
              initClient();
            }

            //设置要发给服务器端的信息
            //providerName 协议头 args[0] 就是客户端调用api hello(???), 参数
            client.setPara(providerName + args[0]);

            //
            return executor.submit(client).get();

          }
        });
  }

  //初始化客户端
  private static void initClient() {
    client = new NettyClientHandler();
    //创建EventLoopGroup
    NioEventLoopGroup group = new NioEventLoopGroup();
    Bootstrap bootstrap = new Bootstrap();
    bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
        .handler(new ChannelInitializer<SocketChannel>() {
          @Override
          protected void initChannel(SocketChannel ch) throws Exception {
            ChannelPipeline pipeline = ch.pipeline();
            pipeline.addLast(new StringDecoder());
            pipeline.addLast(new StringEncoder());
            pipeline.addLast(client);
          }
        });

    try {
      bootstrap.connect("127.0.0.1", 7000).sync();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}

NettyClientHandler

public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {

    private ChannelHandlerContext context;//上下文
    private String result; //返回的结果
    private String para; //客户端调用方法时,传入的参数


    //与服务器的连接创建后,就会被调用, 这个方法是第一个被调用(1)
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(" channelActive 被调用  ");
        context = ctx; //因为我们在其它方法会使用到 ctx
    }

    //收到服务器的数据后,调用方法 (4)
    //
    @Override
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println(" channelRead 被调用  ");
        result = msg.toString();
        notify(); //唤醒等待的线程
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }

    //被代理对象调用, 发送数据给服务器,-> wait -> 等待被唤醒(channelRead) -> 返回结果 (3)-》5
    @Override
    public synchronized Object call() throws Exception {
        System.out.println(" call1 被调用  ");
        context.writeAndFlush(para);
        //进行wait
        wait(); //等待channelRead 方法获取到服务器的结果后,唤醒
        System.out.println(" call2 被调用  ");
        return  result; //服务方返回的结果

    }
    //(2)
    void setPara(String para) {
        System.out.println(" setPara  ");
        this.para = para;
    }
}

ClientBootstrap

public class ClientBootstrap {

  //这里定义协议头
  public static final String providerName = "HelloService#hello#";

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

    //创建一个消费者
    NettyClient customer = new NettyClient();

    //创建代理对象
    HelloService service = (HelloService) customer.getBean(HelloService.class, providerName);

    for (; ; ) {
      Thread.sleep(2 * 1000);
      //通过代理对象调用服务提供者的方法(服务)
      String res = service.hello("你好 dubbo~");
      System.out.println("调用的结果 res= " + res);
    }
  }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值