【Java】 简单实现微服务之 RPC RMI 框架

RPC和RMI

RPC(Remote Procedure Call Protocol) 即远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
RMI:远程方法调用(Remote Method Invocation)是一种面向对象的RPC,是RPC在java层面上的一种实现。

前期准备

1.RpcDefinition

当客户端需要进行远程方法调用时,服务器端需要知道该方法存在且可以执行。所以需要一个RpcFactory类来存放这些服务,并且提供注册方法。因为要根据客户端发送的消息来决定方法的执行,所以需要一个PrcDefinition类将类、对象、封装起来。

public class RpcDefinition {
	private Class<?> klass;
	private Object object;
	private Method method;
	}
	
	RpcDefinition() {
	}
	
	RpcDefinition(Class<?> klass, Object object, Method method) {
		this.klass = klass;
		this.object = object;
		this.method = method;
	}

2.RpcFactory

我们将RpcDefinition存在Map中,将它们唯一的id作为键。并且提供注册方法,服务器提供给客户端的服务接口和该接口的实现类是必要的,在此提供三种传参方式注册以方便用户使用。其中Map只需要一个!

首先先判断传进来的interfaces是否为接口,以及klass是否为接口的实现类,否则均抛出异常。
接口的实现类只需要实例化一次即可!

接下来遍历接口中的方法,并生成每个方法唯一的id,并作为Map中的键。为了保证id的绝对唯一性,并且考虑到不同接口中的同名方法,这里的id等于(interfaces.getName() + interfaceMethod.toString()).hashCode()。

最后提供一个根据id查找的方法。

public class RpcFactory {
	private static final Map<Integer, RpcDefinition> rmiMap;
	static {
		rmiMap = new HashMap<>();
	}
	
	public RpcFactory() {
	}
	
	private RpcFactory registry(Class<?> klass, Class<?> interfaces, Object object) throws Exception {
		if (!interfaces.isInterface()) {
			throw new Exception("[" +  interfaces.getName() + "]不是接口!");
		}
		if (!interfaces.isAssignableFrom(klass)) {
			throw new Exception("[" + klass.getName() + "]不是接口[" + interfaces.getName() + "]的实现类");
		}
		Object obj = object == null ? klass.newInstance() : object;
		
		Method[] interfaceMethods = interfaces.getDeclaredMethods();
		for (Method interfaceMethod : interfaceMethods) {
			String methodName = interfaceMethod.getName();
			Class<?>[] paraTypes = interfaceMethod.getParameterTypes();
			int id = (interfaces.getName() + interfaceMethod.toString()).hashCode();
			
			Method method = klass.getMethod(methodName, paraTypes);
			RpcDefinition rmiDefinition = new RpcDefinition();
			rmiDefinition.setKlass(klass);
			rmiDefinition.setObject(obj);
			rmiDefinition.setMethod(method);
			
			rmiMap.put(id, rmiDefinition);
}
		
		return this;
	}
	
	RpcDefinition getRmiDefinition(int id) {
		RpcDefinition rmi = rmiMap.get(id);
		
		return rmi;
	}
	
}

其它两种传参方式:
因为上述方法的实现足够完善,下面两种方式只需调整参数并重新调用上述方法即可,极大地简化了代码编写。

	public RpcFactory registry(Class<?> klass, Class<?> interfaces) throws Exception {
		return registry(klass, interfaces, null);
	}
	
	public RpcFactory registry(Object object, Class<?> interfaces) throws Exception {
		return registry(object.getClass(), interfaces, object);
	}
	

3.RpcInvoker

该类为服务器端调用,真正的方法执行是在这里进行!开启线程侦听客户端,解析客户端发来的json字符串,根据id在RpcFactory中找到对应方法并执行,将结果发送回客户端。

public class RpcInvoker implements Runnable {
	private static final Type type = new TypeToken<Map<String, String>>() {}.getType();
	private Socket socket;
	private DataInputStream dis;
	private DataOutputStream dos;
	private Gson gson;
	
	RpcInvoker(Socket socket, Gson gson) {
		this.gson = gson;
		this.socket = socket;
		try {
			this.dis = new DataInputStream(socket.getInputStream());
			this.dos = new DataOutputStream(socket.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	// json字符串解析
	// 将参数解析成数组paras
	private Object[] getParas(String paraString, Class<?>[] paraType) {
		Map<String, String> paraStringMap = gson.fromJson(paraString, type);
		int paraCount = paraStringMap.size();
		if (paraCount <= 0) {
			return new Object[] {};
		}
		Object[] paras = new Object[paraCount];

		for (int index = 0; index < paraStringMap.size(); index++) {
			String key = "arg" + index;
			String value = paraStringMap.get(key);
			paras[index] = gson.fromJson(value, paraType[index]);
		}
		
		return paras;
	}
	
	@Override
	public void run() {
		try {
			int methodId = dis.readInt();
			String paraString = dis.readUTF();
			System.out.println("接收到的code" + methodId);
			
			RpcDefinition rmiDefinition = new RpcFactory().getRmiDefinition(methodId);
			if (rmiDefinition == null) {
				throw new Exception(methodId + "没有找到与之匹配的方法!");
			}
			Object object = rmiDefinition.getObject();
			Method method = rmiDefinition.getMethod();
			
			Object[] paras = getParas(paraString, method.getParameterTypes());
			Object result = method.invoke(object, paras);
			
			dos.writeUTF(gson.toJson(result));
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

服务器端

RpcServer类:
这个类是对外提供的一个类,实现了Runnable接口,作为服务器,需要有线程来时刻侦听客户端的连接请求,并且添加线程池来控制客户端连接。侦听到线程只需threadPool.execute(new RpcInvoker(client, gson));可见RpcInvoker类的必要性,高内聚。
采用默认端口号54189,也可添加Properties配置文件配置。

public class RpcServer implements Runnable {
	private int port;
	private ServerSocket serverSocket;
	private volatile boolean goon;
	private ThreadPoolExecutor threadPool;
	
	public static final int DEFAULT_PORT = 54189;
	private static final Gson gson = new GsonBuilder().create();
	
	public RpcServer() {
		this.threadPool = new ThreadPoolExecutor(30, 50, 3, TimeUnit.SECONDS, new ArrayBlockingQueue<>(50));
		this.port = DEFAULT_PORT;
	}

	public void initServer(String path) {
		String portString = PropertiesParser.value("RMI.port");
		if (portString == null) {
			try {
				PropertiesParser.loadProperties(path);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		portString = PropertiesParser.value("RMI.port");
		if (portString != null) {
			port = Integer.valueOf(portString);
		}
	}
	
	public void setPort(int port) {
		this.port = port;
	}
	
	public void startup() {
		try {
			serverSocket = new ServerSocket(port);
			goon = true;
			new Thread(this, "RMI-Server").start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void shutdown() {
		goon = false;
		if (!threadPool.isShutdown()) {
			threadPool.shutdown();
		}
		if (serverSocket != null) {
			try {
				if (!serverSocket.isClosed()) {
					serverSocket.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				serverSocket = null;
			}
		}
	}

	@Override
	public void run() {
		while (goon) {
			try {
				Socket client = serverSocket.accept();
				threadPool.execute(new RpcInvoker(client, gson));
			} catch (IOException e) {
				if (goon != false) {
					goon = false;
					e.printStackTrace();
				}
			}
		}
	}
	
}

客户端

客户端在设置了IP和port后,只需将接口、需要调用的方法、参数发送给服务器即可。最后通过getProxy()方法即可得到执行结果。

public class RpcClient {
	private static final Gson gson = new GsonBuilder().create();
	private String rmiIp;
	private int rmiPort;

	public RpcClient() {
	}

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

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

	private String parasToGson(Object[] args) {
		ArgumentMaker am = new ArgumentMaker();
		if (args != null) {
			for (Object arg : args) {
				am.addArg(arg);
			}
		}
		
		
		return am.toString();
	}
	
	private Object invoker(Class<?> interfaces, Method method, Object[] args) {
		Object result = null;
		try {
			Socket socket = new Socket(rmiIp, rmiPort);
			DataInputStream dis = new DataInputStream(socket.getInputStream());
			DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
			
			String id = interfaces.getName() + method.toString();
			
			dos.writeInt(id.hashCode());
			dos.writeUTF(parasToGson(args));
			
			String resString = dis.readUTF();
			result = gson.fromJson(resString, method.getReturnType());
			
			socket.close();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return result ;
	}
	
	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<?> interfaces) {
		return (T) Proxy.newProxyInstance(interfaces.getClassLoader(), 
				new Class<?>[] { interfaces }, new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						return invoker(interfaces, method, args);
					}
				});
	}
	
}

测试

我们只需要写出服务接口,并完成其实现类,通过new RpcServer()和new RpcFactory();并在factory中注册接口和实现类即可!(registry)
示例:

	public static void main(String[] args) {
		RpcServer rmis = new RpcServer();
		RpcFactory rmiFactory = new RpcFactory();
		try {
			rmiFactory.registry(RMIExample.class, IRMIExample.class);
		} catch (Exception e) {
			e.printStackTrace();
		}
		rmis.setPort(54189);
		rmis.startup();
		
		Complex c = new Complex(1.2, 3.4);
		RpcClient client = new RpcClient();
		client.setIp("127.0.0.1");
		client.setPort(54189);
		IRMIExample irm = client.getProxy(IRMIExample.class);
		irm.fun1("WY");
		irm.fun3(12, "abc", c);
		rmis.shutdown();
	}

输出结果:
在这里插入图片描述

优化及改进

后续可将IP和port封装成一个Node类,以及添加之前做过的计时器工具来检测连接超时等等,并且为Dubbo底层的模拟做好准备工作。
可结合AOP以及依赖注入。
在实际应用过程中,客户端除了可做为客户端之外,也可以作为服务器,那么就需要一个注册中心来管理这些服务器,去给子服务器和客户端分配IP和port,以达到后面将要实现的“云”的目的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值