RPC
一、概述:Remote Procedure Call 远程过程调用,简单的理解是一个节点请求另一个节点提供的服务,即一台客户端向另外一台服务器请求某个方法,调用该方法。
二、设计思路:将服务端待调用的方法以map集合的形式,注册到服务注册中心,服务注册中心通过ServerSocket创建连接对象,设置IP和端口号,客户端再通过Socket创建连接对象和动态代理的方式,即可建立和服务器的连接,并可以调用放置到注册中心中的接口进行传参,再将传完参数的接口执行返回值序列化成对象返回给客户端;
三、服务端:
(1)编写待调用的接口和接口实现类
package org.example.server; public interface HelloService { void say(String s); }
package org.example.server; public class HelloServiceImpl implements HelloService{ @Override public void say(String s) { System.out.println(s); } }
(2)编写服务注册中心
package org.example.server; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.HashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ServerCenter implements Server{ private static HashMap<String,Class> serviceRegister = new HashMap(); private static final int port = 9999; //连接池中存在多个连接对象,每个连接对象都可以处理一个客户端请求 private static ExecutorService executor= Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); @Override public void start() throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException { //思考?如何实现并发连接 ServerSocket server = new ServerSocket(); //该构造器可填两个参数,一个是IP地址,一个是端口号,因为是本机测试所以用.bind绑定端口号就可以 server.bind(new InetSocketAddress(port)); System.out.println("只有while true 时多线程!!!"); while (true){ System.out.println("server start....."); Socket socket = server.accept(); //监听客户端的访问,等待连接(当使用while true调用多线程时,我也是很懵逼,这样死循环不会导致死机吗?但是后面了解到server.accept()会在这里等待客户端连接,起到阻塞的作用,只有当某个客户端再次发起连接后才会继续往下执行下一个while) executor.execute(new ServiceTask(socket)); } } @Override public void register(Class Service, Class serviceImpl) { serviceRegister.put(Service.getName(),serviceImpl); } private static class ServiceTask implements Runnable{ private Socket socket; public ServiceTask(Socket socket){ this.socket = socket; } @Override public void run() { //接收到客户端的连接和请求 try {
//将从客户端发送过来的请求序列化成一个对象 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); String serviceName = input.readUTF(); String methodName = input.readUTF(); Class[] parameterTypes = (Class[]) input.readObject(); Object[] arguments = (Object[]) input.readObject(); Class ServiceClass = serviceRegister.get(serviceName); Method method = ServiceClass.getMethod(methodName, parameterTypes); Object result = method.invoke(ServiceClass.newInstance(), arguments); //将方法执行完毕的返回值序列化后发送给客户端 ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); output.writeObject(result); } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } }
package org.example.server; import java.io.IOException; import java.lang.reflect.InvocationTargetException; public class ServerStart { public static void main(String[] args) throws NoSuchMethodException, IOException, InstantiationException, IllegalAccessException, InvocationTargetException, ClassNotFoundException { //可能有多个客户端进行连接,采用多线程创建对象方式,在ServerCenter注册中心中采用Executors.newFixedThreadPool对象创建线程池,
//再创建一个匿名内部类ServiceTask实现Runnable接口创建线程,线程的特点:当run方法一旦执行结束,则该线程结束,所以需要while(true) 来重复创建线程,但.accept()方法会使while(true)阻塞,只有当客户端连接之后,才会继续创建线程,如此巧妙
new Thread( () -> { Server server = new ServerCenter(); server.register(HelloService.class,HelloServiceImpl.class); try { server.start(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } ).start(); } }
四、客户端:可以创建多个客户端进行连接,测试并发连接是否成功
package org.example.client; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.InetSocketAddress; import java.net.Socket; public class Client { //获取代表服务端接口的动态代理对象(HelloService) //serviceInterface:请求接口的名字 //addr:待请求的服务端Ip和端口号 @SuppressWarnings("unchecked") //将检查异常压制,不会报黄线 public static <T> T getRemoteProxyObj(Class serviceInterface, InetSocketAddress addr) { //a参数:需要代理哪个接口(HelloService接口),就需要将HelloService类加载器传入到第一个参数 //b参数:需要代理哪个对象,具备哪些方法 //c参数:传入handler是将该方法重写后的 return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface}, new InvocationHandler() { //参数a:需要代理的对象 //参数b:需要代理的对象的方法 //参数c:需要代理对象的方法的参数 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //客户端通过socket向服务端发送请求 //发送:通过OutPutStream //接受:通过InPutStream Socket socket = new Socket(); socket.connect(addr); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());//发送:序列化流 //接口名称、方法名称、方法参数类型、方法的参数 output.writeUTF(serviceInterface.getName()); output.writeUTF(method.getName()); output.writeObject(method.getParameterTypes()); output.writeObject(args); //接受服务端处理后的返回值 ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); return input.readObject(); } }); } }
package org.example.client; import org.example.server.HelloService; import java.net.InetSocketAddress; public class ClientStart { public static void main(String[] args) throws ClassNotFoundException {
//参数a:要调用的接口名称;参数b:要连接的服务端ip地址和端口 HelloService service = Client.getRemoteProxyObj(Class.forName("org.example.server.HelloService"),new InetSocketAddress("127.0.0.1",9999)); service.say("我是客户端1,我连接成功!"); } }
程序运行结果: