RPC概念
RPC是远程过程调用(Remote Procedure Call)的缩写形式。第三方的客户程序通过接口调用服务器内部的标准或自定义函数,获得函数返回的数据进行处理。
代码设计思想
- 客户端:通过动态代理,将要访问的接口名字、接口中方法的名字、方法中的参数类型和方法中的参数,通过Socket以序列化流(对象流)的方式发送至服务器端或接收服务器端的返回信息。
- 服务端:服务端利用ServerSocket将客户端发送的数据进行接收,并将发送过来的方法名保存在Map中,其中map中的Key=方法名的,Value=方法的实现类的Class类,通过反射将服务器端的“真正要调用的方法”执行,并将返回值发送至客户端。
设计流程图
代码演示
- 调用接口
package indi.dsl.rpcdome;
public interface HelloClient {
public String sayHi(String name);
}
- 调用接口实现
package indi.dsl.rpcdome;
public class HelloClientImpl implements HelloClient{
@Override
public String sayHi(String name) {
return "Hi,"+name;
}
}
- 服务器中服务中心接口
package indi.dsl.rpcdome.server;
public interface ServerCenter {
public void start() throws Exception;
public void stop();
public void register(Class server,Class serverImpl);
}
- 服务器中服务中心接口实例
package indi.dsl.rpcdome.server;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ServerCenterImpl implements ServerCenter {
private static HashMap<String, Class> serverRegister = new HashMap<>();
private static int port;// 端口号
private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
private static boolean isRuning = false;
public ServerCenterImpl(int port) {
this.port = port;
}
// 开启服务端服务
@Override
public void start() throws Exception {
ServerSocket server = null;
try {
server = new ServerSocket();
// 绑定IP和端口
server.bind(new InetSocketAddress(port));
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
// 服务已经启动
isRuning = true;
while (true) {
System.out.println("Server Start.....");
Socket socket = null;
try {
socket = server.accept();
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
// 启动线程
executor.execute(new ServerTask(socket));
}
}
@Override
public void stop() {
// 关闭服务
isRuning = false;
executor.shutdown();
}
@Override
public void register(Class server, Class serverImpl) {
// 将接口名和接口的实现,以KEY,VALUE的形式存储注册
serverRegister.put(server.getName(), serverImpl);
}
private static class ServerTask implements Runnable {
// 因为使同一次客户请求,所以实际调用的socket和线程中的socket一致,
// 将new ServerTask(socket)中传入的socket利用有参构造函数进行传入保持统一
private Socket socket;
public ServerTask() {
}
public ServerTask(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
// TODO Auto-generated method stub
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
// 接受客户端请求数据,并处理该请求数据
input = new ObjectInputStream(socket.getInputStream());
// 因为使用ObjectInputStream对象流(序列化流)接受数据数据有严格的顺序要求,发送顺序和接收顺序应该严格对应
// 接收接口名
String serverName = input.readUTF();
// 接收方法名
String methodName = input.readUTF();
// 接收方法参数类型
Class[] parameterTypes = (Class[]) input.readObject();
// 接收参数
Object[] arguments = (Object[]) input.readObject();
System.out.println("run...");
System.out.println("serverName:" + serverName);
System.out.println("methodName:" + methodName);
System.out.println("parameterTypes:" + parameterTypes);
System.out.println("arguments:" + arguments);
// 根据客户请求,到map中找到与之对应的具体接口
Class serverClass = serverRegister.get(serverName);
// 通过接口对象serverClass和传入的方法名,找到接口中的与之对应的方法
Method method = serverClass.getMethod(methodName, parameterTypes);
// 调用相应方法,使用invoke执行该方法
Object result = method.invoke(serverClass.newInstance(), arguments);
// 将调用方法处理的结果返回给客户端
output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject(result);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
- 客户端代码
package indi.dsl.rpcdome.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 {
// 获取代表服务端接口的动态代理对象(HelloClent)
@SuppressWarnings("unchecked")
// serverInterface : 请求的接口名称
// adr:服务端的IP和端口
public static <T> T getRemoteProxyObj(Class serverInterface, InetSocketAddress adr) {
return (T) Proxy.newProxyInstance(serverInterface.getClassLoader(), new Class<?>[] { serverInterface },
new InvocationHandler() {
// Object proxy:要代理的对象,
// Method method:代理对象的方法名
// Object[] args:代理对象的参数数组
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 客户端向服务端发送数据
Socket socket = new Socket();
ObjectOutputStream output = null;
ObjectInputStream input = null;
try {
// 通过IP和端口连接服务器
socket.connect(adr);
output = new ObjectOutputStream(socket.getOutputStream());
// 发送接口名和方法名
output.writeUTF(serverInterface.getName());
output.writeUTF(method.getName());
// 发送参数类型和参数
output.writeObject(method.getParameterTypes());
output.writeObject(args);
// 等待服务端处理
// 接收服务端返回结果
input = new ObjectInputStream(socket.getInputStream());
// 将服务端结果返回
return input.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}finally {
if(input!=null)input.close();
if(output!=null)output.close();
}
}
});
}
}
- 服务器端测试代码:
package indi.dsl.rpcdome.test;
import indi.dsl.rpcdome.HelloClient;
import indi.dsl.rpcdome.HelloClientImpl;
import indi.dsl.rpcdome.server.ServerCenter;
import indi.dsl.rpcdome.server.ServerCenterImpl;
public class RPCDomeTest {
public static void main(String[] args) {
// 优化:利用线程开启服务端
new Thread(new Runnable() {
@Override
public void run() {
ServerCenter server = null;
try {
server = new ServerCenterImpl(9999);
server.register(HelloClient.class, HelloClientImpl.class);
server.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
- 客户端测试代码:
package indi.dsl.rpcdome.test;
import java.net.InetSocketAddress;
import indi.dsl.rpcdome.HelloClient;
import indi.dsl.rpcdome.client.Client;
public class ClientTest {
public static void main(String[] args) throws ClassNotFoundException {
HelloClient client = Client.getRemoteProxyObj(Class.forName("indi.dsl.rpcdome.HelloClient"),
new InetSocketAddress("127.0.0.1", 9999));
System.out.println(client.sayHi("zs"));
}
}