Netty -12- 用 Netty自己实现 RPC

RPC 基本介绍

  1. RPC(Remote Procedure Call)— 远程过程调用,是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程

  2. 两个或多个应用程序都分布在不同的服务器上,它们之间的调用都像是本地方法调用一样(如图)

  3. 常见的 RPC 框架有: 比较知名的如阿里的Dubbo、google的gRPC、Go语言的rpcx、Apache的thrift,Spring 旗下的 Spring Cloud


RPC 调用流程图


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)得到结果

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


自己实现 dubbo RPC(基于 Netty)

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

设计说明
  1. 创建一个接口,定义抽象方法。用于消费者和提供者之间的约定

  2. 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据

  3. 创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用 Netty 请求提供者返回数据

  4. 开发的分析图


  1. 目录结构


publicInterface(代码)

IHelloService
public interface IHelloService {

    String hello(String msg);

}

provider(代码)

HelloServiceImpl
public class HelloServerImpl implements IHelloService {

    /**
    * @Description 当消费方调用该方法时,就返回一个结果
    * @date 2020/7/25 16:32
    * @param msg
    * @return java.lang.String
    */
    @Override
    public String hello(String msg) {
        System.out.println("收到客户端消息:"+msg);
        //根据msg,返回不同的结果
        if(msg!=null){
            return "你好客户端,我已经收到你的消息["+msg+"]";
        }else{
            return "大妹子,你没发送信息呀";
        }

    }
}

ServerBootStrap
public class ServerBootStrap {
    public static void main(String[] args) {
        //启动服务
        NettyServer.startServer("127.0.0.1",50000);
    }
}

netty

NettyServer
public class NettyServer {


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


    private static  void startServer0(String host,Integer port){
        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)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        ChannelPipeline pipeline = sc.pipeline();

                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new StringEncoder());
                        //业务处理器
                        pipeline.addLast(new NettyServerHandler());
                    }
                });
            ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(port)).sync();


            //进行监听
            channelFuture.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    System.out.println(Thread.currentThread().getName());
                    if(future.isSuccess()){
                        System.out.println("服务提供方开始 提供服务成功");
                    }else{
                        System.out.println("服务提供方开始 提供服务失败");
                    }
                }
            });

            channelFuture.channel().closeFuture().sync();

        } catch (InterruptedException 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(Thread.currentThread().getName()+"  msg="+msg);

        //客户端在调用服务器的api时,我们需要定义一个协议
        //比如我们要求 每次发送消息都是必须以某个字符串开头"HelloService#hello#"
        if(msg.toString().startsWith("HelloService#hello#")){
            String  result= new HelloServerImpl().hello(msg.toString().substring(msg.toString().lastIndexOf('#')+1));
            ctx.writeAndFlush(result);
        }
    }


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("异常关闭"+cause.getMessage());
        ctx.close();
    }
}

NettyClient
public class NettyClient {

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

    private  static ThreadFactory threadFactory=new DefaultThreadFactory("netty-client");

    private static  ExecutorService executor=new ThreadPoolExecutor(10,10,
                                                                    60L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(10),threadFactory);

    //使用guava提供的ThreadFactoryBuilder来创建线程池(推荐)
    //    private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
    //            .setNameFormat("demo-pool-%d").build();

    private static NettyClientHandler clientHandler;

    public Object getBean(final Class<?> serviceClass,final  String providerName){
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),new Class<?>[]{serviceClass},
                                      ((proxy, method, args) -> {
                                          if(clientHandler==null){
                                              initClient();
                                          }
                                          //设置要发给服务器端的信息
                                          //协议头+调用远程api的参数
                                          clientHandler.setParam(providerName+args[0]);
                                          return executor.submit(clientHandler).get();
                                      }));
    }

    private static void initClient(){
        clientHandler=new NettyClientHandler();
        //创建EventLoopGroup
        EventLoopGroup group=new NioEventLoopGroup();

        try {
            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(clientHandler);
                    }
                });

            ChannelFuture channelFuture = bootstrap.connect(new InetSocketAddress("127.0.0.1", 50000)).sync();


        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

NettyClientHandler
public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable {

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

    /**
    * @Description 与服务器的连接创建后,方法会被调用(1)
    * @date 2020/7/25 17:07
    * @param ctx
    * @return void
    */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //我在其他方法会用到ctx
        this.context=ctx;
    }


    /**
    * @Description 收到服务器的数据后,方法会被调用(4)
    * @date 2020/7/25 17:08
    * @param ctx
    * @param msg
    * @return void
    */
    @Override
    public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("从服务端获取消息  "+Thread.currentThread().getName());
        this.result=msg.toString();
        //唤醒等待的线程
        notify();
    }

    /**
    * @Description 被代理对象调用,真正发送数据给服务器-->wait-->等待被唤醒,获取结果(3)-->(5)
    * @date 2020/7/25 17:12
    * @return java.lang.Object
    */
    @Override
    public synchronized Object call() throws Exception {
        System.out.println("代理发送消息  "+Thread.currentThread().getName());

        context.writeAndFlush(param);
        //进行wait,等待channelRead方法获取服务器的结果后,唤醒
        wait();
        //返回服务方返回的结果
        return this.result;
    }

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

    /**
    * @Description 设置参数(2)
    * @date 2020/7/25 17:20
    * @param param  
    * @return void
    */
    public void setParam(String param){
        this.param=param;
    }

}


consumer

ClientBootStrap
public class ClientBootStrap {

    private static final  String providerName="HelloService#hello#";

    public static void main(String[] args) {
        //创建一个消费者
        NettyClient consumer = new NettyClient();

        //创建一个代理对象
        IHelloService helloService=(IHelloService)consumer.getBean(IHelloService.class,providerName);

        //调用服务提供者的方法
        String res = helloService.hello("你好呀");
        System.out.println("调用的结果res="+res);

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值