Remote invocation of methods using Socket & reflection & Java stream operations (simulating RPC remote invocation)

write in front

To read this article, you must first have basic knowledge of Socket, reflection, and basic API usage of Java stream operations; otherwise, you may not understand this article. . .

Server port listening

To make remote calls, there must be a client and a server. The server is responsible for providing services, and the client makes method calls to the server. So now we are clear: we need a server and a client

So let's do what we say, let's build a server first:

  • Listen to a port (8081) of the local server through Socket
  • Call the accept method of the socket to wait for the connection of the client (the principle of the accpet method )
/**
 *  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();
        }
    }
}

Client establishes connection with server

After our server listens to the port, then we need to use the client to access the port of the target server. The code is as follows:

/**
 *  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();
        }
    }
}

business approach

After establishing a connection with the server, let's go to the next step. Because we want to simulate RPC remote calls, then we have a business method:

business method interface

/**
 * 业务方法接口
 */
public interface HelloService {

	String sayHello(String str);
}

business method implementation class

Remote calls must implement the serializable interface (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;
	}

}

Data Transfer Model Object

After we have the service method, the first thing that comes to our mind is, if we transmit the serialized object to the server, how does the server know which object it is? It is impossible to use Object to call methods, so we need a data transfer object that can encapsulate business class method information. So what information does the data transfer object need to have? The server-side call must use reflection to call the method, so our data transfer object must meet the following conditions:

  • First, the method name String methodName must be known when the reflection call is made
  • Second, the method parameter type Object[] parameterTypes must be known when the reflection call is made
  • Third, the parameter Object[] parameters must be known when the reflection call is made
  • Fourth, the reflection call must know which object is calling Object invokeObject

After the above conditions are met, the method can be called by reflection. However, after we call it through the server, we need to know the data information returned by the server. Then the object also needs one parameter:

  • Fifth, you need a return object Object result

Through the above analysis, we established the object:

/**
 *  数据传输模型
 * @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;
    }
}

The client sets the corresponding call information

After we have the data transmission model, we encapsulate the required object information into the data transmission model, and we can actually start calling the server-side service!

/**
 *  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();
            }
        }
}

Send the data transfer model object to the server

After setting the relevant call information, we can finally call the server, but it is impossible for us to directly "give" the data transmission model object to the server. The data transmitted in the network is transmitted in the form of stream ( bit stream ). , so we also need to convert the data transmission model object into a stream and transmit it to the server.

/**
 *  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();
            }
        }
}

Get the information returned by the server

When we serialize the data and stream it to the server. It's definitely not done, because we still have to know what the server returns to us:

/**
 *  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();
            }
        }
}

At this point, our client's call is done. Next, we should go to the server to receive the data sent by the client.

Server receives client data

The data received by the client exists in the form of a stream, so it needs to be deserialized and converted into a Java object.

/**
 *  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();
        }
    }
}

The server calls the method through reflection

Because the related data such as the object methods to be called are encapsulated in the data transmission model object, we only need to take out the parameters inside, and then remove the local methods that exist on the server through reflection.

/**
 *  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();
        }
    }
}

The server returns the data to the client

After the server calls the target method through reflection, we also need to return the data obtained after calling the target method to the client.

/**
 *  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();
        }
    }
}

test

Start the main method of the server first, and then enable the main method of the client. After that we will see the following output:

调用返回结果=The first step of RPC
客户端调用结束

write at the end

So far, the remote invocation of the method has been completed~~ This article was written a bit hastily, and there will be an interview tomorrow. That's it for today~ Good night~

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325902811&siteId=291194637