RPC介绍与代码实现
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RPC介绍与代码实现相关的知识,希望对你有一定的参考价值。
RPC介绍与代码实现
RPC的英文资料网站:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html
什么是RPC
RPC:
Remote Procedure Call Protocol——远程过程调用协议
是一种通过网络从远程计算机程序上请求服务的协议。
它可以使我们通过参数传递的方式就像调用本地的方法一样调用远程服务器上的函数或方法。
为什么要用RPC
RPC解决了不在同一个内存中的进程之间通信的问题。
在项目开发当中,大多时候程序都是分布在多个服务器节点组成的集群上当中。
比如服务器A的下订单应用程序想要调用服务器B上的支付宝付款应用程序的时候,由于不在同一个内存空间当中,自然无法调用。
通俗点地说,就像是你在你的电脑上写了一个程序A实现了某个功能,我在我的电脑上写了一个B程序实现了某个功能。正常情况下我是不能在我的程序中去调用你写的程序的。
如何才能做到呢?自然是通过网络。RPC就是提供了这样的实现。
RPC原理
(图片来源:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html)
client functions : 客户函数。
client stub : 客户句柄。对于客户端来说,客户端想要调用远程服务时是直接找client stub去调用的。实际与远程服务打交道的是client stub。client stub作用就是接收客户端的请求参数(需要序列化),将请求参数发送到远程服务端(server stub)进行处理,得到处理后的结果(反序列化)再返回给客户端。
sockets : 客户端与远程端的通信就是通过底层的网络协议进行通信的(建立TCP连接)
server stub:服务器句柄,也叫做skeleton。skeleton的作用是接收客户端的请求参数,然后调用服务端实际处理程序(server functions)处理并获取结果。
RPC调用过程:
- client端想要调用某个远程服务的时候,以本地调用方式调用服务,需要调用client stub,传送参数到client stub;
- client stub 接收参数方法信息并且将其组装成能够进行网络传输的消息体(需序列化);
- 通过建立TCP的连接,client stub找到服务地址,将消息发送到服务端;
- server stub 收到网络消息并取得客户端的请求参数(反序列化);
- server stub 调用服务端的程序,将获取到的请求参数传送到真正的远程端的处理函数server functions进行处理;
- server functions 执行程序后将结果返回给server stub;
- server stub 将结果写入socket流中(序列化)发送至客户端;
- 结果信息传回本地主机;
- client stub 接收到结果消息(反序列化);
- client stub将结果消息传送到本地程序。
RPC要解决的问题
1. 通讯问题。
客户端和服务器之间是通过网络协议传输的,建立TCP连接。远程过程调用的所有交换的 数据都通过这个TCP连接传输。
连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。2. 寻址问题
A服务器上的应用应当告诉底层RPC框架如何连接到B服务器特定的端口和方法名称,这样才能完成调用。3. 序列化和反序列化问题
远程过程调用传输的数据是通过网络协议进行传输的,所以这些数据需要进行序列化与反序列化处理。
RPC实现所需要考虑的几个问题
1. 怎么做到透明化远程服务调用?
怎么封装通信细节才能让用户像以本地调用方式调用远程服务呢?对java来说就是使用代理!java代理有两种方式:1) jdk 动态代理;2)字节码生成。
尽管字节码生成方式实现的代理更为强大和高效,但代码不易维护,大部分公司实现RPC框架时还是选择动态代理方式。
2. 确定消息数据结构
1)接口名称
2)方法名
3)参数类型&参数值
4)超时时间
5)requestID,标识唯一请求id
3. 序列化
什么是序列化?
序列化就是将数据结构或对象转换成二进制串的过程,也就是编码的过程。
什么是反序列化?
将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程。
为什么需要序列化?转换为二进制串后才好进行网络传输嘛!为什么需要反序列化?将二进制转换为对象才好进行后续处理!
4. 通信
Netty。
Java代码实现一个简单的RPC框架
服务端:
服务端是暴露服务。
在对Socket操作时,先读后写。
读:先从Socket连接中获取客户端的所要调用的远程服务的方法及请求参数等信息;
写:服务端的Server Stub调用服务端的真正的远程服务处理函数处理后获取结果,再将结果写回Socket。
核心代码:
/**
* 暴露服务
* @param service 服务实现
* @param port 服务端口
* @throws Exception
*/
public static void export(final Object service, int port) throws Exception
ServerSocket ss = new ServerSocket(port);
Socket socket = ss.accept();
/** socket中读取参数等信息并反序列化封装成对象 **/
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
//读取一个String类型的值 : method即为String类型的
String methodName = in.readUTF();
//获取方法上定义的泛型参数
Class<?>[] parameterTypes = (Class<?>[])in.readObject();
//获取参数
Object[] arguments = (Object[])in.readObject();
//调用接口中所需要的方法
Method method = service.getClass().getMethod(methodName, parameterTypes);
//处理结果
Object result = method.invoke(service, arguments);
/** 将结果序列化写回socket **/
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject(result);
客户端:
客户端是引用服务。
在对Socket操作时是先写后读。
写:将服务端想要调用的远程服务的方法名,参数等信息写入Socket;
读: 从Socket中读取服务端的处理结果。
核心代码:
/**
* 引用服务
* @param service
* @param port
* @throws Exception
*/
@SuppressWarnings("unchecked")
public static <T> T refer(final Class<T> interfaceClass, final String host, int port) throws Exception
return (T)Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[]interfaceClass,
new InvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
Socket socket = new Socket(host, port);
/** 先将客户端想要调用的远程服务信息及参数写入socket **/
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
//方法名
out.writeUTF(method.getName());
//方法上的泛型参数
out.writeObject(method.getParameterTypes());
//调用的参数
out.writeObject(args);
/** 获取调用的远程服务处理的结果 **/
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
Object result = in.readObject();
return null;
);
最原始的RPC功能的实现:
package com.wgs.rpc.framework;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.ServerSocket;
import java.net.Socket;
/**
*
* @author GenshenWang.nomico
*
*/
public class RPCFramework
/**
* 暴露服务
* @param service
* @param port
* @throws Exception
*/
public static void export(final Object service, int port) throws Exception
if (service == null)
throw new IllegalArgumentException(" service instance == null");
if (port <= 0 || port > 65535)
throw new IllegalArgumentException("Invalid port " + port);
System.out.println("Export service: " + service.getClass().getName() + "on port :" + port);
//监听端口
ServerSocket server = new ServerSocket(port);
for(;;)
try
final Socket socket = server.accept();
new Thread(new Runnable()
@Override
public void run()
try
/** socket中读取参数等信息并反序列化封装成对象 **/
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
try
//获取方法名
String methodName = in.readUTF();
//获取方法上泛型
Class<?>[] parameterTypes = (Class<?>[])in.readObject();
//获取的参数
Object[] arguments = (Object[])in.readObject();
/** 将处理结果序列化后输出到socket **/
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
try
//获取要调用的方法
Method method = service.getClass().getMethod(methodName, parameterTypes);
//将参数传入,进行处理
Object result = method.invoke(service, arguments);
//将结果序列化输出
out.writeObject(result);
catch(Throwable t)
out.writeObject(t);
finally
out.close();
catch(Exception e)
e.printStackTrace();
finally
in.close();
catch(Exception e)
e.printStackTrace();
finally
try
socket.close();
catch (IOException e)
e.printStackTrace();
).start();
catch(Exception e)
e.printStackTrace();
/**
* 引用远程服务
* @param interfaceClass 调用的接口
* @param host IP
* @param port 端口
* @return
*/
@SuppressWarnings("unchecked")
public static <T> T refer(final Class<T> interfaceClass, final String host, final int port)
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class<?>[]interfaceClass,
new InvocationHandler()
@Override
public Object invoke(Object proxy, Method method, Object[] argumens) throws Throwable
Socket socket = new Socket(host, port);
try
/** 写:先将要调用的远程服务,参数等信息序列化输出到socket **/
ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
try
//将方法名写入socket
out.writeUTF(method.getName());
//将泛型类型写入socket
out.writeObject(method.getParameterTypes());
//将参数写入socket
out.writeObject(argumens);
/** 读:从socket流中读取调用的远程服务的结果 **/
ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
try
Object result = in.readObject();
if (result instanceof Throwable)
throw (Throwable) result;
return result;
finally
in.close();
finally
out.close();
finally
socket.close();
);
测试
- 定义服务接口
package com.wgs.rpc.test;
public interface HelloService
String hello(String name);
- 接口的真正实现,这是在服务端的实现,是隐藏的,不对外开放。
package com.wgs.rpc.test;
public class HelloServiceImpl implements HelloService
@Override
public String hello(String name)
return "name : " + name;
- 服务端暴露服务:
package com.wgs.rpc.test;
import com.wgs.rpc.framework.RPCFramework;
public class RPCProvider
public static void main(String[] args) throws Exception
HelloService service = new HelloServiceImpl();
RPCFramework.export(service, 8989);
- 客户端引用服务:
package com.wgs.rpc.test;
import com.wgs.rpc.framework.RPCFramework;
public class RPCProvider
public static void main(String[] args) throws Exception
HelloService service = new HelloServiceImpl();
RPCFramework.export(service, 8989);
最后先运行服务端再运行客户端,即可打印出”name : Hello First RPC!”.
参考资料:
http://javatar.iteye.com/blog/1123915
以上是关于RPC介绍与代码实现的主要内容,如果未能解决你的问题,请参考以下文章