使用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 ...
随机推荐
- IntelliJ Idea Hide excluded folders 隐藏或显示你需要的文件夹
IntelliJ Idea,以前用idea时,经常maven编译就出现了exclude下的文件夹通常是target,如何隐藏自己不想看见的文件夹,或显示偶尔会用到的文件夹 点击齿轮右下小标 选中文件夹 ...
- git & github 菜鸟笔记
1.概念: 最先进的分布式版本控制系统 文件修改该提交的内容:---版本 文件名 用户 说明 日期 GitHub网站上线了,它为开源项目免费提供Git存储 --CVS及SVN都是集中式的版本控制系统, ...
- Delphi MessageBox
MessageBox对话框是比较常用的一个信息对话框,其不仅能够定义显示的信息内容.信息提示图标,而且可以定义按钮组合及对话框的标题,是一个功能齐全的信息对话框信息提示图标,而且可以定义按钮组合及对话 ...
- 【BZOJ4503】两个串 FFT
[BZOJ4503]两个串 Description 兔子们在玩两个串的游戏.给定两个字符串S和T,兔子们想知道T在S中出现了几次, 分别在哪些位置出现.注意T中可能有“?”字符,这个字符可以匹配任何字 ...
- 【BZOJ4870】[Shoi2017]组合数问题 动态规划(矩阵乘法)
[BZOJ4870][Shoi2017]组合数问题 Description Input 第一行有四个整数 n, p, k, r,所有整数含义见问题描述. 1 ≤ n ≤ 10^9, 0 ≤ r < ...
- delphi ----寻找控件,以字符串类型的名称控件
vari :Integer;beginfor i := 1 to 10 do(FindComponent('Edit'+inttostr(i)) as TEdit).Text := inttostr( ...
- Powershell Get Domain Group的几种方法
Group常见属性介绍: 一.Get-ADGroup获取群组(如下例循环获取群组的发送权限) #群组的发送权限info $groups=Get-ADGroup -filter * -SearchSco ...
- [科普]什么是SysWow64
Wow!什么是Wow64 今天有个同事,被SysWow64搞晕了.这里简单介绍一下. 64位的Windows并不是简单地把所有东西都编译成64位就万事大吉的.关于64位的CPU应该做成什么样子,Int ...
- php 问题及原因总结
1.php 加水印时出现问题的原因 :或许某个参数输入错误,导致页面一点反应都没有.
- Spring Data 关于Repository的介绍(四)
Repository类的定义: public interface Repository<T, ID extends Serializable> { } 1)Repository是一个空接口 ...