网上看到纯java实现的RPC,很不错。
RPC的全名Remote Process Call,即远程过程调用。使用RPC,可以像使用本地的程序一样使用远程服务器上的程序。下面是一个简单的RPC 调用实例,从中可以看到RPC如何使用以及好处:
package com.bijian.rpc;
import com.bijian.rpc.op.Echo;
public class MainClient {
public static void main(String[] args) {
Echo echo = RPC.getProxy(Echo.class, "127.0.0.1", 8080);
System.out.println(echo.echo("hello,hello"));
System.out.println(echo.echo("hellow,rod"));
System.out.println(echo.echo("hellow,bijian"));
System.out.println(echo.echo("hellow,rod"));
System.out.println(echo.echo("hellow,rod"));
System.out.println(echo.echo("hellow,rod"));
}
}
package com.bijian.rpc.op;
public interface Echo {
public String echo(String string);
}
使用RPC.getProxy生成接口Echo的代理实现类。然后就可以像使用本地的程序一样来调用Echo中的echo方法。
使用RPC的好处是简化了远程服务访问。提高了开发效率。在分发代码时,只需要将接口分发给客户端使用,在客户端看来只有接口,没有具体类实现。这样保证了代码的可扩展性和安全性。
在看了RPCClient如何使用,我们再来定义一个RPC服务器的接口,看看服务器都提供什么操作:
package com.bijian.rpc.support;
import com.bijian.rpc.protocal.Invocation;
public interface Server {
public void stop();
public void start();
public void register(Class interfaceDefiner, Class impl);
public void call(Invocation invo);
public boolean isRunning();
public int getPort();
}
服务器提供了start和stop方法。使用register注册一个接口和对应的实现类。call方法用于执行Invocation指定的接口的方法名。isRunning返回了服务器的状态,getPort()则返回了服务器使用的端口。
来看看Invocation的定义:
package com.bijian.rpc.protocal;
import java.io.Serializable;
import java.util.Arrays;
public class Invocation implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
private Class interfaces;
private Method method;
private Object[] params;
private Object result;
/**
* @return the result
*/
public Object getResult() {
return result;
}
/**
* @param result the result to set
*/
public void setResult(Object result) {
this.result = result;
}
/**
* @return the interfaces
*/
public Class getInterfaces() {
return interfaces;
}
/**
* @param interfaces the interfaces to set
*/
public void setInterfaces(Class interfaces) {
this.interfaces = interfaces;
}
/**
* @return the method
*/
public Method getMethod() {
return method;
}
/**
* @param method the method to set
*/
public void setMethod(Method method) {
this.method = method;
}
/**
* @return the params
*/
public Object[] getParams() {
return params;
}
/**
* @param params the params to set
*/
public void setParams(Object[] params) {
this.params = params;
}
@Override
public String toString() {
return interfaces.getName() + "." + method.getMethodName() + "(" + Arrays.toString(params) + ")";
}
}
具体服务器实现类中的call方法是这样使用Invocation的:
package com.bijian.rpc;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import com.bijian.rpc.protocal.Invocation;
import com.bijian.rpc.support.Listener;
import com.bijian.rpc.support.Server;
public class RPC {
public static class RPCServer implements Server {
private int port = 8080;
private Listener listener;
private boolean isRuning = true;
/**
* @param isRuning the isRuning to set
*/
public void setRuning(boolean isRuning) {
this.isRuning = isRuning;
}
/**
* @return the port
*/
public int getPort() {
return port;
}
/**
* @param port the port to set
*/
public void setPort(int port) {
this.port = port;
}
private Map<String, Object> serviceEngine = new HashMap<String, Object>();
@Override
public void call(Invocation invo) {
System.out.println(invo.getClass().getName());
Object obj = serviceEngine.get(invo.getInterfaces().getName());
if (obj != null) {
try {
Method m = obj.getClass().getMethod(invo.getMethod().getMethodName(), invo.getMethod().getParams());
Object result = m.invoke(obj, invo.getParams());
invo.setResult(result);
} catch (Throwable th) {
th.printStackTrace();
}
} else {
throw new IllegalArgumentException("has no these class");
}
}
@Override
public void register(Class interfaceDefiner, Class impl) {
try {
this.serviceEngine.put(interfaceDefiner.getName(), impl.newInstance());
System.out.println(serviceEngine);
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
@Override
public void start() {
System.out.println("开始启动服务");
listener = new Listener(this);
this.isRuning = true;
listener.start();
}
@Override
public void stop() {
this.setRuning(false);
}
@Override
public boolean isRunning() {
return isRuning;
}
}
}
下面来看服务器接收连接并处理连接请求的核心代码:
package com.bijian.rpc.support;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import com.bijian.rpc.protocal.Invocation;
public class Listener extends Thread {
private ServerSocket socket;
private Server server;
public Listener(Server server) {
this.server = server;
}
@Override
public void run() {
System.out.println("启动服务器中,打开端口" + server.getPort());
try {
socket = new ServerSocket(server.getPort());
} catch (IOException e1) {
e1.printStackTrace();
return;
}
while (server.isRunning()) {
try {
System.out.println("等待请求");
Socket client = socket.accept();
System.out.println("请求到来");
ObjectInputStream ois = new ObjectInputStream(client.getInputStream());
Invocation invo = (Invocation) ois.readObject();
System.out.println("远程调用:" + invo);
server.call(invo);
ObjectOutputStream oos = new ObjectOutputStream(client.getOutputStream());
oos.writeObject(invo);
oos.flush();
oos.close();
ois.close();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
try {
if (socket != null && !socket.isClosed())
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
服务器端代码搞定后,来看看客户端的代码,先看看我们刚开始使用RPC.getProxy方法:
package com.bijian.rpc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import com.bijian.rpc.protocal.Invocation;
import com.bijian.rpc.support.Client;
public class RPC {
public static <T> T getProxy(final Class<T> clazz, String host, int port) {
final Client client = new Client(host, port);
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Invocation invo = new Invocation();
invo.setInterfaces(clazz);
invo.setMethod(new com.bijian.rpc.protocal.Method(method.getName(), method.getParameterTypes()));
invo.setParams(args);
client.invoke(invo);
return invo.getResult();
}
};
T t = (T) Proxy.newProxyInstance(RPC.class.getClassLoader(), new Class[] { clazz }, handler);
return t;
}
}
Client类的代码如下:
package com.bijian.rpc.support;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import com.bijian.rpc.protocal.Invocation;
public class Client {
private String host;
private int port;
private Socket socket;
private ObjectOutputStream oos;
private ObjectInputStream ois;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public Client(String host, int port) {
this.host = host;
this.port = port;
}
public void init() throws UnknownHostException, IOException {
socket = new Socket(host, port);
oos = new ObjectOutputStream(socket.getOutputStream());
}
public void invoke(Invocation invo) throws UnknownHostException, IOException, ClassNotFoundException {
init();
System.out.println("写入数据");
oos.writeObject(invo);
oos.flush();
ois = new ObjectInputStream(socket.getInputStream());
Invocation result = (Invocation) ois.readObject();
invo.setResult(result.getResult());
}
}
至此,RPC的客户端和服务器端代码完成,启动服务器的代码如下:
package com.bijian.rpc;
import com.bijian.rpc.op.Echo;
import com.bijian.rpc.op.RemoteEcho;
import com.bijian.rpc.support.Server;
public class Main {
public static void main(String[] args) {
Server server = new RPC.RPCServer();
server.register(Echo.class, RemoteEcho.class);
server.start();
}
}
现在先运行服务器端代码,再运行客户端代码,就可以成功运行。
Client运行输出:
写入数据 服务器回应:hello,hello 写入数据 服务器回应:hellow,rod 写入数据 服务器回应:hellow,bijian 写入数据 服务器回应:hellow,rod 写入数据 服务器回应:hellow,rod 写入数据 服务器回应:hellow,rod
Server运行输出:
{com.bijian.rpc.op.Echo=com.bijian.rpc.op.RemoteEcho@1fb8ee3} 开始启动服务 启动服务器中,打开端口8080 等待请求 请求到来 远程调用:com.bijian.rpc.op.Echo.echo([hello,hello]) com.bijian.rpc.protocal.Invocation 等待请求 请求到来 远程调用:com.bijian.rpc.op.Echo.echo([hellow,rod]) com.bijian.rpc.protocal.Invocation 等待请求 请求到来 远程调用:com.bijian.rpc.op.Echo.echo([hellow,bijian]) com.bijian.rpc.protocal.Invocation 等待请求 请求到来 远程调用:com.bijian.rpc.op.Echo.echo([hellow,rod]) com.bijian.rpc.protocal.Invocation 等待请求 请求到来 远程调用:com.bijian.rpc.op.Echo.echo([hellow,rod]) com.bijian.rpc.protocal.Invocation 等待请求 请求到来 远程调用:com.bijian.rpc.op.Echo.echo([hellow,rod]) com.bijian.rpc.protocal.Invocation 等待请求
这个RPC实例,在数据串行化上,使用了java的标准io序列化机制,虽然不能跨平台,但是做DEMO还是不错的;另外在处理客户端请求上,使用了ServerSocket,而没有使用ServerSocketChannel这个java nio中的新特性;在动态生成接口的实现类上,使用了java.lang.reflet中的Proxy类,他可以动态创建接口的实现类。