RPC,全称为Remote Procedure Call(远程过程调用)。通俗一点讲就是在本地调用远程服务器上的功能。实现远程调用至少需要满足以下几个条件:

1.网络通信

2.序列化与反序列化

3.反射

远程通信是远程调用的前题,只有经过序列化后的数据才能在网络上传输,传输到服务器端后需要反序列化成对象,然后通过反射机制调用服务器上客户端指定的服务,再将结果返回给客户端,用一张图表示:

服务器每收到一次客户端的请求,就启动一个线程处理,调用具体的服务对象相应的方法,并将返回结果返回给客户端。

基于此原理,下面写一个示例实现RPC功能:

项目结构图如下:分别有客户端、服务器、服务以及请求与响应。

首先抽象出请求类与响应类:

请求类:属性有请求的类名、方法名、输入参数类型、输入数据。

package cn.yang.common;

import java.io.Serializable;

//请求
public class Request implements Serializable {
private String className;//类名
private String methodName;//方法名 private Class[] inType;//输入参数类型
private Object[] inData;//输入数据 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[] getInType() {
return inType;
} public void setInType(Class[] inType) {
this.inType = inType;
} public Object[] getInData() {
return inData;
} public void setInData(Object[] inData) {
this.inData = inData;
}
}

响应类:属性有返回对象

package cn.yang.common;

import java.io.Serializable;

//响应
public class Response implements Serializable {
private Object obj;//返回对象 public Object getObj() {
return obj;
} public void setObj(Object obj) {
this.obj = obj;
}
}

服务器类以及服务处理类:

服务器类负责接收客户端的请求,并将请求分配给服务处理类进行处理。

服务器类:

package cn.yang.server;

import cn.yang.server.handler.ServerHandler;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket; //服务器类,负责接收任务,分配任务
public class Server {
public static void main(String args[]){
try {
ServerSocket socket = new ServerSocket();
socket.bind(new InetSocketAddress(1234));
BufferedReader in;
Socket accept;
while (true){
accept = socket.accept();
System.out.println("accept a request from"+accept.getRemoteSocketAddress());
new Thread(new ServerHandler(accept)).start();
}
}catch(IOException e){
e.printStackTrace();
} }
}

服务处理类:

package cn.yang.server.handler;

import cn.yang.common.Request;
import cn.yang.common.Response; import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket; //服务处理类
public class ServerHandler implements Runnable {
private Socket socket;
private ObjectInputStream in;
private ObjectOutputStream out; public ServerHandler(Socket socket){
this.socket=socket;
} @Override
@SuppressWarnings("unchecked")
public void run() {
try {
in = new ObjectInputStream(socket.getInputStream());
Request request = (Request)in.readObject(); //类名
String className=request.getClassName();
//方法名
String methodNmae=request.getMethodName();
//参数类型
Class[] inType=request.getInType();
//参数值
Object[] inDate=request.getInData(); //根据类名导入类的字节码
Class cls=Class.forName(className); //根据方法名得到方法
Method method = cls.getMethod(methodNmae, inType); //创建类对象
Object obj = cls.newInstance(); //执行方法得到结果
Object result = method.invoke(obj, inDate); //得到socket输出流
out=new ObjectOutputStream(socket.getOutputStream()); //构造Response对象并输出
Response response=new Response();
response.setObj(result);
out.writeObject(response);
} catch (InstantiationException | InvocationTargetException
| IllegalAccessException | NoSuchMethodException |
ClassNotFoundException | IOException e) {
e.printStackTrace();
}finally {
if(in!=null){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
in=null;
}
} if(out!=null){
try {
out.close();
} catch (IOException e2) {
e2.printStackTrace();
}finally {
out=null;
}
} if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}finally {
socket=null;
}
}
}
}
}

服务类:

package cn.yang.service;

import java.util.Arrays;
import java.util.List; //服务类
public class Hello{
//sayHello服务方法
public String sayHello(String name){
return "hello:"+name;
} //list服务方法
public List<String> list(){
List<String> list= Arrays.asList("1","2","3");
return list;
}
}

最后是客户端类:

package cn.yang.client;

import cn.yang.common.Request;
import cn.yang.common.Response; import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetSocketAddress;
import java.net.Socket; //客户端
public class client {
public static void main(String args[]){
Socket socket = new Socket();
ObjectOutputStream objectOutputStream=null;
ObjectInputStream objectInputStream=null;
try {
socket.connect(new InetSocketAddress(1234)); //封装请求类
Request request=new Request();
request.setClassName("cn.yang.service.Hello");//全类名
request.setMethodName("sayHello");//方法
request.setInType(new Class[]{String.class});//参数类型
request.setInData(new Object[]{"LiMing"});//参数值 /*Request request=new Request();
request.setClassName("cn.yang.service.Hello");
request.setMethodName("list");
request.setInType(new Class[]{});
request.setInData(new Object[]{});*/ //打开socket输出流
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//写入Request对象
objectOutputStream.writeObject(request); //打开socket输入流
objectInputStream = new ObjectInputStream(socket.getInputStream()); //读取Response对象
Response response=(Response)objectInputStream.readObject();
Object obj = response.getObj();
System.out.println("result:"+obj); } catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(objectOutputStream!=null){
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if(objectInputStream!=null){
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
} if(socket!=null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} }
}

运行Server,然后再运行Client,得到结果:

总结:RPC实际上是在网络通信基础之上,运用反射技术达到远程功能调用的目的。Request和Response类必须是可序列化类,以便在网络上传输,并且客户端和服务器都必须有这两个类。这个示例只是以最简单的方式说明RPC本质是什么。

现在有许多RPC框架,如Thrift,Hessian,JsonRPC,Dubbo,rPcx,gRPC等,他们就是在此基础之上进行再封装,优化,使用户更简便的使用,达到调用远程功能,就如同调用本地功能一样方便。

上面所说的网络通信、序列化与反序例化以及反射的实现各个框架都有所不同。

网络通信有的选择TCP,有的选择HTTP。TCP 是传输层协议,HTTP 是应用层协议,而传输层较应用层更加底层,在数据传输方面,越底层越快,因此,在一般情况下,TCP 一定比 HTTP 快。

就序列化而言,Java 提供了默认的序列化方式,但在高并发的情况下,这种方式将会带来一些性能上的瓶颈,于是市面上出现了一系列优秀的序列化框架,比如:Protobuf、Kryo、Hessian、Jackson 等,它们可以取代 Java 默认的序列化,从而提供更高效的性能。

反射技术有Java自带的反射技术,Objenesis以及CGLIB。

实际应用中,服务不会直接是一个具体的类,而是一个接口,也称为协议,即客户端和服务端达成的一种共识。客户端请求时,传输接口名字,服务端通过接口名字找到接口的实现类,然后创建对象,并执行方法返回结果或者客户端通过服务注册中心找到服务实现类名,传送给服务器,这涉及到服务注册与发现,不同的框架实现的方式也有所不同,甚至一些框架还有流量监控与控制,服务故障转换,负载均衡等。

这一篇文章只是执砖引玉,希望大家在了解RPC基础原理之后,走上更高的台阶!

参考:

轻量级分布式 RPC 框架

阿里Dubbo疯狂更新,关Spring Cloud什么事

剥掉层层外衣后的RPC是什么样子的?的更多相关文章

  1. nginx获取经过层层代理后的客户端真实IP(使用正则匹配)

    今天帮兄弟项目搞了一个获取客户端真实IP的问题,网上这种问题很多,但是对于我们的场景都不太合用,现把我的解决方案share给大家,如有问题,请及时指出. 场景: 在请求到达后端服务之前,会经过层层代理 ...

  2. 「坐上时光机,查找编译压缩后的文件最初的样子」gulp-sourcemaps 使用说明

    一般我们调试的 js/css 文件都是编译压缩后的,一旦出错很难定位原始的位置,gulp-sourcemaps 的出现帮助我们解决了这个问题. 首先我们看下目录结构: css js a.js b.js ...

  3. PPTP协议握手流程分析

    一  PPTP概述 PPTP(Point to Point Tunneling Protocol),即点对点隧道协议.该协议是在PPP协议的基础上开发的一种新的增强型安全协议,支持多协议虚拟专用网,可 ...

  4. C++系列总结——构造与析构

    前言 在使用资源前,我们需要做一些准备工作保证资源能正常使用,在使用完资源后,我们需要做一些扫尾工作保证资源没有泄露,这就是构造与析构了,这和编程语言是无关的,而是使用资源的一种方式.C++只不过是把 ...

  5. PPTP协议握手流程分析--转载

    一  PPTP概述   PPTP(Point to Point Tunneling Protocol),即点对点隧道协议.该协议是在PPP协议的基础上开发的一种新的增强型安全协议,支持多协议虚拟专用网 ...

  6. 大括号之谜:C++的列表初始化语法解析

    有朋友在使用std::array时发现一个奇怪的问题:当元素类型是复合类型时,编译通不过. struct S { int x; int y; }; int main() { int a1[3]{1, ...

  7. CentOS7设置SVN自启动,提交报错,无权限.手动kill掉后重启,成功.

    参考文档:http://tieba.baidu.com/p/5174054662 最近想尝试在CentOS7上搭建SVN服务.遇到的问题大致如题,我这边再详细描述一下. 虚拟机:VMware® Wor ...

  8. TCP、UDP数据包大小的限制(UDP数据包一次发送多大为好)——数据帧的物理特性决定的,每层都有一个自己的数据头,层层递减

    1.概述 首先要看TCP/IP协议,涉及到四层:链路层,网络层,传输层,应用层. 其中以太网(Ethernet)的数据帧在链路层 IP包在网络层 TCP或UDP包在传输层 TCP或UDP中的数据(Da ...

  9. ZBUS = MQ + RPC

    http://git.oschina.net/rushmore/zbus http://my.oschina.net/sbz/blog  Readme.md 18.02 KB ZBUS = MQ + ...

随机推荐

  1. python全栈学习--day10(函数进阶)

    一,引言 现在我有个问题,函数里面的变量,在函数外面能直接引用么? def func1(): m = 1 print(m) print(m) #这行报的错 报错了:NameError: name 'm ...

  2. 福州大学W班-助教总结

    开学初对自己的期望 在即将到来的学期前,我希望我可以做到以下几点: 1.多参与同学的课程设计,并提出自己的见解 2.不断提高个人的专业技能,活到老学到老 3.能够及时对同学的博客进行评论,并给出有用的 ...

  3. C/C++生成随机数

    一.rand和srand   在C++11标准出来之前,C/C++都依赖于stdlib.h头文件的rand或者srand来生成随机数.   其不是真正的随机数,是一个伪随机数,是根据一个数(我们可以称 ...

  4. 简单的C语言编译器--语法分析器

      语法分析算是最难的一部分了.总而言之,语法分析就是先设计一系列语法,然后再用设计好的语法去归约词法分析中的结果.最后将归约过程打印出来,或者生成抽象语法树. 1. 设计文法 以下是我的文法(引入的 ...

  5. JAVA中最容易让人忽视的基础。

    可能很多找编程工作的人在面试的时候都有这种感受,去到一个公司填写面试试题的时候,多数人往往死在比较基础的知识点上.不要奇怪,事实就是如此一般来说,大多数公司给出的基础题大概有122道,代码题19道左右 ...

  6. 配置SpringAop时需要用到的AspectJ表达式

    Aspectj切入点语法定义 在使用spring框架配置AOP的时候,不管是通过XML配置文件还是注解的方式都需要定义pointcut"切入点" 例如定义切入点表达式  execu ...

  7. node创建第一个应用

    如果我们使用PHP来编写后端的代码时,需要Apache 或者 Nginx 的HTTP 服务器,并配上 mod_php5 模块和php-cgi. 从这个角度看,整个"接收 HTTP 请求并提供 ...

  8. RE:1054652545 - 论自己是如何蠢死的

    1.Java web 项目中 login/list 文件夹中return "login/list" 反复读取不到对应的jsp文件 一周后检查出来的原因上一级文件夹login前面多出 ...

  9. vue jquery js 获取当前时间本周的第一天 和 本月的第一天

    交互的时候传输数据 后台要求这样的数据 直接上代码 这是我找度姨要的  附上链接  https://www.cnblogs.com/wasabii/p/7756560.html 它里面有本季度第一天  ...

  10. Docker_部署jenkins(dockerfile实现)

    docker+jenkins开始合体! 我用的是ubuntu14.04的基础镜像,具体的这里不做赘述. 我在/tmp/目录下建了一个Dockerfile文件: touch Dockerfile vi ...