使用Socket&反射&Java流操做进行方法的远程调用(模拟RPC远程调用)

写在前面

阅读本文首先得具有基本的Socket、反射、Java流操做的基本API使用知识;不然本文你可能看不懂。。。java

服务端的端口监听

进行远程调用,那就必须得有客户端和服务端。服务端负责提供服务,客户端来对服务端进行方法调用。因此如今咱们清楚了: 须要一个服务端、一个客户端面试

那么咱们说干就干,咱们先创建一个服务端:数组

  • 经过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);
}

业务方法实现类socket

远程调用必需要实现序列化接口(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来调用方法吧,因此咱们须要一个能封装业务类方法信息的数据传输对象。那么该数据传输对象须要具有哪些信息?服务端调用确定得用反射来调用方法,因此咱们这个数据传输对象就得知足一下条件:this

  • 第一,反射调用时必须知道方法名 String methodName
  • 第二,反射调用时必须知道方法参数类型 Object[] parameterTypes
  • 第三,反射调用时必须知道参数 Object[] parameters
  • 第四,反射调用时必须知道哪一个对象在调用 Object invokeObject

知足以上条件后,就能够进行反射调用方法了,可是,咱们经过服务端调用后,咱们须要知道服务端返回的数据信息。那么该对象还须要一个参数:.net

  • 第五,须要一个返回对象 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
客户端调用结束

写在最后

至此,方法的远程调用已经完成~~ 这篇文章写得有点仓促,明天还有面试。今天就先这样了~ 晚安~

相关文章
相关标签/搜索