RPC远程调用原理浅析

1.创建三个maven项目

  • 服务者
  • 消费者
  • API
  • 让服务者和消费者都依赖API
    在这里插入图片描述
    在这里插入图片描述

2.在消费者创建ConsumerApp类

2.1 使用代理对象

具体代码在ProxyUtils中

public class ConsumerApp {
	public static void main(String[] args) {
	    //while死循环是为了测试调用提供者是否为随机
		while (true) {
			try {
				Thread.sleep(2000);
				// 获得代理对象
				AddService addService = ProxyUtils.getProxy(AddService.class);
				// 只要调用方法就会进入代理对象invoke方法
				int result = addService.add(15, 684);
				System.out.println(result);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

		}
	}
}

3.在API中创建Request,AddService,ProxyUtils,ZkUtils

3.1 创建Request(该类为传输对象,必须实现序列化)

public class Request implements Serializable{

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private String interfaceName;

	private String methodName;

	private Object[] args;

	public String getInterfaceName() {
		return interfaceName;
	}

	public void setInterfaceName(String interfaceName) {
		this.interfaceName = interfaceName;
	}

	public String getMethodName() {
		return methodName;
	}

	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}

	public Object[] getArgs() {
		return args;
	}

	public void setArgs(Object[] args) {
		this.args = args;
	}

	@Override
	public String toString() {
		return "Request [interfaceName=" + interfaceName + ", methodName=" + methodName + ", args="
				+ Arrays.toString(args) + "]";
	}

}

3.2 创建AddService

package com.chenlei.service;

public interface AddService {
	public int add(Integer a, Integer b);
}

3.3 创建ProxyUtils(重点)

public class ProxyUtils {

	private static Random RDM = new Random();

	@SuppressWarnings("unchecked")
	public static <T> T getProxy(Class<T> interfaces) {
		T proxy = (T) Proxy.newProxyInstance(ProxyUtils.class.getClassLoader(), new Class<?>[] { interfaces },
				new InvocationHandler() {
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
						String methodName = method.getName();
						if ("toString".equals(methodName)) {
							return interfaces.getClass().getName() + "$Proxy";
						}
						if ("hashCode".equals(methodName)) {
							return Object.class.hashCode();
						}
						if ("equals".equals(methodName)) {
							return Object.class.equals(this);
						}
						// 消费者发送过去
						Request request = new Request();
						request.setInterfaceName(interfaces.getName());
						request.setMethodName(methodName);
						request.setArgs(args);
						// 找到interfaces下的所有节点
						List<String> serverList = ZkUtils.discover(interfaces.getName());
						String one = randomOne(serverList);// 拿到的结果为ip:port 如127.0.0.1:8888
						String[] split = one.split(":");
						String address = split[0];
						Integer port = Integer.valueOf(split[1]);
						Socket socket = null;
						// 打开书出管道,发送请求
						Object result = null;
						OutputStream outputStream = null;
						ObjectOutputStream objectOutputStream = null;
						InputStream inputStream = null;
						ObjectInputStream objectInputStream = null;
						try {
							socket = new Socket(address, port);
							outputStream = socket.getOutputStream();
							objectOutputStream = new ObjectOutputStream(outputStream);
							objectOutputStream.writeObject(request);
							inputStream = socket.getInputStream();
							objectInputStream = new ObjectInputStream(inputStream);
							result = objectInputStream.readObject();
							System.out.println("本次调用的是======" + port);
						} catch (Exception e) {
							e.printStackTrace();
						} finally {
							closeResources(objectInputStream, inputStream, objectOutputStream, outputStream, socket);
						}
						return result;
					}
				});
		return proxy;
	}

	/**
	 * 从节点中随机找出一个
	 * 
	 * @param serverList
	 * @return
	 */
	private static String randomOne(List<String> serverList) {
		if (null == serverList || 0 == serverList.size()) {
			return null;
		}
		int index = RDM.nextInt(serverList.size());
		return serverList.get(index);
	}

	/**
	 * 关闭资源的方法
	 */
	public static void closeResources(Closeable... resources) {
		for (Closeable resource : resources) {
			if (null != resource) {
				try {
					resource.close();
				} catch (IOException e) {
					e.printStackTrace();
				} finally {
					resource = null;
				}
			}
		}
	}
}

3.4 创建ZkUtils(zookeeper注册和发现,另加缓存解决脏读)

3.4.1 在API项目中导入zkclient的依赖
public class ZkUtils {

	private static final String ZK_URL = "自己的域名:2181";

	private static ZkClient zkClient = null;
	
	//创建zookeeper缓存
	private static Map<String, List<String>> cache = new HashMap<String, List<String>>();

	static {
		zkClient = new ZkClient(ZK_URL, 10000, 10000);
	}

	/**
	 * 服务节点向zookeeper的注册
	 * 
	 * @param serverName
	 * @param serverPort
	 */
	public static void register(String serverName, String serverPort) {
		if (null == serverName || "".equals(serverName)) {
			throw new RuntimeException("服务名不能为空");
		}
		if (null == serverPort || "".equals(serverPort)) {
			throw new RuntimeException("服务ip和端口不能为空");
		}
		if (!zkClient.exists("/" + serverName)) {
			zkClient.createPersistent("/" + serverName);
		}
		if (!zkClient.exists("/" + serverName + "/" + serverPort)) {
			zkClient.createEphemeral("/" + serverName + "/" + serverPort);
		}
		System.out.println("注册一个服务节点为" + "/" + serverName + "/" + serverPort);
	}

	/**
	 * 向zookeeper发现服务节点
	 * 
	 * @param serverName
	 * @return
	 */
	public static List<String> discover(String serverName) {
		if (null == serverName || "".equals(serverName)) {
			throw new RuntimeException("服务名不能为空");
		}
		// 先从缓存里找
		if (cache.containsKey(serverName)) {
			System.out.println("在缓存中找到" + serverName + "节点");
		}
		// 如果该节点在zookeeper中不存在,直接返回空
		if (!zkClient.exists("/" + serverName)) {
			return null;
		}
		zkClient.subscribeChildChanges("/" + serverName, new IZkChildListener() {
			@Override
			public void handleChildChange(String parentPath, List<String> currentChilds) throws Exception {
				// 一旦进入此方法,证明有节点改变
				cache.put(serverName, currentChilds);
				System.out.println(serverName + "节点有变化-----" + "缓存完成更新");
			}
		});
		return zkClient.getChildren("/" + serverName);
	}
}

4. 写提供者代码

4.2 创建AddServiceImpl

注意类名最好是AddService+Impl,并且类全路径也要对应com.chenlei.service.impl.AddServiceImpl,否则代码需要调整
package com.chenlei.service.impl;

import com.chenlei.service.AddService;

public class AddServiceImpl implements AddService {

	@Override
	public int add(Integer a, Integer b) {
		return a + b;
	}
}

4.3 创建ProviderApp(重点)

public class ProviderApp {

	public static void main(String[] args) {
		Integer port = 7777;
		ServerSocket serverSocket = bind(port);
		// 向zookeeper注册
		ZkUtils.register(AddService.class.getName(), "127.0.0.1" + ":" + port);
		// 监听+处理请求
		listener(serverSocket);
	}

	/**
	 * 监听和处理请求
	 * 
	 * @param serverSocket
	 */
	private static void listener(ServerSocket serverSocket) {
		//此处死循环是为了让次提供者一直处于工作状态
		while (true) {
			Socket socket = null;
			InputStream inputStream = null;
			ObjectInputStream objectInputStream = null;
			OutputStream outputStream = null;
			ObjectOutputStream objectOutputStream = null;
			try {
				socket = serverSocket.accept();
				inputStream = socket.getInputStream();
				objectInputStream = new ObjectInputStream(inputStream);
				Request request = (Request) objectInputStream.readObject();
				Object answer = invoker(request);
				outputStream = socket.getOutputStream();
				objectOutputStream = new ObjectOutputStream(outputStream);
				objectOutputStream.writeObject(answer);
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				ProxyUtils.closeResources(objectOutputStream, outputStream, objectInputStream, inputStream, socket);
			}
		}
	}

	/**
	 * 处理请求返回结果
	 * 
	 * @param request
	 * @return
	 */
	private static Object invoker(Request request) {
		// 获得从消费者传过来的信息
		String interfaceName = request.getInterfaceName();
		String methodName = request.getMethodName();
		Object[] args = request.getArgs();
		// 获得对应实现类全名
		String className = getClassNameByInterfaceName(interfaceName);
		Object answer = null;
		try {
			// 找到该类
			Class<?> clazz = Class.forName(className);
			// 创建一个对象
			Object object = clazz.newInstance();
			Class<?>[] argsType = new Class<?>[args.length];
			if (null != args || 0 != args.length) {
				for (int i = 0; i < args.length; i++) {
					argsType[i] = args[i].getClass();
				}
			}
			Method method = clazz.getMethod(methodName, argsType);
			answer = method.invoke(object, args);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return answer;
	}

	/**
	 * 通过请求者传来的类信息,获得对应实现类的所有信息,并返回实现类的全名
	 * 
	 * @param interfaceName
	 * @return
	 */
	private static String getClassNameByInterfaceName(String interfaceName) {
		// 传过来的接口名为com.chenlei.service.AddService
		int index = interfaceName.lastIndexOf(".");
		StringBuilder sb = new StringBuilder();
		// com.chenlei.service
		sb.append(interfaceName.subSequence(0, index));
		// com.chenlei.service.impl.
		sb.append(".impl.");
		// com.chenlei.service.impl.AddService
		sb.append(interfaceName.substring(index + 1)).append("Impl");
		return sb.toString();
	}

	/**
	 * 绑定一个端口
	 * 
	 * @param port
	 * @return
	 */
	private static ServerSocket bind(Integer port) {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(port);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return serverSocket;
	}
	/**
	 * 测试代码
	 */
//	public static void main(String[] args) {
//		String interfaceName = "com.chenlei.service.AddService";
//		int index = interfaceName.lastIndexOf(".");
//		StringBuilder sb = new StringBuilder();
//		// com.chenlei.service
//		sb.append(interfaceName.subSequence(0, index));
//		// com.chenlei.service.impl.
//		sb.append(".impl.");
//		// com.chenlei.service.impl.AddService
//		sb.append(interfaceName.substring(index + 1)).append("Impl");
//		System.out.println(sb.toString());
//	}
}

5.更改提供者端口,分别启动三个提供者

在这里插入图片描述

6. 再启动消费者,查看结果

在这里插入图片描述

其他错误和注意事项

在这里插入图片描述
如果出现以上错误就是传输对象没有实现序列化
在这里插入图片描述
其他知识点
在这里插入图片描述
写代码思路
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
提供的源码资源涵盖了安卓应用、小程序、Python应用和Java应用等多个领域,每个领域都包含了丰富的实例和项目。这些源码都是基于各自平台的最新技术和标准编写,确保了在对应环境下能够无缝运行。同时,源码中配备了详细的注释和文档,帮助用户快速理解代码结构和实现逻辑。 适用人群: 这些源码资源特别适合大学生群体。无论你是计算机相关专业的学生,还是对其他领域编程感兴趣的学生,这些资源都能为你提供宝贵的学习和实践机会。通过学习和运行这些源码,你可以掌握各平台开发的基础知识,提升编程能力和项目实战经验。 使用场景及目标: 在学习阶段,你可以利用这些源码资源进行课程实践、课外项目或毕业设计。通过分析和运行源码,你将深入了解各平台开发的技术细节和最佳实践,逐步培养起自己的项目开发和问题解决能力。此外,在求职或创业过程中,具备跨平台开发能力的大学生将更具竞争力。 其他说明: 为了确保源码资源的可运行性和易用性,特别注意了以下几点:首先,每份源码都提供了详细的运行环境和依赖说明,确保用户能够轻松搭建起开发环境;其次,源码中的注释和文档都非常完善,方便用户快速上手和理解代码;最后,我会定期更新这些源码资源,以适应各平台技术的最新发展和市场需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值