一个简单的"RPC框架"代码分析

0,服务接口定义---Echo.java

/*
 * 定义了服务器提供的服务类型
 */
public interface Echo {
    public String echo(String string);
}

一,客户端代码分析--实现类:MainClient.java

客户端实现包括:获得一个代理对象,并使用该代理对象调用服务器的服务。获取代理对象时,需要指定被代理的类(相当于服务器端提供的服务名),Server IP,Port.

Echo echo = RPC.getProxy(Echo.class, "127.0.0.1", 20382);
System.out.println(echo.echo("hello,hello"));//使用代理对象调用服务器的服务.并将结果输出

二,服务器端分析--实现类:MainServer.java

服务器实现包括:创建一个服务器对象,将它能提供的服务注册,并启动进程监听客户端的连接

        Server server = new RPC.RPCServer();

        /*
         * server 启动后,需要注册server端能够提供的服务,这样client使用 服务的名字、
         * 服务器的IP、以及服务所运行的端口 来调用 server 的服务
         */
        server.register(Echo.class, RemoteEcho.class);//注册服务的名字
        server.register(AnOtherEchoService.class, AnOtherEchoServiceImpl.class);

        server.start();//启动server

三,服务器监听Client连接分析----实现类:Listener.java

当server.start()后,它要创建一个Listener对象,这是一个线程类,该线程用来监听Client连接。

public void start() {
            System.out.println("启动服务器");

            /*
             * server 启动时,需要Listener监听是否有client的请求连接
             * listener 是一个线程,由它来监听连接
             */
            listener = new Listener(this);
            this.isRuning = true;
            listener.start();//listener 是一个线程类,start()后会执行线程的run方法
        }

其实,监听连接就是JAVA ServerSocket类和Socket类提供的相关功能而已。

/*
 * accept()是一个阻塞方法,server_socket 一直等待client 是否有连接到来
 */
 Socket client = server_socket.accept();//建立一条TCP连接

四,动态代理对象 生成---RPC.java

客户端只需要编写生成代理对象,用代理对象去调用远程服务的代码即可。但是,底层的功能如:建立连接,序列化(本例中也没有考虑),跨语言调用(未考虑)...是由RPC框架完成的。

当MainClient 语句:RPC.getProxy(Echo.class, "127.0.0.1", 20382);执行时,会由

/*
         * @param Class[]{} 该参数声明了动态生成的代理对象实现了的接口,即 clazz 所代表的接口类型 .
         * 这表明了生成的代理对象它是一个它所实现了的接口类型的对象
         * 从而就可以用它来调用它所实现的接口中定义的方法
         *
         * @param handler 生成代理实例对象时需要传递一个handler参数
         * 这样当该 代理实例对象调用接口中定义的方法时,将会委托给InvocationHandler 接口中声明的invoke方法
         * 此时,InvocationHandler 的invoke 方法将会被自动调用
         */
        T t = (T) Proxy.newProxyInstance(RPC.class.getClassLoader(), new Class[] {clazz}, handler);
        return t;

返回该代理对象,然后就会委托第三个参数 handler 自动执行 invoke(),invoke将客户端调用的所有相关信息封装到Invocation 对象中(后面分析)。然后执行第16行代码发起连接。

 1 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2                 Invocation invo = new Invocation();
 3                 invo.setInterfaces(clazz);
 4
 5                 //利用反射机制将java.lang.reflect.Method 所代表的方法名,参数 封装到 Invocation invo对象中
 6                 invo.setMethod(new org.jy.rpc.protocal.Method(method.getName(),method.getParameterTypes()));
 7                 invo.setParams(args);
 8
 9                 /*
10                  * 当把需要调用的远程server端的方法名和参数封装到invo之后,Client 对象 就可以把 invo 作为参数 传递给服务器了.
11                  * 为什么需要这样做呢?InvocationHandler 的invoke方法是自动执行的,在该方法里面,它根据生成的代理对象 proxy (第一个参数)
12                  * 所实现的接口(由 Proxy.newProxyInstance()的第二个参数指定) 就可以知道这个接口中定义了哪些方法
13                  * InvocationHandler 的 invoke 方法的第二个参数Method method 就可以解析出接口中的方法名和参数了
14                  * 把它们封装进Invocation invo对象中,再将 invo 作为 client.invoke(invo)的参数 发送到服务器方
15                  */
16                 client.invoke(invo);//invoke 先调用init发起一个Socket连接,再将invo 发送至输出流中
17                 return invo.getResult();
18             }

五,“客户端存根”--Client.java

最重要的是它的 invoke方法(注意与InvocationHandler的invoke()区分)。它负责建立连接,打开输入、输出流,向服务器发送字节数据。

1     public void invoke(Invocation invo) throws UnknownHostException, IOException, ClassNotFoundException {
2         init();
3         System.out.println("写入数据");
4         oos.writeObject(invo);//将Client 需要调用的Server的 接口、方法、参数 封装起来 发给服务器
5         oos.flush();
6         ois = new ObjectInputStream(socket.getInputStream());//用来接收从 server 返回 回来的执行结果 的输入流
7         Invocation result = (Invocation) ois.readObject();
8         invo.setResult(result.getResult());//将结果 保存到 Invocation result对象中
9     }

六,“服务器存根“---实现类:RPCServer.java

上面提到,服务器通过Listener监听客户端连接,当建立客户端连接后,Socket client = server_socket.accept(); 不再阻塞,服务器调用它的call()方法完成客户端请求的功能。也即,客户端请求的结果实际上是在服务器执行生成的。返回的结果是在Client.java 的 invoke() 方法里被读取出来 。call()再一次用到了JAVA反射(第11行) 参考:JAVA动态代理

 1 public void call(Invocation invo) {
 2             System.out.println(invo.getClass().getName());
 3             Object obj = serviceEngine.get(invo.getInterfaces().getName());
 4             if(obj!=null) {
 5                 try {
 6                     Method m = obj.getClass().getMethod(invo.getMethod().getMethodName(), invo.getMethod().getParams());
 7                     /*
 8                      * 利用JAVA反射机制来执行java.lang.reflect.Method 所代表的方法
 9                      * @param result : 执行实际方法后 得到的 服务的执行结果
10                      */
11                     Object result = m.invoke(obj, invo.getParams());
12                     invo.setResult(result);//将服务的执行结果封装到invo对象中。在后面的代码中,将该对象写入到输出流中
13                 } catch (Throwable th) {
14                     th.printStackTrace();
15                 }
16             } else {
17                 throw new IllegalArgumentException("has no these class");
18             }
19         }

七,”RPC 编码、解码,协议的定义“---Invocation.java   Method.java

其实,这里并不是那种实用的开源RPC框架如Thrift中所指的编码、IDL……上面两个类只是RPC实现过程中辅助完成Java动态代理的实现,说白了就是封装客户端需要调用的方法,然后指定生成的代理对象需要实现的接口(服务).

八,总结:

先运行MainServer.java启动服务器,然后,再运行MainClient.java 启动一个客户端连接服务器就可以看到执行结果。

当需要添加新的服务时:按以下步骤即可:①定义服务接口及其实现类,如:AnOtherEchoService.java  ②:在MainServer.java中注册新添加的服务。

③:在MainClient.java中编写获得新服务的代理对象的代码,并用该代理对象调用新服务接口中声明的方法。

这样,在客户端就能够远程地调用服务器上的一个新服务了。

整个源码下载

参考文章:自定义的RPC的Java实现

时间: 2024-10-13 11:00:56

一个简单的"RPC框架"代码分析的相关文章

通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的

实验一:通过反汇编一个简单的C程序,分析汇编代码理解计算机是如何工作的 学号:20135114 姓名:王朝宪 注: 原创作品转载请注明出处   <Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 1 1)实验部分(以下命令为实验楼64位Linux虚拟机环境下适用,32位Linux环境可能会稍有不同) 使用 gcc –S –o main.s main.c -m32 命令编译成汇编代码,如下代码中的数字请自行修改以防与

自己实现的一个简单的EF框架(反射实现)

我实现了一个简单的EF框架,主要用于操纵数据库.实现了对数据库的基本操纵--CRUD 这是项目结构 这是一个 core 下的 DLL 写了一个数据库工厂,用于执行sql语句.调用sql语句工厂 写了一个sql语句工厂,用于生成sql语句.调用类型工厂 写了一个类型工厂,用于获取所需的类型,识别特性等. appsettings.json是配置文件 最后一个是使用说明 我实现过程的首先从底层开始. 首先写的是类型工厂 结构 BaseTypeHelper.cs 是基础的类型帮助类 TypeHelper

使用idea搭建一个简单的SSM框架:(3)配置spring+mybatis

在此之前请先查看: 使用idea搭建一个简单的SSM框架:(1)使用idea创建maven项目 使用idea搭建一个简单的SSM框架:(2)配置springMVC 1 配置spring和mybatis整合文件 spring和mybatis整合分为三个步骤:(1)配置数据库,(2)配置SqlSessionFactoryBean (来自mybatis-spring),(3)配置MapperScannerConfigurer,该配置就我们通过接口定义的方法,就是mybatis的xml对应的namesp

反汇编一个简单的C程序并分析

反汇编一个简单的C程序并分析 C 源码: int g(int x) { return x+1; } int f(int x) { return g(x); } int main(void) { return f(2) + 3; } 汇编源码: 1 g: 2 pushl %ebp 3 movl %esp, %ebp 4 movl 8(%ebp), %eax 5 addl $1, %eax 6 popl %ebp 7 ret 8 f: 9 pushl %ebp 10 movl %esp, %ebp

一个简单的web框架实现

一个简单的web框架实现 #!/usr/bin/env python # -- coding: utf-8 -- __author__ = 'EchoRep' from wsgiref.simple_server import make_server def index(): # data = open('html/index.html').read() return data def echo(): # data = open('html/echo.html').read() return d

java创建一个简单的小框架frame

import java.awt.*; import javax.swing.*; public class SimpleFrameTest { public static void main(String[] args) { EventQueue.invokeLater(new Runnable(){ // 开一个线程 public void run() { SimpleFrame frame = new SimpleFrame(); frame.setTitle("记事本"); //

初学者如何做一个简单的计算器,代码分享

先新建一个类 startCalculator 声明如下 #import <Foundation/Foundation.h> @interface StartCalculator : NSObject //声明两个要计算的变量 @property float opValue1; @property float opValue2; //声明一个运算符 @property char op; //普通方法 //- (float) gzyWorkAdd; // //- (float) gzyWorkSu

理解计算机的工作方式——通过汇编一个简单的C程序并分析汇编代码

Author: 翁超平 Notice:原创作品转载请注明出处 See also:<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000  本文通过汇编一个简单的C程序,并分析汇编代码,来理解计算机是如何工作的.整个过程都在实验楼上完成,感兴趣的读者可以通过上面给出的课程链接自行动手学习.以下是实验过程和结果. 一.操作步骤 1.首先在通过vim程序建立main.c文件.代码如下: 图1 2.使用如下命令将main.c编

《Linux内核分析》MOOC课程 反汇编一个简单的C程序,分析汇编代码

一个简单c程序 分析一个简单的c程序 main.c 如下图: 用命令 gcc –S –o main.s main.c -m32编译成汇编文件.在汇编文件中有许多的虚指令并不会形成机器指令,为了使分析简单我们把大部分去掉: 得到如下图所示: 栈的介绍 APUE中指出每一个c程序,都有一个独立的地址空间,在内存中的典型布局如下: 对栈的操作和我们在数据结构中的栈的操作是类似的,ebp,esp(具体名称与cpu体系结构相关) 这两个寄存器直接与栈的操作相关. 栈地址是从高到低的方向分配的. 开始一个新