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

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

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

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

延伸:分布式环境下,Client如何打到Server的服务?---因为,在服务器中运行的某些服务不像标准服务有着固定的端口,如HTTP的80端口。

一种解决方法是:在运行服务的每台机器上都运行一个特殊的守护进程,该守护进程负责跟踪位于该机器中每一项服务所使用的端口;此外,守护进程还监听一个特定的已经端口,Client通过这个端口与守护进程联系,请求得到指定服务的端口。

复杂的RPC实现框架中,比如可以把服务注册到ZooKeeper中,Client也从ZooKeeper中查询服务。参考:一个更复杂的RPC框架实现

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行代码发起连接。

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

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

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

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

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

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

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

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

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

八,总结:

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

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

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

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

整个源码下载

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

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

  1. 徒手撸一个简单的RPC框架

    来源:https://juejin.im/post/5c4481a4f265da613438aec3 之前在牛逼哄哄的 RPC 框架,底层到底什么原理得知了RPC(远程过程调用)简单来说就是调用远程的 ...

  2. 动手实现一个简单的 rpc 框架到入门 grpc (上)

    rpc 全称 Remote Procedure Call 远程过程调用,即调用远程方法.我们调用当前进程中的方法时很简单,但是想要调用不同进程,甚至不同主机.不同语言中的方法时就需要借助 rpc 来实 ...

  3. 动手实现一个简单的 rpc 框架到入门 grpc (下)

    之前手动实现了一次简陋的 rpc 调用,为了简单使用了 json 编码信息,其实这是非常不可靠的,go 中 json 解析会有一些问题,比如整数会变成浮点数,而且 json 字符串比较占空间. gRP ...

  4. 【RPC】手撸一个简单的RPC框架实现

      涉及技术   序列化.Socket通信.Java动态代理技术,反射机制   角色   1.服务提供者:运行在服务端,是真实的服务实现类   2.服务发布监听者:运行在RPC服务端,1将服务端提供的 ...

  5. 一个简单的RPC框架

    一个 系统模型 二.数据库代码实现 1. mkdir database cd database vim dbInit.c /* * * Database Init tool * */ #include ...

  6. 一个简单的RPC框架实现

    package com.rpc; public interface EchoService { String echo(String ping); } package com.rpc; public ...

  7. 自己用 Netty 实现一个简单的 RPC

    目录: 需求 设计 实现 创建 maven 项目,导入 Netty 4.1.16. 项目目录结构 设计接口 提供者相关实现 消费者相关实现 测试结果 总结 源码地址:github 地址 前言 众所周知 ...

  8. 教你用 Netty 实现一个简单的 RPC!

    众所周知,dubbo 底层使用了 Netty 作为网络通讯框架,而 Netty 的高性能我们之前也分析过源码,对他也算还是比较了解了. 今天我们就自己用 Netty 实现一个简单的 RPC 框架. 1 ...

  9. 最简单的RPC框架实现

    通过java原生的序列化,Socket通信,动态代理和反射机制,实现一个简单的RPC框架,由三部分组成: 1.服务提供者,运行再服务端,负责提供服务接口定义和服务实现类 2.服务发布者,运行再RPC服 ...

随机推荐

  1. Null Object Design Pattern (Python recipe)

    Null Object 个人感觉非常有用.也是在review公司其他同事写代码的时候看到. 当时使用了flask的request全局请求变量g,然后使用了g.x保存了一个东西. 当时在view代码读取 ...

  2. 自定义控件DataPager

    在最近的一个项目中,涉及到一个自定义翻页控制的控件,下面就这一个控件做详细的说明,这个自定义控件的主要作用是对数据源进行翻页控制,比如说:“上一页.下一页.首页.末页”等相关操作,由于在一个项目中有多 ...

  3. CF-Contest339-614

    614A-Link/Cut Tree 比较水,注意64位int仍然可能溢出. #include <cstdio> #include <algorithm> #include & ...

  4. EF code first 迁移问题

    错误 : 支持"Entities"上下文的模型已在数据库创建后发生更改.请考虑使用 Code First 迁移更新数据库(http://go.microsoft.com/fwlin ...

  5. Maven整理

    第一章 Maven安装 1.1 下载Maven库 下载地址:http://maven.apache.org/download.cgi 1.2 解压下载的库,认识Maven库目录 备注: 解压文件尽量不 ...

  6. 读取Properties文件以及中文乱码问题

    在java类中常见的读取Properties文件方式,是使用Properties.load(inputStream);的方式但是常常出现中文乱码问题,这就很尴尬了 public synchronize ...

  7. FreeRTOS 中 systick 相关配置

    @2018-7-16 > systick 属性配置 在文件 <port.c> 中函数 void vPortSetupTimerInterrupt( void ) 中配置计数周期.时钟 ...

  8. CF1073E Segment Sum 解题报告

    CF1073E Segment Sum 题意翻译 给定\(K,L,R\),求\(L~R\)之间最多不包含超过\(K\)个数码的数的和. \(K\le 10,L,R\le 10^{18}\) 数位dp ...

  9. cf983E NN Country (倍增+dfs序+树状数组)

    首先可以求出从某点做$2^k$次车能到的最浅的点,这个只要dfs一下,把它的孩子能到的最浅的点更新过来就可以 然后倍增地往上跳,不能跳到lca的上面,记录坐车的次数ans 此时有三种情况(设最远能跳到 ...

  10. Java内存模型基础

    Java内存模型的基础 并发编程模型的两个关键问题 在并发编程种,需要处理两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体).通信是指线程之间以何种机制来交换信息.在 ...