使用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 ...
随机推荐
- hpfeeds协议解析
一. hpfeeds协议简介 hpfeeds是一个轻量级的验证发布-订阅协议(authenticated publish-subscribe protocol). 发布-订阅协议:发布/订阅协议定义了 ...
- 第二百四十二节,Bootstrap列表组面板和嵌入组件
Bootstrap列表组面板和嵌入组件 学习要点: 1.列表组组件 2.面板组件 3.响应式嵌入组件 本节课我们主要学习一下 Bootstrap 的三个组件功能:列表组组件.面板组件. 响应 式嵌入组 ...
- iOS开发之 -- 判断tableview/scrollview的滑动方法,及导航栏渐变的实现代码
开发的过程中,肯定会用到在视图想上滑动的时候,在导航处做一些操作,比如向上滑动的时候,做个动画,出现一个搜索框,或者其他的操作,那么我们怎么来判断它的滑动方向呢? 首先我们应该知道tableview继 ...
- 关于在ubuntu平台下使用apt-get命令下载速度太慢的问题解决
1. 进入设置,从哪进都一样找到就行. 2.选择软件与更新(Software and updates,英语估计是这个把) 点击下载自:这个下拉框,选中其它站点,选择镜像 选择阿里的或者搜狐的镜像,然后 ...
- java关于Timer schedule执行定时任务 !!!!!!!!!
1.在应用开发中,经常需要一些周期性的操作,比如每5分钟执行某一操作等.对于这样的操作最方便.高效的实现方式就是使用java.util.Timer工具类. private java.util.Time ...
- 【bzoj4518】[Sdoi2016]征途 斜率优化dp
原文地址:http://www.cnblogs.com/GXZlegend/p/6812435.html 题目描述 Pine开始了从S地到T地的征途. 从S地到T地的路可以划分成n段,相邻两段路的分界 ...
- cannot be cast to javax.servletFilter
java.lang.ClassCastException: org.springframework.web.filter.CharacterEncodingFilter cannot be cast ...
- (转)Linux-epoll
在Linux网络编程中,Linux内核2.6版本之前大多都是用 select() 作为非阻塞的事件触发模型,但是效率低,使用受限已经很明显的暴露了select()(包括poll)的缺陷,为了解决这些缺 ...
- 使用ShardingJdbc分表
项目中做个统一订单的基础服务(只记录订单的基本的公共信息),1.便与后续各种其他业务的接入~ 2.同时APP端提供统一订单信息的查询入口,后续其他业务不用升级 由于统一的订单服务,所以订单量会很大,所 ...
- Cisco路由器DHCP配置浅析
enable config terminal (进入配置模式) ip dhcp pool global(配置一个根地址池,global是地址池的名称,你可以采用有意义的字符串来表示) config ...