远程调用其实没有想象地那么复杂,当然,性能好的RPC当然需要仔细设计,那么最基础的理解什么是RPC呢?从本DEMO看起:
暴露的服务:等待远程来调用。客户端在调用前需要来绑定生成个代理,类似于动态代理模式
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.ServerSocket; import java.net.Socket; /** * RpcFramework * * @author william.liangf */ public class RpcFramework { /** * 暴露服务 * * @param service 服务实现 * @param port 服务端口 * @throws Exception */ public static void export(final Object service, int port) throws Exception { if (service == null) throw new IllegalArgumentException("service instance == null"); if (port <= 0 || port > 65535) throw new IllegalArgumentException("Invalid port " + port); System.out.println("Export service " + service.getClass().getName() + " on port " + port); ServerSocket server = new ServerSocket(port); for(;;) { try { final Socket socket = server.accept(); new Thread(new Runnable() { @Override public void run() { try { try { ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); try { String methodName = input.readUTF(); Class<?>[] parameterTypes = (Class<?>[])input.readObject(); Object[] arguments = (Object[])input.readObject(); ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); try { Method method = service.getClass().getMethod(methodName, parameterTypes); Object result = method.invoke(service, arguments); output.writeObject(result); } catch (Throwable t) { output.writeObject(t); } finally { output.close(); } } finally { input.close(); } } finally { socket.close(); } } catch (Exception e) { e.printStackTrace(); } } }).start(); } catch (Exception e) { e.printStackTrace(); } } } /** * 引用服务 * * @param <T> 接口泛型 * @param interfaceClass 接口类型 * @param host 服务器主机名 * @param port 服务器端口 * @return 远程服务 * @throws Exception */ @SuppressWarnings("unchecked") public static <T> T refer(final Class<T> interfaceClass, final String host, final int port) throws Exception { if (interfaceClass == null) throw new IllegalArgumentException("Interface class == null"); if (! interfaceClass.isInterface()) throw new IllegalArgumentException("The " + interfaceClass.getName() + " must be interface class!"); if (host == null || host.length() == 0) throw new IllegalArgumentException("Host == null!"); if (port <= 0 || port > 65535) throw new IllegalArgumentException("Invalid port " + port); System.out.println("Get remote service " + interfaceClass.getName() + " from server " + host + ":" + port); return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class<?>[] {interfaceClass}, new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable { Socket socket = new Socket(host, port); try { ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream()); try { output.writeUTF(method.getName()); output.writeObject(method.getParameterTypes()); output.writeObject(arguments); ObjectInputStream input = new ObjectInputStream(socket.getInputStream()); try { Object result = input.readObject(); if (result instanceof Throwable) { throw (Throwable) result; } return result; } finally { input.close(); } } finally { output.close(); } } finally { socket.close(); } } }); } }
调用的接口:
/** * HelloService * * @author william.liangf */ public interface HelloService { String hello(String name); String cry(String name,int minutes); }
接口的实现:
/** * HelloServiceImpl * * @author william.liangf */ public class HelloServiceImpl implements HelloService { public String hello(String name) { return "Hello " + name; } @Override public String cry(String name,int minutes) { // TODO Auto-generated method stub return "Cry " + name+"minutes"+minutes; } }
消费者:
/** * RpcConsumer * * @author william.liangf */ public class RpcConsumer { public static void main(String[] args) throws Exception { HelloService service = RpcFramework.refer(HelloService.class, "127.0.0.1", 1234); for (int i = 0; i < Integer.MAX_VALUE; i ++) { String hello = service.hello("World" + i); System.out.println(hello); if (i==2||i==1) { String Cry = service.cry("CryCryCry" + i,i); System.out.println(Cry); } Thread.sleep(1000); } } }
提供者:
/** * RpcProvider * * @author william.liangf */ public class RpcProvider { public static void main(String[] args) throws Exception { HelloService service = new HelloServiceImpl(); RpcFramework.export(service, 1234); } }
从代码中可以看出,该RPC调用的核心是SOCKET通信。
然后往Socket里写Object,轻松理解RPC
------------------------------------------------------------------------我是分割线
那么对比下另外一种远程调用:rmi
服务的接口
import java.rmi.Remote; import java.rmi.RemoteException; public interface AddServer extends Remote { public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException; }
服务的实现
public class AddServerImpl extends UnicastRemoteObject implements AddServer { public AddServerImpl() throws RemoteException { super(); } public int AddNumbers(int firstnumber,int secondnumber) throws RemoteException { return firstnumber + secondnumber; } }
远程RMI的具体服务:
import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class RmiServer { /** * 启动 RMI 注册服务并进行对象注册 */ public static void main(String[] argv) { try { //启动RMI注册服务,指定端口为1099 (1099为默认端口) //也可以通过命令 $java_home/bin/rmiregistry 1099启动 //这里用这种方式避免了再打开一个DOS窗口 //而且用命令rmiregistry启动注册服务还必须事先用RMIC生成一个占位程序(stub类)为它所用 LocateRegistry.createRegistry(1099); //创建远程对象的一个或多个实例,下面是hello对象 //可以用不同名字注册不同的实例 AddServer add = new AddServerImpl(); //把hello注册到RMI注册服务器上,命名为Hello Naming.rebind("Hello", add); //如果要把hello实例注册到另一台启动了RMI注册服务的机器上 //Naming.rebind("//192.168.1.105:1099/Hello",hello); System.out.println("Hello Server is ready."); } catch (Exception e) { System.out.println("Hello Server failed: " + e); } } }
客户端:
import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.RemoteException; import com.gjy.rmi.service.AddServer; public class RmiClient { public static void main(String args[]) throws RemoteException, MalformedURLException, NotBoundException { String url="rmi://127.0.0.1/Hello"; AddServer add; add = (AddServer)Naming.lookup(url); int result=0; for (int i =0;i<10;i++){ result = add.AddNumbers(10,i); System.out.println(result); } } }
从以上可看出rmi的编码效率远大于socket的形式,节约了非常多的代码。下一遍文章会分析其他的远程调用并对比性能