使用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 ...
随机推荐
- 初识Python、PyCharm、Anaconda与tensorflow
最近裸辞了,未来希望转深度学习.语音识别.文本挖掘,觉得这块特别有意思,比较好玩.开始自学相关知识,为了能够独立地.系统地了解和学习相关知识,计划不定期记录和更新一些平时的学习总结,个人关于以上几个方 ...
- Yii中的criteria 类
$criteria = new CDbCriteria; //select $criteria->select = '*';//默认* $criteria->select = 'id,na ...
- win7(64位)+vs2008配置Directshow
参考链接:http://zhuyanfeng.com/archives/1663 PC环境:win7 64bit + vs2008 1.下载64位的win7 SDK2.安装过程中遇到错误(必须要卸载v ...
- iOS-tableView本地动画刷新
比如:就拿删除tableView中一个Cell为例子. // XXXTableViewCellDelegate - (void)tapDeleteHelloUser:(CJHelloTableView ...
- 智力大冲浪(洛谷P1230)
题目描述 小伟报名参加中央电视台的智力大冲浪节目.本次挑战赛吸引了众多参赛者,主持人为了表彰大家的勇气,先奖励每个参赛者m元.先不要太高兴!因为这些钱还不一定都是你的?!接下来主持人宣布了比赛规则: ...
- 【BZOJ3829】[Poi2014]FarmCraft 树形DP(贪心)
[BZOJ3829][Poi2014]FarmCraft Description In a village called Byteville, there are houses connected ...
- angualar入门学习-- 自定义指令 指令编译执行过程
3个阶段: 一.加载阶段 加载angular.js的源码,找到ng-app确定应用边界范围. 二.编译阶段 compile 查找所有指令,保存在一个列表中 对所有指令按优先级(property属性值) ...
- 滚动插件 animatescroll(可以设置要滚动到位置)
1. 引入 <script src="js/animatescroll.js"></script> 2.设置要滚动到的位置 $('目标位置').an ...
- SpringCloud落地实践
这几年微服务架构越来越火.伴随着微服务概念的提示,越来越多的组织为了方便开发,结合实际提供很多微服务机构, 之前工作中一直使用dubbo作为微服务框架, dubbo只是专注于服务之间的通讯,所以更灵活 ...
- 推荐10 个短小却超实用的 JavaScript 代码段
1. 判断日期是否有效 JavaScript中自带的日期函数还是太过简单,很难满足真实项目中对不同日期格式进行解析和判断的需要.jQuery也有一些第三方库来使日期相关的处理变得简单,但有时你可能只需 ...