使用Socket&反射&Java流操作进行方法的远程调用(模拟RPC远程调用)
写在前面
阅读本文首先得具备基本的Socket、反射、Java流操作的基本API使用知识;否则本文你可能看不懂。。。
服务端的端口监听
进行远程调用,那就必须得有客户端和服务端。服务端负责提供服务,客户端来对服务端进行方法调用。所以现在我们清楚了: 需要一个服务端、一个客户端
那么我们说干就干,我们先建立一个服务端:
- 通过Socket监听本地服务器的一个端口(8081)
- 调用socket的accept方法等待客户端的连接(accpet方法原理)
/**
* RPC服务端
* @author wushuaiping
* @date 2018/3/15 下午12:23
*/
public class ObjectServerSerializ {
public static void main(String[] args) {
try {
// 启动服务端,并监听8081端口
ServerSocket serverSocket = new ServerSocket(8081);
// 服务端启动后,等待客户端建立连接
Socket accept = serverSocket.accept();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端与服务端建立连接
我们服务端监听了端口后,那么我们需要使用客户端去访问目标服务端的这个端口,代码如下:
/**
* RPC客户端,这里发起调用请求。
* 模拟RPC框架调用过程
* @author wushuaiping
* @date 2018/3/15 下午12:22
*/
public class ObjectClientSerializ {
public static void main(String[] args) {
try {
// 使用Socket与指定IP的主机端口进行连接。
Socket socket = new Socket("localhost", 8081);
} catch (Exception e) {
e.printStackTrace();
}
}
}
业务方法
与服务端建立连接后,那我们进行下一步。因为我们要模拟RPC远程调用,那么我们的有一个业务方法:
业务方法接口
/**
* 业务方法接口
*/
public interface HelloService {
String sayHello(String str);
}
业务方法实现类
远程调用必须要实现序列化接口(Serializable)。
/**
*
* @author wushuaiping
*
*/
public class HelloServiceImpl implements Serializable, HelloService {
/**
*
*/
private static final long serialVersionUID = 203100359025257718L;
/**
*
*/
public String sayHello(String str) {
System.out.println("执行方法体,入参=" + str);
return str;
}
}
数据传输模型对象
我们有了服务方法后,首先想到的是,我们如果将序列化后的对象传输到服务端以后,服务端如何知道这是哪个对象?不可能使用Object来调用方法吧,所以我们需要一个能封装业务类方法信息的数据传输对象。那么该数据传输对象需要具备哪些信息?服务端调用肯定得用反射来调用方法,所以我们这个数据传输对象就得满足一下条件:
- 第一,反射调用时必须知道方法名 String methodName
- 第二,反射调用时必须知道方法参数类型 Object[] parameterTypes
- 第三,反射调用时必须知道参数 Object[] parameters
- 第四,反射调用时必须知道哪个对象在调用 Object invokeObject
满足以上条件后,就可以进行反射调用方法了,但是,我们通过服务端调用后,我们需要知道服务端返回的数据信息。那么该对象还需要一个参数:
- 第五,需要一个返回对象 Object result
通过上述分析,我们建立了该对象:
/**
* 数据传输模型
* @author wushuaiping
* @date 2018/3/15 下午12:25
*/
public class TransportModel implements Serializable{
/**
*
*/
private static final long serialVersionUID = -6338270997494457923L;
//返回结果
private Object result;
//对象
private Object object;
//方法名
private String methodName;
//参数
private Class<?>[] parameterTypes;
private Object[] parameters;
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setResult(Object result) {
this.result = result;
}
public Object getResult() {
return result;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getParameters() {
return parameters;
}
public void setParameters(Object[] parameters) {
this.parameters = parameters;
}
}
客户端设置相应调用信息
有了数据传输模型后,我们将需要的对象信息封装进数据传输模型,我们就可以真正的开始对服务端的服务进行调用了!
/**
* RPC客户端,这里发起调用请求。
* 模拟RPC框架调用过程
* @author wushuaiping
* @date 2018/3/15 下午12:22
*/
public class ObjectClientSerializ {
public static void main(String[] args) {
try {
// 使用Socket与指定IP的主机端口进行连接。
Socket socket = new Socket("localhost", 8081);
// 创建一个业务对象,模拟客户端发起调用。
HelloService helloService = new HelloServiceImpl();
// 该传输模型对象存储了客户端发起调用的业务对象的一些信息。
TransportModel model = new TransportModel();
// 设置客户端的调用对象
model.setObject(helloService);
// 设置需要调用的方法
model.setMethodName("sayHello");
// 获得业务对象的字节码信息
Class class1 = helloService.getClass();
// 在业务对象的字节码信息中获取"sayHello"并且方法入参为String的方法
Method method = class1.getMethod("sayHello",String.class);
// 设置传输模型对象中的调用信息。
// 设置方法参数类型
model.setParameterTypes(method.getParameterTypes());
// 设置方法参数
model.setParameters(new Object[]{"The first step of RPC"});
} catch (Exception e) {
e.printStackTrace();
}
}
}
将数据传输模型对象发送到服务端
在设置好相关调用信息后,现在终于可以去服务端调用了,但是我们不可能直接将数据传输模型对象“给”服务端,在网络中传输数据都是以流(比特流)的形式传输的, 所以我们还要将数据传输模型对象转为流,传输给服务端。
/**
* RPC客户端,这里发起调用请求。
* 模拟RPC框架调用过程
* @author wushuaiping
* @date 2018/3/15 下午12:22
*/
public class ObjectClientSerializ {
public static void main(String[] args) {
try {
// 使用Socket与指定IP的主机端口进行连接。
Socket socket = new Socket("localhost", 8081);
// 创建一个业务对象,模拟客户端发起调用。
HelloService helloService = new HelloServiceImpl();
// 该传输模型对象存储了客户端发起调用的业务对象的一些信息。
TransportModel model = new TransportModel();
// 设置客户端的调用对象
model.setObject(helloService);
// 设置需要调用的方法
model.setMethodName("sayHello");
// 获得业务对象的字节码信息
Class class1 = helloService.getClass();
// 在业务对象的字节码信息中获取"sayHello"并且方法入参为String的方法
Method method = class1.getMethod("sayHello",String.class);
// 设置传输模型对象中的调用信息。
// 设置方法参数类型
model.setParameterTypes(method.getParameterTypes());
// 设置方法参数
model.setParameters(new Object[]{"The first step of RPC"});
// 把存储了业务对象信息的数据传输模型对象转为流,也就是序列化对象。方便在网络中传输。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(model);
oos.flush();
byte[] byteArray = bos.toByteArray();
// 获得一个socket的输出流。通过该流可以将数据传输到服务端。
OutputStream outputStream = socket.getOutputStream();
// 往输出流中写入需要进行传输的序列化后的流信息
outputStream.write(byteArray);
outputStream.flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
获取服务端返回的信息
当我们把数据序列化后以流的方式传输给了服务端。肯定不是大功告成了,因为我们还得知道服务端给我们返回了什么东西:
/**
* RPC客户端,这里发起调用请求。
* 模拟RPC框架调用过程
* @author wushuaiping
* @date 2018/3/15 下午12:22
*/
public class ObjectClientSerializ {
public static void main(String[] args) {
try {
// 使用Socket与指定IP的主机端口进行连接。
Socket socket = new Socket("localhost", 8081);
// 创建一个业务对象,模拟客户端发起调用。
HelloService helloService = new HelloServiceImpl();
// 该传输模型对象存储了客户端发起调用的业务对象的一些信息。
TransportModel model = new TransportModel();
// 设置客户端的调用对象
model.setObject(helloService);
// 设置需要调用的方法
model.setMethodName("sayHello");
// 获得业务对象的字节码信息
Class class1 = helloService.getClass();
// 在业务对象的字节码信息中获取"sayHello"并且方法入参为String的方法
Method method = class1.getMethod("sayHello",String.class);
// 设置传输模型对象中的调用信息。
// 设置方法参数类型
model.setParameterTypes(method.getParameterTypes());
// 设置方法参数
model.setParameters(new Object[]{"The first step of RPC"});
// 把存储了业务对象信息的数据传输模型对象转为流,也就是序列化对象。方便在网络中传输。
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(model);
oos.flush();
byte[] byteArray = bos.toByteArray();
// 获得一个socket的输出流。通过该流可以将数据传输到服务端。
OutputStream outputStream = socket.getOutputStream();
// 往输出流中写入需要进行传输的序列化后的流信息
outputStream.write(byteArray);
outputStream.flush();
// 因为socket建立的是长连接,所以可以获取到将流数据传到服务端后,返回的信息。
// 所以我们需要通过输入流,来获取服务端返回的流数据信息。
InputStream inputStream = socket.getInputStream();
ObjectInputStream ois = new ObjectInputStream(inputStream);
// 将得到的流数据读成Object对象,强转为我们的数据传输模型对象。最后得到服务端返回的结果。
TransportModel readObject = (TransportModel)ois.readObject();
System.out.println("调用返回结果="+readObject.getResult());
socket.close();
System.out.println("客户端调用结束");
} catch (Exception e) {
e.printStackTrace();
}
}
}
此时,我们客户端的调用算是大功告成了。接下来我们应该去服务端接收客户端发送过来的数据了。
服务端接收客户端数据
客户端接收到的数据是以流方式存在的,所以需要反序列化转流为Java对象。
/**
* RPC服务端
* @author wushuaiping
* @date 2018/3/15 下午12:23
*/
public class ObjectServerSerializ {
public static void main(String[] args) {
try {
// 启动服务端,并监听8081端口
ServerSocket serverSocket = new ServerSocket(8081);
// 服务端启动后,等待客户端建立连接
Socket accept = serverSocket.accept();
// 获取客户端的输入流,并将流信息读成Object对象。
// 然后强转为我们的数据传输模型对象,因为我们客户端也是用的该对象进行传输,所以强转没有问题。
InputStream inputStream = accept.getInputStream();
ObjectInputStream ois = new ObjectInputStream(inputStream);
TransportModel transportModel = (TransportModel) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端通过反射调用方法
因为需要调用的对象方法等相关数据都封装在数据传输模型对象里面,所以我们只需要把里面的参数拿出来,再通过反射去掉用服务端存在的本地方法即可。
/**
* RPC服务端
* @author wushuaiping
* @date 2018/3/15 下午12:23
*/
public class ObjectServerSerializ {
public static void main(String[] args) {
try {
// 启动服务端,并监听8081端口
ServerSocket serverSocket = new ServerSocket(8081);
// 服务端启动后,等待客户端建立连接
Socket accept = serverSocket.accept();
// 获取客户端的输入流,并将流信息读成Object对象。
// 然后强转为我们的数据传输模型对象,因为我们客户端也是用的该对象进行传输,所以强转没有问题。
InputStream inputStream = accept.getInputStream();
ObjectInputStream ois = new ObjectInputStream(inputStream);
TransportModel transportModel = (TransportModel) ois.readObject();
// 因为客户端在把流信息发过来之前,已经把相关的调用信息封装进我们的数据传输模型对象中了
// 所以这里我们可以直接拿到这些对象的信息,然后通过反射,对方法进行调用。
Object object = transportModel.getObject();
String methodName = transportModel.getMethodName();
Class<?>[] parameterTypes = transportModel.getParameterTypes();
Object[] parameters = transportModel.getParameters();
// 通过方法名和方法参数类型,得到一个方法对象
Method method = object.getClass().getMethod(methodName,parameterTypes);
// 然后通过这个方法对象去掉用目标方法,返回的是这个方法执行后返回的数据
Object res = method.invoke(object, parameters);
System.out.println("提供服务端执行方法返回结果:"+res);
} catch (Exception e) {
e.printStackTrace();
}
}
}
服务端将数据返回给客户端
服务端通过反射调用完目标方法后,我们还需要将调用目标方法后得到的数据返回给客户端。
/**
* RPC服务端
* @author wushuaiping
* @date 2018/3/15 下午12:23
*/
public class ObjectServerSerializ {
public static void main(String[] args) {
try {
// 启动服务端,并监听8081端口
ServerSocket serverSocket = new ServerSocket(8081);
// 服务端启动后,等待客户端建立连接
Socket accept = serverSocket.accept();
// 获取客户端的输入流,并将流信息读成Object对象。
// 然后强转为我们的数据传输模型对象,因为我们客户端也是用的该对象进行传输,所以强转没有问题。
InputStream inputStream = accept.getInputStream();
ObjectInputStream ois = new ObjectInputStream(inputStream);
TransportModel transportModel = (TransportModel) ois.readObject();
// 因为客户端在把流信息发过来之前,已经把相关的调用信息封装进我们的数据传输模型对象中了
// 所以这里我们可以直接拿到这些对象的信息,然后通过反射,对方法进行调用。
Object object = transportModel.getObject();
String methodName = transportModel.getMethodName();
Class<?>[] parameterTypes = transportModel.getParameterTypes();
Object[] parameters = transportModel.getParameters();
// 通过方法名和方法参数类型,得到一个方法对象
Method method = object.getClass().getMethod(methodName,parameterTypes);
// 然后通过这个方法对象去掉用目标方法,返回的是这个方法执行后返回的数据
Object res = method.invoke(object, parameters);
System.out.println("提供服务端执行方法返回结果:"+res);
// 获得服务端的输出流
OutputStream outputStream = accept.getOutputStream();
// 建立一个字节数组输出流对象。把数据传输模型对象序列化。方便进行网络传输
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
// 创建一个数据传输模型对象,将服务端的返回数据传到客户端。
TransportModel transportModel1 = new TransportModel();
transportModel1.setResult(res);
oos.writeObject(transportModel1);
outputStream.write(bos.toByteArray());
outputStream.flush();
bos.close();
outputStream.close();
serverSocket.close();
System.out.println("服务端关闭");
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试
先启动服务端的main方法,在启用客户端的main方法。之后我们会看到如下输出:
调用返回结果=The first step of RPC
客户端调用结束
https://my.oschina.net/u/3152087/blog/1635614
使用Socket&反射&Java流操作进行方法的远程调用(模拟RPC远程调用)的更多相关文章
- java字符流操作flush()方法及其注意事项
java字符流操作flush()方法及其注意事项 flush()方法介绍 查阅文档可以发现,IO流中每一个类都实现了Closeable接口,它们进行资源操作之后都需要执行close()方法将流关闭 ...
- java 流操作对文件的分割和合并的实例详解_java - JAVA
文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 java 流操作对文件的分割和合并的实例详解 学习文件的输入输出流,自己做一个小的示例,对文件进行分割和合并. 下面是代 ...
- 初探java流操作
在处理集合时,我们通常会迭代遍历它的元素,并从每个元素上执行某项操作.例如,假设我们想要对某本书中的所有长单词进行计数.首先我们要将所有单词放入一个列表中: String contents = new ...
- Java流操作之转换流
流的操作规律: 1.明确流和目的. 数据源(源头):就是需要读取,可以使用两个体系:InputStream.Reader 数据汇(目的地):就是需要写入,可以使用两个体系:OutputStream.W ...
- [WCF编程]10.操作:流操作
一.流操作概述 在默认情况下,当客户端调用服务时,服务只有在接收到完整的消息后才会被调用,同样,客户端只有在包含了调用结果的返回消息被完整接受时,才会解除对它的阻塞. 对于数据量小的消息,这种交换模式 ...
- Java 8-Lambda表达式、方法引用、标准函数接口与流操作、管道操作之间的关系
1.Lambda表达式与接口之间的关系 只要Lambda表达式的声明形式与接口相一致,在很多情况下都可以替换接口.见如下代码 Thread t1 = new Thread(new Runnable() ...
- java网络编程ServerSocket类 和Socket类的常用构造方法及其方法
Socket类Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号.Socket(String host, int po ...
- java 大文件上传 断点续传 完整版实例 (Socket、IO流)
ava两台服务器之间,大文件上传(续传),采用了Socket通信机制以及JavaIO流两个技术点,具体思路如下: 实现思路: 1.服:利用ServerSocket搭建服务器,开启相应端口,进行长连接操 ...
- Java中Socket上的Read操作堵塞问题
从Socket上读取对端发过来的数据一般有两种方法: 1)依照字节流读取 BufferedInputStream in = new BufferedInputStream(socket.getInpu ...
随机推荐
- RNN与BPTT (公式甚多)
前言: 现在深度学习是一个潮流,同时,导师也给自己制定了深度学习的方向.在一次组会中,自己讲解了RNN的基本用法,和RNN数学原理的推导.以下是自己根据当时的PPT总结下来的东西.
- SQL Server RAISERROR() 函数
生成错误消息并启动会话的错误处理. RAISERROR 可以引用 sys.messages 目录视图中存储的用户定义消息,也可以动态建立消息. 该消息作为服务器错误消息返回到调用应用程序,或返回到 T ...
- 【vijos】1750 建房子(线段树套线段树+前缀和)
https://vijos.org/p/1750 是不是我想复杂了.... 自己yy了个二维线段树,然后愉快的敲打. 但是wa了两法.......sad 原因是在处理第二维的更新出现了个小问题,sad ...
- Stanford CoreNLP 3.6.0 中文指代消解模块调用失败的解决方案
当前中文指代消解领域比较活跃的研究者是Chen和Vincent Ng,这两个人近两年在AAAI2014, 2015发了一些相关的文章,研究领域跨越零指代.代词指代.名词指代等,方法也不是很复杂,集中于 ...
- Google I/O 2013 – Volley: Easy, Fast Networking for Android
1.什么是volley Volley是Ficus Kirpatrick在Gooogle I/O 2013发布的一个处理和缓存网络请求的库,能使网络通信更快,更简单,更健壮.Volle ...
- Windows按名称排序问题
偶然发现一个按名称排序的文件夹内,文件顺序是混乱的,例如: 在一个文件夹内建立如下三个文件: 0F.txt 1A.txt 02.txt 按名称/升序排列, 将得到上述结果,0F在最前,02在最后. 百 ...
- [Go语言]从Docker源码学习Go——指针和Structs
这两天在看reflect这个包在Docker中的使用时,遇到了各种问题,最后虽然知道怎么用了. 但是对于这块的原理还不是太懂,于是把"THE WAY TO GO"中关键的几章看了下 ...
- python 解析 XML文件
如下使用xml.etree.ElementTree模块来解析XML文件.ElementTree模块中提供了两个类用来完成这个目的: ElementTree表示整个XML文件(一个树形结构) Eleme ...
- 巨蟒python全栈开发数据库攻略4:多表操作&Navicat&pymysql
1.多表查询 2.连表补充 3.boss工具=>Navicat 4.索引加速寻找工具=>everything 5.pymysql 6.pymysql初识 7.pymysql的各个方法
- Powershell 脚本调用方法
方法一: Get-Content "D:\PowershellScripts\Test\ErrorMailNotice.ps1" | Invoke-Expression 方法二: ...