基于Netty手写RPC框架

前言

课程目标:

  1. 深刻理解RPC框架设计的基本原理
  2. 基于Netty手写mini版本RPC框架

一、Dubbo四大核心板块 

二、基于Netty手写RPC框架

2.1 maven依赖及包的创建

maven依赖:

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

	<dependency>
		<groupId>org.projectlombok</groupId>
		<artifactId>lombok</artifactId>
		<version>1.16.10</version>
	</dependency>

 包的创建(总览):

正常情况下,provider、consumer、registry不会在一个项目里,但是我们简化了代码,将他们放在一个项目中。

2.2 创建通信协议类

InvokerProtocol.class

public class InvokerProtocol implements Serializable {

    private String className;  //类名
    private String methodName; //方法名称
    private Class<?>[] params; //形参列表
    private Object[] values;   //实参列表

}

2.3 Registry实现

2.3.1 RpcRegistry.class(Netty 服务端启动类)

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


    private void start() {
         // 服务器端应用程序使用两个NioEventLoopGroup创建两个EventLoop的组,EventLoop这个相当于一个处理线程,是Netty接收请求和处理IO请求的线程。
        // 主线程组, 用于接受客户端的连接,但是不做任何处理,跟老板一样,不做事
        EventLoopGroup bossGroup = new NioEventLoopGroup();
         // 从线程组, 当boss接受连接并注册被接受的连接到worker时,处理被接受连接的流量。
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // netty服务器启动类的创建, 辅助工具类,用于服务器通道的一系列配置
            ServerBootstrap server = new ServerBootstrap();
             /**
             * 使用了多少线程以及如何将它们映射到创建的通道取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。
             * 设置循环线程组,前者用于处理客户端连接事件,后者用于处理网络IO(server使用两个参数这个)
             * public ServerBootstrap group(EventLoopGroup group)
             * public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup)
             */
            server.group(bossGroup, workerGroup)
                    // 用于构造socketchannel工厂
                    .channel(NioServerSocketChannel.class)//指定NIO的模式
                    /**
                     * @Description: 初始化器,channel注册后,会执行里面的相应的初始化方法,传入自定义客户端Handle(服务端在这里操作)
                     *
                     @Override
                     protected void initChannel(SocketChannel channel) throws Exception {
                     // 通过SocketChannel去获得对应的管道
                     ChannelPipeline pipeline = channel.pipeline();

                     // 通过管道,添加handler
                     pipeline.addLast("nettyServerOutBoundHandler", new NettyServerOutBoundHandler());
                     pipeline.addLast("nettyServerHandler", new NettyServerHandler());
                     }
                      * 子处理器也可以通过下面的内部方法来实现。
                     */
                    .childHandler(new ChannelInitializer() {// 子处理器,用于处理workerGroup
                        @Override
                        protected void initChannel(Channel ch) throws Exception {
                            //接收课客户端请求的处理流程
                            ChannelPipeline pipeline = ch.pipeline();

                            int fieldLength = 4;
                            //通用解码器设置
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,fieldLength,0,fieldLength));
                            //通用编码器
                            pipeline.addLast(new LengthFieldPrepender(fieldLength));
                            //对象编码器
                            pipeline.addLast("encoder",new ObjectEncoder());
                            //对象解码器
                            pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));

                            pipeline.addLast(new RegistryHandler());
                        }
                    })
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
             // 启动server,绑定端口,开始接收进来的连接,设置8088为启动的端口号,同时启动方式为同步
            ChannelFuture future = server.bind(this.port).sync();
            System.out.println("GP RPC registry is start,listen at " + this.port);
            // 监听关闭的channel,等待服务器 socket 关闭 。设置位同步方式
            future.channel().closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //退出线程组
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        new RpcRegistry(8080).start();
    }

}

上述代码步骤如下: 

  1.  初始化用于Acceptor的主"线程池"以及用于I/O工作的从"线程池";
  2. 初始化ServerBootstrap实例, 此实例是netty服务端应用开发的入口;
  3. 通过ServerBootstrap的group方法,设置(1)中初始化的主从"线程池";
  4. 指定通道channel的类型,由于是服务端,故而是NioServerSocketChannel;
  5. 设置ServerSocketChannel的处理器
  6. 设置子通道也就是SocketChannel的处理器, 其内部是实际业务开发的"主战场"
  7. 配置子通道也就是SocketChannel的选项
  8. 绑定并侦听某个端口

netty编程的精华就在上面的代码中,代码注释已经给出,以后慢慢研究。

2.3.2 RegistryHandler.class(Netty 服务端处理类Handler)

public class RegistryHandler extends ChannelInboundHandlerAdapter {

    //注册中心容器
    private static ConcurrentHashMap<String,Object> registryMap = new ConcurrentHashMap<String, Object>();

    private List<String> classNames = new ArrayList<String>();

    public RegistryHandler(){
        
        //扫描所有需要注册的类
        //com.gupaoedu.vip.netty.rpc.provider
        scannerClass("com.gupaoedu.vip.netty.rpc.provider");
        
        //将扫描到的类注册到一个容器中
        doRegister();
    }

    private void doRegister() {
        if(classNames.size() == 0){return;}

        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                Class<?> i = clazz.getInterfaces()[0];
                registryMap.put(i.getName(),clazz.newInstance());
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    private void scannerClass(String packageName) {
        URL url = this.getClass().getClassLoader().getResource(packageName.replaceAll("\\.","/"));
        File dir = new File(url.getFile());

        for (File file : dir.listFiles()) {

            //如果是文件夹,继续递归
            if(file.isDirectory()){
                scannerClass(packageName + "." + file.getName());
            }else {
                classNames.add(packageName + "." + file.getName().replace(".class","").trim());
            }
        }
    }

    /**本方法用于读取客户端发送的信息*/
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Object result = new Object();
        InvokerProtocol request = (InvokerProtocol) msg;

        if(registryMap.containsKey(request.getClassName())){
            //用反射直接调用Provider的方法
            Object provider = registryMap.get(request.getClassName());
            Method method = provider.getClass().getMethod(request.getMethodName(),request.getParams());
            result = method.invoke(provider,request.getValues());
        }
        ctx.write(result);
        ctx.flush();
        ctx.close();
    }

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

2.4 Provider代码实现

提供者接口:

public interface IRpcHelloService {
    String hello(String name);
}

 提供者实现:

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

2.5 Consumer消费端代码实现

 2.5.1 RpcProxy.class(代理类——Netty 客户端启动类)

public class RpcProxy {
    public static <T> T create(Class<?> clazz){

        MethodProxy proxy = new MethodProxy(clazz);
        Class<?> [] interfaces = clazz.isInterface() ?
                                 new Class[]{clazz} :
                                 clazz.getInterfaces();
        T result = (T)Proxy.newProxyInstance(clazz.getClassLoader(),interfaces,proxy);
        return result;
    }

    public static class MethodProxy implements InvocationHandler {
        private Class<?> clazz;
        public MethodProxy(Class<?> clazz) {
            this.clazz = clazz;
        }

        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if(Object.class.equals(method.getDeclaringClass())){
                return method.invoke(this,args);
            }else{
                return rpcInvoke(proxy,method,args);
            }
        }

        private Object rpcInvoke(Object proxy, Method method, Object[] args) {

            //封装请求的内容
            InvokerProtocol msg = new InvokerProtocol();
            msg.setClassName(this.clazz.getName());
            msg.setMethodName(method.getName());
            msg.setParams(method.getParameterTypes());
            msg.setValues(args);


            final RpcProxyHandler consumerHandler = new RpcProxyHandler();

            // 客户端启动类程序
            EventLoopGroup group = new NioEventLoopGroup();

            try {
                Bootstrap client = new Bootstrap();
                //EventLoop的组
                client.group(group)
                        //用于构造socketchannel工厂
                        .channel(NioSocketChannel.class)
                        //自定义客户端Handle(客户端在这里搞事情)
                        .handler(new ChannelInitializer() {
                            @Override
                            protected void initChannel(Channel ch) throws Exception {
                                //接收课客户端请求的处理流程
                                ChannelPipeline pipeline = ch.pipeline();

                                int fieldLength = 4;
                                //通用解码器设置
                                pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,fieldLength,0,fieldLength));
                                //通用编码器
                                pipeline.addLast(new LengthFieldPrepender(fieldLength));
                                //对象编码器
                                pipeline.addLast("encoder",new ObjectEncoder());
                                //对象解码器
                                pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));

                                pipeline.addLast("handler",consumerHandler);
                            }
                        })
                        /**设置选项
                         * 参数:Socket的标准参数(key,value),可自行百度保持呼吸,不要断气!
                         * */
                        .option(ChannelOption.TCP_NODELAY, true);
                /** 开启客户端监听,连接到远程节点,阻塞等待直到连接完成*/
                ChannelFuture future = client.connect("localhost",8080).sync();
                future.channel().writeAndFlush(msg).sync();
                /**阻塞等待数据,直到channel关闭(客户端关闭)*/
                future.channel().closeFuture().sync();
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                group.shutdownGracefully();
            }

            return consumerHandler.getResponse();
        }
    }
}

2.5.2 消费这代码 

public class RpcConsumer {
    public static void main(String[] args) {
        IRpcHelloService rpcHello = RpcProxy.create(IRpcHelloService.class);
        System.out.println(rpcHello.hello("Tom"));
    }
}

2.6 启动效果

启动注册中心:

启动客户端:

此结果我们这次基于Netty手写RPC框架成功!

本章参考博客:Netty学习之Demo搭建

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值