写在前面

阅读本文首先得具备基本的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远程调用)的更多相关文章

  1. java字符流操作flush()方法及其注意事项

    java字符流操作flush()方法及其注意事项   flush()方法介绍 查阅文档可以发现,IO流中每一个类都实现了Closeable接口,它们进行资源操作之后都需要执行close()方法将流关闭 ...

  2. java 流操作对文件的分割和合并的实例详解_java - JAVA

    文章来源:嗨学网 敏而好学论坛www.piaodoo.com 欢迎大家相互学习 java 流操作对文件的分割和合并的实例详解 学习文件的输入输出流,自己做一个小的示例,对文件进行分割和合并. 下面是代 ...

  3. 初探java流操作

    在处理集合时,我们通常会迭代遍历它的元素,并从每个元素上执行某项操作.例如,假设我们想要对某本书中的所有长单词进行计数.首先我们要将所有单词放入一个列表中: String contents = new ...

  4. Java流操作之转换流

    流的操作规律: 1.明确流和目的. 数据源(源头):就是需要读取,可以使用两个体系:InputStream.Reader 数据汇(目的地):就是需要写入,可以使用两个体系:OutputStream.W ...

  5. [WCF编程]10.操作:流操作

    一.流操作概述 在默认情况下,当客户端调用服务时,服务只有在接收到完整的消息后才会被调用,同样,客户端只有在包含了调用结果的返回消息被完整接受时,才会解除对它的阻塞. 对于数据量小的消息,这种交换模式 ...

  6. Java 8-Lambda表达式、方法引用、标准函数接口与流操作、管道操作之间的关系

    1.Lambda表达式与接口之间的关系 只要Lambda表达式的声明形式与接口相一致,在很多情况下都可以替换接口.见如下代码 Thread t1 = new Thread(new Runnable() ...

  7. java网络编程ServerSocket类 和Socket类的常用构造方法及其方法

    Socket类Socket(InetAddress address, int port) 创建一个流套接字并将其连接到指定 IP 地址的指定端口号.Socket(String host, int po ...

  8. java 大文件上传 断点续传 完整版实例 (Socket、IO流)

    ava两台服务器之间,大文件上传(续传),采用了Socket通信机制以及JavaIO流两个技术点,具体思路如下: 实现思路: 1.服:利用ServerSocket搭建服务器,开启相应端口,进行长连接操 ...

  9. Java中Socket上的Read操作堵塞问题

    从Socket上读取对端发过来的数据一般有两种方法: 1)依照字节流读取 BufferedInputStream in = new BufferedInputStream(socket.getInpu ...

随机推荐

  1. AWT提供了Java Applet 和Java Application中可用的用户图形界面 GUI 中的基本组件

    AWT提供了Java Applet 和Java Application中可用的用户图形界面 GUI 中的基本组件( component s). 由于Java是一种独立于平台的 程序设计语言 ,但GUI ...

  2. sql server数据库数据查询成功

    <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding= ...

  3. React资料

    基于ReactNative开发的APPhttp://reactnative.cn/cases.htmlhttp://www.cnblogs.com/qiangxia/p/5584622.html F8 ...

  4. 把資源加载到内存中 BMP 出错

    BMP文件放到VS的資源中時,VS會將BMP的文件頭去掉,即BITMAPFILEHEADER,這個結構體去除.所以當加載BMP到內存中時,如果是使用GDI+或是其它解釋庫時,會解析失敗. 所以在讀取B ...

  5. JZOJ.5230【NOIP2017模拟8.5】队伍统计

    Description 现在有n个人要排成一列,编号为1->n .但由于一些不明原因的关系,人与人之间可能存在一些矛盾关系,具体有m条矛盾关系(u,v),表示编号为u的人想要排在编号为v的人前面 ...

  6. 3969 [Mz]平方和【斐波那契平方和】

    3969 [Mz]平方和  时间限制: 1 s  空间限制: 64000 KB  题目等级 : 大师 Master 题解  查看运行结果     题目描述 Description 斐波那契数列:f[0 ...

  7. 【BZOJ1576】[Usaco2009 Jan]安全路经Travel 最短路+并查集

    [BZOJ1576][Usaco2009 Jan]安全路经Travel Description Input * 第一行: 两个空格分开的数, N和M * 第2..M+1行: 三个空格分开的数a_i, ...

  8. vitess元数据跨机房灾备解决方案

    测试使用vitess的时候发现vitess元数据的实现有多种方案,etcd, etcd2, zk,zk2, 由于刚开始测试的时候使用的是基于k8s集群+etcd的,以下就分步说明灾备实现方案: 1. ...

  9. TADOConnection和TADQuery组件连接访问数据库总结

    首先先来简单了解一下TADOConnection控件和TADOQuery控件作用,如下图: 好了简单对这两个控件有了一个初步的认识了,下面我们就通过两种形式来进行访问数据库:第一种是通过手动配置不需要 ...

  10. Java Json API:Gson使用简单入门

    GSON是Google开发的Java API,用于转换Java对象和Json对象.本文讨论并提供了使用API的简单代码示例.更多关于GSON的API可以访问:http://sites.google.c ...