1. Java的网络通信Socket和IO,忘记的赶紧去百度一个基于Socket的聊天的Demo,就差不多了。为什么要学习Socket和IO呢?因为很大部分RPC框架底层就是基于Socke进行通信的,所以他是必不可少的知识点;
2. Java的JDK动态代理知识;
3. Java的序列化知识,简单理解,就是把java对象进行序列化用于网络传输,使得机器和机器之间方便进行数据通信的。
4. Java的反射基础知识;
什么是RPC?
RPC(Remote Procedure Call Protocol)—— 远程过程调用协议,它是一种通过网络请求从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。
为什么需要RPC?
因为近年来,随着分布式和微服务技术的发展,把单个服务程序按照功能拆分成多个模块,并把每个模块部署到多台机器上,多个模块之间相互配合提供服务。而多个机器之间相互配合需要进行通信,而如果直接写原生的Socket代码进行通信会造成代码冗余、资源浪费等。因此就产生了RPC框架,而RPC框架就是为机器之间进行通信服务的,让开发者不必关心底层实现,只需要按照指定的即可。
作为程序员要做的不仅是学会如何使用,而且还要理解它的底层原理,这样才成为一位合格的开发者。
Dubbo、Thrift、gRpc等。
本章是,自定义一个简单的RPC,主要的功能是实现远程接口服务调用,至于一些其他的功能不会实现。本章主要讲的是调用的原理,知道了原理就大致了解了大部分RPC框架的是如何通信工作的。只需要知道调用的原理即可,其他的细节的东西不会多说,有些兴趣的话可以留言或者自己查看。
一个简单的RPC调用过程,大致流程:
最简单的理解是,客户端封装一个服务端对象(包含类名,方法名,参数)通过socket传递给服务端,服务端拿到这个对象进行反射执行该类的方法,然后得到结果,在通过socket返回给客服端这个过程,就是一个rpc通信的基本过程。其他框架的也都是在此基础上进行封装,提供更加丰富的功能。下面是代码的实现过程:
首先建立一个服务端A,创建一个maven工程simple-rpc-server,然后在simple-rpc-server下创建两个子模块simple-rpc-api、simple-rpc-provider。
simple-rpc-provider是为服务端私有的,不会发布出去,他的作用是simple-rpc-api接口的具体实现。
public class User implements Serializable {
private Integer uid;//uid
private String username;//用户名
private String password;//密码
private String name;//名称
private Integer age;//年龄
public Integer getUid() {
return uid;
}
public void setUid(Integer uid) {
this.uid = uid;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
2、RpcRequest这个类是最主要的,也是核心,作用是客户端把要请求的api信息,包装成RpcRequest对象,通过socket发送给服务端,然后服务端对对象进行解析,通过反射调用服务端的实现方法,在把执行的结果封装通过socket返回给客户端,这就完成了Rpc的远程调用。也是这个类的作用所在。并实现序列化接口
public class RpcRequest implements Serializable {
//类的全路径
private String className;
//方法的名称
private String methodName;
//方法的参数
private Object[] params;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
}
3、RpcResponse这个类,为服务端对象RcRequest解析执行,得到的结果包装成RpcResponse对象返回给客户端。
public class RpcResponse implements Serializable {
private Integer code;
private String message;
private Object result;
public RpcResponse(Integer code, String message, Object result) {
this.code = code;
this.message = message;
this.result = result;
}
public static RpcResponse success(Object result){
return new RpcResponse(200, "结果正确", result);
}
public static RpcResponse failed(String message){
return new RpcResponse(500, message, null);
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
}
4、UserService这个接口就是simple-rpc-server这个服务需要提供的功能,有保存user类和根据uid获取到user类
public interface UserService {
public void saveUser(User user);
public User getUserByUid(Integer uid);
}
simple-rpc-provider是simple-rpc-api的实现,主要用于发布服务,用于接收服务端提交过来的RcRequest参数,然后通过反射调用实现方法进行执行,得到返回结果,封装并返回。该模块要引用simple-rpc-api的引用,对他的接口进行实现。该过程对应的是上图的的蓝色箭头。
1、UserServiceImpl为simple-rpc-api接口UserService的实现
public class UserServiceImpl implements UserService {
//模拟数据库功能,进行存取
private static Map<Integer, User> userMap = new HashMap();
static {
User user1 = new User();
user1.setUid(10);
user1.setName("zhangsan");
user1.setAge(32);
user1.setPassword("OOSDIAIDSI123213");
user1.setUsername("41122400");
userMap.put(user1.getUid(), user1);
User user2 = new User();
user2.setUid(12);
user2.setName("lisi");
user2.setAge(22);
user2.setPassword("OSVASDASDASC112");
user2.setUsername("41122400");
userMap.put(user2.getUid(), user2);
}
@Override
public void saveUser(User user) {
//保存操作
userMap.put(user.getUid(), user);
System.out.println("数据库中的数据:");
for (User dbUser : userMap.values()) {
System.out.println(dbUser);
}
}
@Override
public User getUserByUid(Integer uid) {
return userMap.get(uid);
}
}
2、ProxtService为服务端的代理层(参照模型图),主要用于发布服务,并处理来自客户端的请求,并响应结果。底层是基于Socket实现的。
public class ProxyService {
//创建一个线程池
private static ExecutorService executorService = new ThreadPoolExecutor(5, 10, 3, TimeUnit.MINUTES, new ArrayBlockingQueue<>(10));
public static void publish(int port, Object instance){
System.out.println("RPC服务器 已启动, 端口为:" + port);
try {
ServerSocket serverSocket = new ServerSocket(port);
while (true){
//socket为客户端的一个引用,到该方法会连接阻塞
Socket socket = serverSocket.accept();
//使用线程池来处理请求,避免阻塞
executorService.execute(new RpcHandler(socket, instance));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
3、RpcHandler是用来做真正的业务处理的
public class RpcHandler implements Runnable {
private Socket socket;
private Object instance;//暴露哪一个实例
public RpcHandler(Socket socket, Object instance) {
this.socket = socket;
this.instance = instance;
}
@Override
public void run() {
ObjectInputStream objectInputStream = null;
ObjectOutputStream objectOutputStream = null;
try {
//获取到客户端的输入,其实就是RpcRequest对象
objectInputStream = new ObjectInputStream(socket.getInputStream());
RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();
//把rpcReuqest交给MethodHandler方法处理,通过反射调用执行,并得到返回结果
RpcResponse rpcResponse = MethodHandler.process(instance, rpcRequest);
//获取到输出流,把返回结果写出到客户端
objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
objectOutputStream.writeObject(rpcResponse);
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
if(objectInputStream != null){
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(objectOutputStream != null){
try {
objectOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
4、MethodHandler是用来进行对RpcRequest进行分析,通过反射调用对象的实现方法,本例中是UserServiceImpl类中的方法,并返回结果,对其进行包装RpcRequest。
public class MethodHandler {
public static RpcResponse process(Object instance, RpcRequest rpcRequest){
//获取到类的全路径
String className = rpcRequest.getClassName();
//获取参数的类信息,用于指定方法获取
Object[] params = rpcRequest.getParams();
Class[] paramsType = null;
if(params != null || params.length != 0){
paramsType = new Class[params.length];
for (int i = 0; i < params.length; i++) {
paramsType[i] = params[i].getClass();
}
}
try {
Class<?> clazz = Class.forName(className);
Method method = clazz.getMethod(rpcRequest.getMethodName(), paramsType);
Object result = method.invoke(instance, params);
return RpcResponse.success(result);
} catch (ClassNotFoundException e) {
return RpcResponse.failed("类不存在");
} catch (NoSuchMethodException e) {
return RpcResponse.failed("方法不存在");
} catch (IllegalAccessException e) {
return RpcResponse.failed("访问异常");
} catch (InvocationTargetException e) {
return RpcResponse.failed("调用方法失败");
}
}
}
5、App类为我们的启动测试类,用于发布服务:
public class AppService {
public static void main(String[] args) {
//要发布服务端实例
UserService userService = new UserServiceImpl();
ProxyService.publish(8080, userService);
}
}
到这来我们的服务端,以及底层的原理基本以及完成了,当启动的时候会在连接的时候一直阻塞,直到有客户端连接服务端,服务端才开始工作。
可以在另一台电脑上或者同一个电脑上建立一个客户端A,创建一个maven工程simple-rpc-client,然后在simple-rpc-server下创建一个子模块simple-rpc-consumer。
simple-rpc-consumer服务主要是对simple-rpc-api里面的接口进行代理,封装RpcRequest对象通过socket传递给服务端,让服务端解析对象并执行,在得到结果,返回给客户端。
并且记得引入simple-rpc-api在仓库中的引用位置。
1、ProxyService主要对接口进行代理
public class ProxyService {
private int port;
private String url;
public ProxyService(String url, int port) {
this.port = port;
this.url = url;
}
//根据传递的接口类型信息,进行代理
public Object proxy(Class<?> clazz){
//得到代理的对象
Object obj;
obj = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{clazz}, new RpcHandler(url, port));
return obj;
}
}
2、RpcHandler为具体执行方法,当代用接口的getUserById方法时,会执行该类的invoke方法,封装对象传递给服务端执行,并返回结果。
public class RpcHandler implements InvocationHandler {
private String url;
private int port;
public RpcHandler(String url, int port) {
this.url = url;
this.port = port;
}
//为UserService的代理对象,当带哦用UserService的任何方法时,都会调用invoke这个方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//只需要封装好RpcRequest对象,传给服务端即可
RpcRequest rpcRequest = new RpcRequest();
rpcRequest.setClassName(method.getDeclaringClass().getName());
rpcRequest.setMethodName(method.getName());
rpcRequest.setParams(args);
//端口写死,可以自己做修改
Socket socket = new Socket(url, port);
//获取到输出流,把rpcRequest对象传到服务端,让服务端进行解析
ObjectOutputStream os = new ObjectOutputStream(socket.getOutputStream());
os.writeObject(rpcRequest);
//获取到输入流,从服务器端获取到执行的结果,并对其进行解析
ObjectInputStream is = new ObjectInputStream(socket.getInputStream());
RpcResponse rpcResponse = (RpcResponse) is.readObject();
if(rpcResponse.getCode() == 200){
return rpcResponse.getResult();
}
return null;
}
}
到这里客户端A就已经写完。
public class AppClient {
public static void main(String[] args) {
//创建一个代理对象,对接口进行代理
ProxyService proxyService = new ProxyService("localhost", 8080);
//获取代理对象,当调用方法的时候,其实是执行代理对象的invoke方法
UserService userService = (UserService) proxyService.proxy(UserService.class);
//获取id为10的用户
User user = userService.getUserByUid(10);
System.out.println(user);
User user1 = new User();
user1.setUid(1001);
user1.setName("rpc Cient");
//执行保存操作
userService.saveUser(user1);
}
}
先启动服务端,在启动客户端,会得到一下执行结果:
客服端:
服务端:
到这里就完成了rpc的远程调用。可以参照之前的图在此对比一边,只是一个简单的rpc调用过程,只需要知道他大致是如何实现的,一些其他功能可自己进行扩展。