RPC
remote procedure call,远程过程调用,顾名思义就是向远程计算机发送请求来获取服务。对于开发人员来说,调用远程服务就像是调用本地服务一样便捷。尤其是在微服务盛行的今天,了解RPC的原理过程是十分有必要的。今天我就来实现一个简单的RPC的功能。
建立三个maven项目
- rpc-sever: 服务端,即服务的提供者
- rpc-client: 客户端,即服务的消费者
- rpc-common: 公共包,提供服务端和客户端依赖的公共包
先编写rpc-common
- 定义一个简单的对象Student
@Data
public class Student implements Serializable {
private String stuName;
private Integer stuAge;
}
上面用到的lombok的注解,可以节省些代码,在maven中引入lombok依赖并在IDE里安装好lombok插件即可。
- 定义一个服务的接口StudentService
public interface StudentService {
String sayHello(String student);
String printStudent(Student student);
}
- 定义一个请求的RpcRequest,来封装请求的服务,以及参数
@Data
@AllArgsConstructor
@NoArgsConstructor
public class RpcRequest implements Serializable {
private String className;
private String methodName;
private Object[] parameters;
}
这些就是rpc-client和rpc-sever依赖的一些公共类,在rpc-client和rpc-sever的pom文件都依赖进去。
再编写rpc-sever
编写服务实现类StudentServiceImpl
public class StudentServiceImpl implements StudentService {
@Override
public String sayHello(String student) {
return "hello," + student;
}
@Override
public String printStudent(Student student) {
return student.toString();
}
}
编写服务的代理类RpcServerProxy,这里用BIO方式,来处理请求。
public class RpcServerProxy {
ExecutorService executorService = Executors.newFixedThreadPool(10);
public void publisher(Object server, int port) {
try (ServerSocket serverSocket = new ServerSocket(port);){
while (true) {
//持续监听请求
Socket socket = serverSocket.accept();
executorService.execute(new ProcessHandler(socket, server));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
这里简单说明一下代码,就是定义一个固定容量的线程池,和一个服务发布的方法,当服务监听对应端口是否有请求进来,如果有,分发一个线程去处理对应的请求,处理者就是ProcessHandler
编写ProcessHandler
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ProcessHandler implements Runnable {
private Socket socket;
private Object service;
@Override
public void run() {
System.out.println("开始处理请求-----------");
try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
RpcRequest request = (RpcRequest) inputStream.readObject();
System.out.println("客户端请求:" + request);
Object result = invoke(request);
try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())){
outputStream.writeObject(result);
outputStream.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
private Object invoke(RpcRequest request) {
Object[] args = request.getParameters();
Class<?>[] types = new Class[args.length];
for (int i = 0; i < args.length; i++) {
types[i] =args[i].getClass();
}
try {
Method method = service.getClass().getMethod(request.getMethodName(),types);
Object result = method.invoke(service, args);
return result;
} catch (Exception e){
e.printStackTrace();
}
return null;
}
}
这个类的作用就是来处理我们服务端收到的每个请求,线程执行run方法,先反序列化请求,即我们封装的RpcRequest,接着调用invoke()方法,通过反射来调用对应的服务的实现方法,将返回结果序列化并冲刷出去并关闭流。
这样一个rpc-sever就写好了,来写一个Main主类,开启我们的服务。
public class Main {
public static void main(String[] args) {
StudentService studentService = new StudentServiceImpl();
RpcServerProxy rpcServerProxy = new RpcServerProxy();
rpcServerProxy.publisher(studentService, 8080);
}
}
主类对外暴露8080端口,提供studentService服务。
最后编写rpc-client
我们也采用代理的方式,编写一个客户端代理类RpcClientProxy
public class RpcClientProxy {
public <T> T clientProxy(Class<T> interfaceCls, String host, Integer port) {
return (T) Proxy.newProxyInstance(interfaceCls.getClassLoader(),
new Class<?>[] {interfaceCls}, new ClientInvocationHandler(host, port));
}
}
编写我们的ClientInvocationHandler
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ClientInvocationHandler implements InvocationHandler {
private String host;
private Integer port;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest request = new RpcRequest();
request.setMethodName(method.getName());
request.setParameters(args);
RpcTransport rpcTransport = new RpcTransport(host, port);
return rpcTransport.sendRequest(request);
}
}
这里就主要是封装参数为一个RpcRequest,通过rpcTransport.sendRequest()方法,来将我们的请求发送出去。
@Data
@AllArgsConstructor
public class RpcTransport {
private String host;
private Integer port;
public Object sendRequest(RpcRequest request) {
try (Socket socket = new Socket(host, port)) {
//建立连接
System.out.println("建立一个连接-------");
try (ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream())) {
outputStream.writeObject(request);
outputStream.flush();
try (ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream())) {
Object result = inputStream.readObject();
return result;
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
RpcTransport完成的功能很简单,就是把我们的request发送出去,并读取返回并反序列为我们想要的结果。
这样rpc-client也编写完了,接下来编写客户端的Main主类,
public class Main {
public static void main(String[] args) {
RpcClientProxy rpcClientProxy = new RpcClientProxy();
StudentService studentService = rpcClientProxy.clientProxy(StudentService.class, "localhost", 8080);
String result = studentService.sayHello("小张");
System.out.println("返回的结果:" + result);
Student student = new Student("小张", 23);
String result2 = studentService.printStudent(student);
System.out.println("返回的结果:" + result2);
}
}
好了,来测试一下,先启动我们的服务提供方rpc-sever,再启动我们的服务的消费方rpc-client;
来看一下控制台打印的结果:
rpc-sever:
rpc-client:
这下对rpc的过程应该有所了解了吧,本次示例主要利用的java的序列化技术,以及jdk的动态代理来实现的,实际工作中,往往都会使用现成的RPC框架,比如dubbo,motan,grpc等。