30分钟手写一个简单的RPC

 

RPC全名为Remote Procedure Call,顾名思义为远程调用,既然是远程调用,那就再熟悉不过了,IO、socket这些是逃不掉的。

国内非常流行的rpc框架无疑是阿里巴巴开源框架—dubbo。

dubbo底层就是利用netty进行网络IO实现通信。

那么如何快速启动一个netty服务呢。

我觉得,大家都是伸手党,直接copy代码跑起来是最爽的,那么就来吧。

public class RPCServer {
    private int port;

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

    public void start() {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
                    .localAddress(port).childHandler(new ChannelInitializer<SocketChannel>() {

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            // 忽略前4个字节的解码器(目的是得到不带长度的数据域)
                            pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                            // 4个字节表示数据长度的编码器
                            pipeline.addLast(new LengthFieldPrepender(4));
                            // netty自带object编码与解码器(所以这是为什么我们dubbo接口的参数要序列化)
                            pipeline.addLast("encoder", new ObjectEncoder());
                            pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                            // 自定义handler
                            pipeline.addLast(new InvokerHandler());
                        }
                    }).option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true);
            ChannelFuture future = serverBootstrap.bind(port).sync();
            System.out.println("Server start listen at " + port);
            future.channel().closeFuture().sync();
        } catch (Exception e) {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        int port = 8080;
        new RPCServer(port).start();
    }

}

 

这是一个很常见的启动netty服务端的代码(百度上一大片哦),主要定义了一些RPC调用需要用到的解码编码器。

其中自定义的handler代码。

public class InvokerHandler extends ChannelInboundHandlerAdapter {

    public static ConcurrentHashMap<String, Object> classMap = new ConcurrentHashMap<String, Object>();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        Message message = (Message) msg;
        Object clazz = null;
        if (!classMap.containsKey(message.getClassName())) {
            try {
                clazz = Class.forName(message.getClassName()).newInstance();
                classMap.put(message.getClassName(), clazz);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            clazz = classMap.get(message.getClassName());
        }
        Method method = clazz.getClass().getMethod(message.getMethodName(), message.getTypes());
        Object result = method.invoke(clazz, message.getObjects());
        ctx.write(result);
        ctx.flush();
        ctx.close();
    }

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

}

 

message。

public class Message implements Serializable {
    private static final long serialVersionUID = -8970942815543515064L;

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

    public String getClassName() {
        return className;
    }
    public void setClassName(String className) {
        this.className = className;
    }
    public String getMethodName() {
        return methodName;
    }
    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }
    public Class<?>[] getTypes() {
        return types;
    }
    public void setTypes(Class<?>[] types) {
        this.types = types;
    }
    public Object[] getObjects() {
        return objects;
    }
    public void setObjects(Object[] objects) {
        this.objects = objects;
    }

    @Override
    public String toString() {
        return "Message{" +
                "className='" + className + '\'' +
                ", methodName='" + methodName + '\'' +
                ", types=" + Arrays.toString(types) +
                ", objects=" + Arrays.toString(objects) +
                '}';
    }
}

 

通过以上代码可以暂时先分析出基本逻辑:

  • 客户端肯定发送的是message类
  • message类里携带的信息是类名、方法名、参数类型、参数

那么基本可以猜测出大致逻辑了。

客户端发送一个message告诉服务端,我要调用你的什么类的什么方法,我的参数类型是什么,我的参数值是什么,你(服务端)通过反射然后执行,执行完了给我(客户端)一个response即可!

----------------------------------------------------------------------------------------------------------------

接下来就是客户端如何编写了。

我们先想一下之前使用dubbo的场景,先通过配置文件注册reference的bean,然后通过bean调用方法。这么一看好像并没有发送任何IO的请求去访问服务端。

其实这里是用到了动态代理,看似执行了此方法,但此方法非彼方法。

我们来看客户端的代码。

public class RPCProxy {

    @SuppressWarnings("unchecked")
    public static <T> T create(final Object target) {
        // 生成代理对象
        return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 组装message
                Message message = new Message();
                message.setClassName(target.getClass().getName());
                message.setMethodName(method.getName());
                message.setObjects(args);
                message.setTypes(method.getParameterTypes());

                final ResultHandler resultHandler = new ResultHandler();
                EventLoopGroup group = new NioEventLoopGroup();
                try {
                    Bootstrap b = new Bootstrap();
                    b.group(group)
                            .channel(NioSocketChannel.class)
                            .option(ChannelOption.TCP_NODELAY, true)
                            .handler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                public void initChannel(SocketChannel ch) throws Exception {
                                    ChannelPipeline pipeline = ch.pipeline();
                                    pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
                                    pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
                                    pipeline.addLast("encoder", new ObjectEncoder());
                                    pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
                                    // 注册自定义handler
                                    pipeline.addLast("handler", resultHandler);
                                }
                            });
                    // 客户端通过IP+端口号去访问netty服务端
                    ChannelFuture future = b.connect("localhost", 8080).sync();
                    // 向服务器发送message
                    future.channel().writeAndFlush(message).sync();
                    future.channel().closeFuture().sync();
                } finally {
                    group.shutdownGracefully();
                }
                // 从handler中拿到服务端的返回值
                return resultHandler.getResponse();
            }
        });
    }

}

客户端handler。

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;
        System.out.println("client接收到服务器返回的消息:" + msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("client exception is general");
    }
}

 

于是乎我们带着测试一下心情写出了类似hello-world的代码。

定义一个接口。

public interface Hello {
    String print(String name);
}

实现类。

public class HelloImpl implements Hello {
    public String print(String name) {
        return "hello " + name;
    }
}

 

测试类。

public class Test {
    public static void main(String [] args){
        Hello hello2 = RPCProxy.create(new HelloImpl());
        System.out.println(hello2.print("world!"));
    }
}

跑之前记得先把netty服务端的server启动了。

结果令人很满意。

 

此项目的git地址:

https://github.com/feiyangmemeda/rpc.git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值