[java] RPC and RMI framework

Posted by xyzleft on Tue, 28 Jan 2020 07:32:42 +0100

Catalog

Summary

  • RPC (Remote Procedure Call Protocol) A remote procedure call protocol that requests a service from a remote computer over the network.It is a protocol that requests services from remote computer programs over a network without needing to know the underlying network technology.
  • RMI (Remote Method Invocation) remote method call.Enables objects on client-side Java virtual machines to invoke methods on objects in server-side Java virtual machines as if they were local objects.

The difference between them is that RPC is a network transport protocol and does not support object transfer; while RMI is based on Java and supports object transfer, RMI can be seen as a java version of the RPC implementation.

From the above point of view, RMI and RPC are basically the same design idea, they are all remote connection servers to request a service.The following blogger will explain the implementation of the RMI framework in detail.

RMI Framework

There are many RMI frameworks on the Internet based on object serialization, and bloggers will implement the RMI framework through proxy mechanism. In the client, only the interface of calling method is saved, but the interface implementation class is not saved. The implementation class object of interface is obtained through proxy mechanism, the connection of remote server, the transfer of methods and parameters, and the reception of returned results are carried out in method interception.The connection to the server is closed after the result is received.Save the interface and interface implementation class on the server side, register the implementation class objects and methods into the container through annotations, and send the results of method execution back to the client when the client invokes the method.

register

Registering classes and methods implemented by server-side interfaces is a prerequisite for RMI. Registering can be done by annotations or by file configuration. This framework uses annotations.Scan through packages before opening the server (visible) Package Scanning Tool ) Find the class with the specified annotation, which is an array of interface types, scan each interface in the array successively to find the corresponding method to implement the class, form a MethodDefinition, and save the hashCode with the full method name of the interface as the key-value in the map for use by RPCServer.
Since this framework only saves interfaces containing methods on the client side, when the client sends interface methods to the server side, the server will find a way to save the interface implementation classes on the server side by the method name, number of parameters, parameter type of the methods sent. This process is very cumbersome and time-consuming; the blogger's solution is to sweep the classes through the package beforehand.Describes how methods in an interface and methods in an interface implementation class are mapped to each other to form a map so that clients can find the corresponding method directly by sending the hash value of the interface method, saving time for remote calls.

MethodFactory

package com.dl.server.core;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import com.parser_reflect.util.PackageScanner;

/**
 * RPC Method Scan Class <br>
 * 1,This class is the core class of RPC and scans the specified interfaces in the specified classes; <br>
 * 2,Cache the scanned method in the methodPool; <br>
 * 3,map Hash value of interface method is key, MethodDefinition is value; <br>
 * 4,Provides a manual registration method to support getting Definition from hash values;
 * 
 * @author dl
 *
 */
public class MethodFactory {
	private static final Map<Integer, MethodDefinition> methodPool;
	
	static {
		methodPool = new HashMap<>();
	}

	public MethodFactory() {
	}
	
	/**
	 * Package scan to find the class for the specified annotation;
	 * 
	 * @param path
	 */
	public static void scanPackage(String path) {
		new PackageScanner() {
			
			@Override
			public void dealClass(Class<?> klass) {
				if (klass.getAnnotation(ProxyAnntotation.class) == null) {
					return;
				}
				priRegistry(klass);
			}
		}.packageScanner(path);
	}
	
	/**
	 * Traverse the methods of the specified interface in the class to form a map.
	 * 
	 * @param klass
	 */
	private static void priRegistry(Class<?> klass) {
		Object object = null;
		try {
			object = klass.newInstance();
		} catch (InstantiationException e1) {
			e1.printStackTrace();
		} catch (IllegalAccessException e1) {
			e1.printStackTrace();
		}
		
		ProxyAnntotation proxy = klass.getAnnotation(ProxyAnntotation.class);
		Class<?>[] interfaces = proxy.interfaces();
		// Traversing through an array of interfaces in a comment
		for (Class<?> clazz : interfaces) {
			Method[] methods = clazz.getMethods();
			for (Method method : methods) {
				try {
					// method.getParameterTypes() gives generic parameter types
					Method meth = klass.getDeclaredMethod(method.getName(), method.getParameterTypes());
					if (meth != null) {
						MethodDefinition definition = new MethodDefinition(klass, object, meth);
						// Form key-value with hashCode as key and MethodDefinition as value
						methodPool.put(method.toString().hashCode(), definition);
					}
				} catch (NoSuchMethodException e) {
					e.printStackTrace();
				} catch (SecurityException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
	/**
	 * Provide a manual means of registration for display;
	 * 
	 * @param klass
	 */
	public void registry(Class<?> klass) {
		if (klass.getAnnotation(ProxyAnntotation.class) == null) {
			return;
		}
		priRegistry(klass);
	}
	
	/**
	 * Get the corresponding MethodDefinition through the hashCode of the interface method;
	 * 
	 * @param rpcMethodId
	 * @return
	 */
	public static MethodDefinition getMethodDefinition(int rpcMethodId) {
		return methodPool.get(rpcMethodId);
	}
}

MethodDefinition

package com.dl.server.core;

import java.lang.reflect.Method;

/**
 * Class holding method information <br>
 * Save the scanned methods and corresponding classes and objects;
 * 
 * @author dl
 *
 */
public class MethodDefinition {
	private Class<?> klass;
	private Object object;
	private Method method;

	public MethodDefinition() {
	}
	
	public MethodDefinition(Class<?> klass, Object object, Method method) {
		this.klass = klass;
		this.object = object;
		this.method = method;
	}
	
	public Class<?> getKlass() {
		return klass;
	}

	public void setKlass(Class<?> klass) {
		this.klass = klass;
	}

	public Object getObject() {
		return object;
	}

	public void setObject(Object object) {
		this.object = object;
	}

	public Method getMethod() {
		return method;
	}

	public void setMethod(Method method) {
		this.method = method;
	}

}

ProxyAnntotation

package com.dl.server.core;

import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(TYPE)

/**
 * Note <br>
 * Prepare for package scan;
 * 
 * @author dl
 *
 */
public @interface ProxyAnntotation {
	Class<?>[] interfaces();
}

Server

RPCServer

package com.dl.server.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import com.parser_reflect.util.PropertiesParser;
import com.util.ArgumentMaker;

/**
 * RPC Server <br>
 * 1,Support RPC server on and off; <br>
 * 2,Use thread pools to process method calls from RPC clients; <br>
 * 3,port of configurable server;
 * 
 * @author dl
 *
 */
public class RPCServer implements Runnable {
	public static final int DEFAULT_PORT = 55555;
	private static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 100, 
			5000, TimeUnit.MILLISECONDS, 
			new LinkedBlockingDeque<>());
	
	private ServerSocket server;
	private int port;
	
	private volatile boolean goon;
	
	/**
	 * Read the configuration file under the default path
	 */
	public RPCServer() {
		this.port = DEFAULT_PORT;
		readConfig("/RPCConfig.properties");
	}
	
	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * Read the configuration file under the specified path
	 * @param path
	 */
	public void readConfig(String path) {
		PropertiesParser.load(path);
		
		String portStr = PropertiesParser.findElement("RPCPort");
		try {
			if (portStr != null && !portStr.equals("")) {
				int port = Integer.valueOf(portStr);
				if (port > 0 && port < 65536) {
					this.port = port;	
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Open the server and add the client listening thread to the thread pool.
	 */
	public void startup() {
		try {
			server = new ServerSocket(port);
			goon = true;
			threadPool.execute(new Thread(this));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Close the server and the thread pool.
	 */
	public void shutdown() {
		goon = false;
		try {
			if (!server.isClosed() && server != null) {
				server.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			server = null;
		}
		threadPool.shutdown();
	}

	/**
	 * Listening threads, if new client connections are received, will be processed through the thread pool.
	 */
	@Override
	public void run() {
		while (goon) {
			try {
				Socket socket = server.accept();
				ServerExecutor executer = new ServerExecutor();
				executer.setClient(socket);
				threadPool.execute(executer);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}

ServerExecutor

Bloggers use internal classes to bind communication classes to RPCServer classes

/**
 * Communication internal class <br>
 * 1,Save communication channels, receive and send messages; <br>
 * 2,Parse the client's message and find the appropriate method to send after execution;
 * 
 * @author dl
 *
 */
private class ServerExecutor implements Runnable {
	private DataInputStream dis;
	private DataOutputStream dos;
	
	public ServerExecutor() {
	}
	
	public void setClient(Socket client) {
		try {
			dis = new DataInputStream(client.getInputStream());
			dos = new DataOutputStream(client.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Close communication channel
	 */
	private void close() {
		try {
			if (dis != null) {
				dis.close();		
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dis = null;
		}
		try {
			if (dos != null) {
				dos.close();	
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dos = null;
		}
	}
	
	/**
	 * 1,Client sends twice, server receives twice; <br>
	 * 2,Find and execute the corresponding method by receiving rpcMethodId; <br>
	 * 3,Send the result of method execution to client;
	 */
	@Override
	public void run() {
		try {
			String method = dis.readUTF();
			int rpcMethodId = Integer.valueOf(method);
			String argsStr = dis.readUTF();
			ArgumentMaker maker = new ArgumentMaker(argsStr);
			MethodDefinition methodDefinition = MethodFactory.getMethodDefinition(rpcMethodId);
			if (methodDefinition == null) {
				dos.writeUTF("null");
			} else {
				Object result = invokeMethod(methodDefinition, maker);
				String resultStr = ArgumentMaker.gson.toJson(result);
				dos.writeUTF(resultStr);
			}
			close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 1,Get the corresponding method and object through methodDefinition; <br>
	 * 2,Converts the passed string to the specified object through gson;
	 * 
	 * @param methodDefinition
	 * @param maker
	 * @return
	 */
	private Object invokeMethod(MethodDefinition methodDefinition, ArgumentMaker maker) {
		Object object = methodDefinition.getObject();
		Method method = methodDefinition.getMethod();
		
		Parameter[] parameters = method.getParameters();
		int size = parameters.length;
		Object[] args = new Object[size];
		for (int index = 0; index < size; index++) {
			args[index] = maker.getValue(
						"arg" + index, 
						parameters[index].getParameterizedType());
		}
		
		try {
			return method.invoke(object, args);
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return null;
	}		
}

ArgumentMaker

Although map can preserve the mapping relationship between parameter names and parameter objects, it can disrupt the order of parameters, and the reflection mechanism can not get an accurate parameter name, which will cause the reflection execution method to fail; therefore, the relationship between each parameter and its corresponding object needs to be specified through the protocol, so the following class can be used to refer to the invokeMethod method of the ServerExecutor class for the gson objectUse is not explained here.

package com.util;

import java.lang.reflect.Type;

import java.util.HashMap;
import java.util.Map;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;

public class ArgumentMaker {
	public static final Gson gson = new GsonBuilder().create();
	private static final Type type = new TypeToken<Map<String, String>>() {}.getType();
	private Map<String, String> paraMap;
	
	public ArgumentMaker() {
		paraMap = new HashMap<String, String>();
	}
	
	public ArgumentMaker(String str) {
		paraMap = gson.fromJson(str, type);
	}
	
	public ArgumentMaker addArg(String name, Object value) {
		paraMap.put(name, gson.toJson(value));
		
		return this;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getValue(String name, Type type) {
		String valueString = paraMap.get(name);
		if (valueString == null) {
			return null;
		}
		
		return (T) gson.fromJson(valueString, type);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getValue(String name, Class<?> type) {
		String valueString = paraMap.get(name);
		if (valueString == null) {
			return null;
		}
		
		return (T) gson.fromJson(valueString, type);
	}
	
	public int getValuesCount() {
		return paraMap.size();
	}
	
	@Override
	public String toString() {
		return gson.toJson(paraMap);
	}	
}

configuration file

RPCConfig.properties

RPCIp = 127.0.0.1
RPCPort = 55555

Client

ClientProxy

The core class of the client, which is used to proxy the interface and get the interface to implement the class object; This RMI framework provides two proxy methods, one of which can be used to reduce the pressure on the server; There are many ways to reduce the load pressure on the server, such as improving server performance, improving server concurrency, distributed servers, etc. But these are the measures taken at the server side.The idea is that we can prevent users from doing wrong things on the client side. For example, login. When users click on the login button for a long time, they may click on the login button frantically. Each click will visit the server to verify that the username and password are correct, which will greatly increase the pressure on the server, causing the opposite result;The method is, when the user clicks on the login button, a prompting modal box pops up to prevent the user from clicking on the login button again. When the login result returns, the modal box can be closed.Because this functionality is not the focus of the RMI framework, the details are not in place. For example, if the server does not respond for a long time, the modal box will not close for a long time; the solution is to use a timer to monitor the response time after the client connects to the server, and force the communication channel to close and the modal box to close when the threshold (configurable) is exceeded.Because this is a simple operation, the blogger does not implement it and the reader can do it on his own if he is interested.

package com.dl.client.core;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

import javax.swing.JFrame;

/**
 * Proxy class <br>
 * 1,In preparation for RPC calls, clients only save interfaces; <br>
 * 2,Call method by getting proxy object through interface; <br>
 * 3,Intercept in method, connect server to make remote method calls when intercepting; <br>
 * 4,Supports opening of modal boxes;
 * 
 * @author dl
 *
 */
public class ClientProxy {
	private RPCClient client;
	
	public ClientProxy() {
	}
	
	public void setClient(RPCClient client) {
		this.client = client;
	}
	
	/**
	 * The interface passed in through the jdk proxy operates remotely in invoke();
	 * @param interfaces Proxyed Target Interface
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T jdkProxy(Class<?> interfaces) {
		ClassLoader classLoader = interfaces.getClassLoader();
		return (T) Proxy.newProxyInstance(classLoader, new Class<?>[]{interfaces}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				return client.remoteProcedureCall(method, args);
			}
		});
	}
	
	/**
	 * Modal Box RMI Connection (Prevents users from clicking more than once)<br>
	 * 1,Open the modal box in invoke() for the interface passed in through the jdk proxy; <br>
	 * 2,Opening a modal box will block the opening thread, so closing the modal box needs to be done in another thread; <br>
	 * 3,The modal box cannot be closed until the server returns the result, so remote operations can only be placed on another thread; <br>
	 * 4,You can use the Callable interface to receive return values from threads (that is, return values from the server);
	 * @param interfaces Proxyed Target Interface
	 * @param parent parent window
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public <T> T jdkProxy(Class<?> interfaces, JFrame parent) {
		ClassLoader classLoader = interfaces.getClassLoader();
		return (T) Proxy.newProxyInstance(classLoader, new Class<?>[]{interfaces}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				ClientDialog dialog = new ClientDialog();
				DialogManager manager = new DialogManager(dialog, method, args);
				FutureTask<Object> future = new FutureTask<>(manager);
				new Thread(future).start();
				dialog.showMessageDialog(parent, "Tips", "Processing, please wait!");
				
				return future.get();
			}
		});
	}
	
	/**
	 * Management class for modal boxes
	 * 1,Implements the Callable interface and accepts return values from thread methods; <br>
	 * 2,Opening a modal box blocks the current thread, so opening and closing modal boxes requires two threads; <br>
	 * 3,Place calls to remote methods in this thread and close the modal box after receiving the results;
	 * 
	 * @author dl
	 *
	 */
	private class DialogManager implements Callable<Object> {
		private ClientDialog dialog;
		private Method method;
		private Object[] args;
		
		public DialogManager(ClientDialog dialog, Method method, Object[] args) {
			this.dialog = dialog;
			this.method = method;
			this.args = args;
		}
		
		@Override
		public Object call() throws Exception {
			Object result = null;
			try {
				result = client.remoteProcedureCall(method, args);
			} finally {
				// No matter what happens, the modal box must be closed
				dialog.close();
			}
			return result;
		}
		
	}
}

ClientDialog

package com.dl.client.core;

import java.awt.BorderLayout;
import java.awt.Dialog;
import java.awt.Frame;

import javax.swing.JLabel;
import javax.swing.JPanel;

import com.swing.util.FontAndColor;

/**
 * Modal Box Interface <br>
 * When the client calls a method, open the modal box to prevent the user from clicking again.
 * 
 * @author dl
 *
 */
public class ClientDialog extends FontAndColor {
	private Dialog dialog;
	
	public ClientDialog() {
	}
	
	public void showMessageDialog(Frame owner, String title, String string) {
		dialog = new Dialog(owner, title, false);
		dialog.setSize(300, 180);
		dialog.setResizable(false);
		dialog.setLocationRelativeTo(owner);
		dialog.setModal(true);
		
		JPanel jpnlDlg = new JPanel(new BorderLayout());
		jpnlDlg.setBackground(backColor);
		jpnlDlg.setVisible(true);
		JLabel jlblDlg = new JLabel(string, JLabel.CENTER);
		jlblDlg.setFont(normalFont);
		jlblDlg.setForeground(fontColor);
		jpnlDlg.add(jlblDlg, BorderLayout.CENTER);
		dialog.add(jpnlDlg);
		dialog.setVisible(true);
	}
	
	public void close() {
		dialog.dispose();
	}
}

RPCClient

package com.dl.client.core;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.Socket;
import java.net.UnknownHostException;

import com.parser_reflect.util.PropertiesParser;
import com.util.ArgumentMaker;

/**
 * RPC Client <br>
 * 1,Provide a way to connect to the server; <br>
 * 2,Supports server ip, port configuration; <br>
 * 3,Provides an interface for load balancing;
 * 
 * @author dl
 *
 */
public class RPCClient {
	public static final String DEFAULT_IP = "127.0.0.1";
	public static final int DEFAULT_PORT = 55555;
	
	private Socket client;
	private DataInputStream dis;
	private DataOutputStream dos;
	private int port;
	private String ip;
	
	private ServerBalance serverBalance;
	
	public RPCClient() {
		this.ip = DEFAULT_IP;
		this.port = DEFAULT_PORT;
		readConfig("/RPCConfig.properties");
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}
	
	/**
	 * Load Balancing Interface
	 * @param serverBalance
	 */
	public void setServerBalance(ServerBalance serverBalance) {
		this.serverBalance = serverBalance;
	}
	
	public void readConfig(String path) {
		PropertiesParser.load(path);
		
		String ip = PropertiesParser.findElement("RPCIp");
		if (ip != null && !ip.equals("")) {
			this.ip = ip;
		}
		String portStr = PropertiesParser.findElement("RPCPort");
		try {
			if (portStr != null && !portStr.equals("")) {
				int port = Integer.valueOf(portStr);
				if (port > 0 && port < 65536) {
					this.port = port;	
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Client connects to server; <br>
	 * If the user has set up a load balancer, the service node after the load balancing algorithm is used.
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	private void startup() throws UnknownHostException, IOException {
		if (serverBalance != null) {
			ServerNetNode node = serverBalance.getServerNode();
			this.ip = node.getIp();
			this.port = node.getPort();
		}
		client = new Socket(ip, port);
		dis = new DataInputStream(client.getInputStream());
		dos = new DataOutputStream(client.getOutputStream());
	}
	
	private void close() {
		try {
			if (!client.isClosed() && client != null) {
				client.close();		
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			client = null;
		}
		try {
			if (dis != null) {
				dis.close();		
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dis = null;
		}
		try {
			if (dos != null) {
				dos.close();	
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dos = null;
		}
	}
	
	/**
	 * RMI Client Core Method <br>
	 * 1,Connect server; <br>
	 * 2,Get the hash value of the calling method and pass it to the server; <br>
	 * 3,Converts the parameter array to a specified string and passes it to the server; <br>
	 * 4,Receive execution results from server-side methods; <br>
	 * 5,Convert the received result to an object and return it; <br>
	 * 6,Close the communication channel and complete an RMI connection;
	 * @param method Method invoked
	 * @param args Array of parameter objects
	 * @return
	 * @throws UnknownHostException
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	public <T> T remoteProcedureCall(Method method, Object[] args) throws UnknownHostException, IOException {
		startup();
		int hashCode = method.toString().hashCode();
		ArgumentMaker maker = new ArgumentMaker();
		try {
			dos.writeUTF(String.valueOf(hashCode));
			if (args == null) {
				dos.writeUTF("");
			} else {
				int index = 0;
				for (Object object : args) {
					maker.addArg("arg" + index++, object);
				}
				dos.writeUTF(maker.toString());
			}
			String str = dis.readUTF();
			T result = (T) ArgumentMaker.gson.fromJson(str, method.getReturnType());
			close();
			return result;
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}

Load Balancing Interface

package com.dl.client.core;

/**
 * Provide an interface for server load balancing
 * 
 * @author dl
 *
 */
public interface ServerBalance {
	ServerNetNode getServerNode();
}
package com.dl.client.core;

/**
 * Node Information Class <br>
 * Prepare for load balancing;
 * 
 * @author dl
 *
 */
public class ServerNetNode {
	private String ip;
	private int port;
	
	public ServerNetNode() {
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}
}

summary

The RMI framework can be understood as a set of service interface definitions and implementations that allow users to use services directly without having to care about the implementation of service logic; it uses short connections without maintaining connections and holding communication channels for a long time, thus saving resources for the server.However, there are pros and cons to anything. Short and long connections have their own advantages, but the scenarios are different. If a client frequently communicates with the server in RMI in a short time, a large amount of time will be wasted due to the three handshakes and four waves of TCP protocol.

Published 16 original articles. Praise 9. Visits 732
Private letter follow

Topics: Java socket network Google