MyRPC版本一
上一篇博客的地址:https://blog.csdn.net/qq_41115379/article/details/118281642
下一篇博客的地址:https://blog.csdn.net/qq_41115379/article/details/119153197
本节问题
1.让客户端请求远程方法能支持多种
2.让服务端返回值的类型多样
总结知识
Serializable
https://www.jianshu.com/p/2e83ab8210dc
他是一个对象序列化的接口,一个类只有实现了Serializable接口,对象才能被序列化。而什么是序列化,怎么体现实现了序列化呢。
序列化含义
将对象的状态信息转换为可以存储或传输的形式的过程,在序列化期间,对象将其当前状态写入到临时存储区或持久性存储区,之后便可以通过从存储区中读取或反序列化对象的状态信息来重新创建该对象。序列化就是将数据分解成字节流,以便存储在文件中或在网络中传输,反序列化就是打开字节流并重构对象
反射的知识
//这边更新的就是 接收解析request请求和封装response对象
//读取客户端传过来的request 这里是调用的ois的这个方法 可以再看看
RPCRequest rpcRequest= (RPCRequest) ois.readObject();
//再对这个request进行解析吧 反射调用对应方法
Method method=userService.getClass().getMethod(rpcRequest.getMethodName(),rpcRequest.getParamsTypes());
Object invoke=method.invoke(userService,rpcRequest.getParams());
//然后封装 写入response对象
oos.writeObject(RPCResponse.success(invoke));
反射中的 method getClass() getMethod和invoke
Method就相当于是定义了一个反射的方法
getClass()就是通过反射获得实例的类型类(代表一个类型的类)
getMethod 返回类或接口的特定方法,并且这个方法是需要用public修饰的
method.invoke(obj,args[]) 就是调用对应的方法,其中obj是对象名,args是参数
对应源码
对应参数就是 name方法名和参数类型列表
返回一个 符合method名称和参数的method对象
@CallerSensitive
public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException {
Objects.requireNonNull(name);
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
}
Method method = getMethod0(name, parameterTypes);
if (method == null) {
throw new NoSuchMethodException(methodToString(name, parameterTypes));
}
return getReflectionFactory().copyMethod(method);
}
动态代理的知识
RPCRequest rpcRequest=RPCRequest.builder().interfaceName(method.getDeclaringClass().getName()).methodName(method.getName()
).params(args).paramsTypes(method.getParameterTypes()).build();
method.getDeclaringClass().getName()是找到接口的name
其中getDeclaringClass():如果该类或接口是另一个类的成员,则此方法返回该类的声明类,否则此方法返回null.底层成员的类的对象
定义一个通用的Request对象(消息格式)
package RPCVersion01;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
* request只发送了一个id参数传过去,不合理
* 因为服务端不会只有一个服务和一个方法,所以只传一个参数过去是不会知道调用哪个方法的
* 因此一个RPC请求中,client发送的应该是需要调用的Service接口名,方法名,参数和参数类型
* 这样服务器就能根据这些信息根据反射调用相应的方法
* 使用到了Java自带的序列化方法
*/
@Data
@Builder
public class RPCRequest implements Serializable{
//Serializable是一个序列化的接口,一个类只有实现了Serializable接口,他的对象才能被序列化
//服务类名 客户端只知道接口名,在服务端中用接口名指向实现类
private String interfaceName;
//方法名
private String methodName;
//参数列表
private Object[] params;
//参数类型
private Class<?>[] paramsTypes;
}
定义一个通用的Responese对象(消息格式)
package RPCVersion01;
import lombok.Builder;
import lombok.Data;
import java.io.Serializable;
/**
*之前的例子中 response传输的是 User对象,但在一个应用中不可能只传输一种类型的数据
* 因此我们将传输对象抽象变成Object
* RPC需要经过网络传输,有可能会失败,类似于http,引入状态码和状态信息来表示服务调用成功还是失败
*/
@Data
@Builder
public class RPCResponse implements Serializable {
//状态信息
private int code;
private String message;
//具体数据
private Object data;
//并且定义两个方法
//调用成功
public static RPCResponse success(Object data){
//然后返回一个 通过builder生成的数值
return RPCResponse.builder().code(200).data(data).build();
}
//调用失败
public static RPCResponse fail(){
//调用失败没有参数
return RPCResponse.builder().code(500).message("服务器发生错误").build();
}
}
这样在传输过程当中,就是request和response格式的数据了,客户端和服务端就要负责封装与解析以上结构数据
UserService
package RPCVersion01;
/**
* 定义客户端需要调用,服务端需要提供对应的服务接口
*/
public interface UserService {
//客户端提供这个接口调用服务端的实现类
User getUserByUserId(Integer id);
//服务端接收request请求,调用request中对应的方法 可以不仅仅传输的是id了
Integer insertUserId(User user);
}
UserServiceImpl
package RPCVersion01;
import java.util.Random;
import java.util.UUID;
/**
* 服务端实现service接口
*/
public class UserServiceImpl implements UserService {
@Override
public User getUserByUserId(Integer id) {
System.out.println("客户端查询了"+id+"的用户");
//模拟从数据库中取用户的行为
Random random=new Random();
//这里就调用了那个builder方法
User user=User.builder().userName(UUID.randomUUID().toString()).id(id).sex(random.nextBoolean()).build();
return user;
}
//新写的功能
@Override
public Integer insertUserId(User user) {
System.out.println("插入数据成功"+user);
return 1;
}
}
RPCServer
package RPCVersion01;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服务端以BIO的方式监听Socket
* 如果有数据的话就调用对应服务的实现类来执行任务
* 将结果返回给客户端
*/
public class RPCServer {
public static void main(String[] args) {
UserServiceImpl userService=new UserServiceImpl();
try{
//这边是和Socket进行对应连接,得到对应的接口
//这个参数就是创建到绑定固定端口的套接字 而且这个是默认到50端口的
ServerSocket serverSocket=new ServerSocket(8899);
System.out.println("服务端启动了");
//使用BIO的方式 监听Socket
while (true){
//这边使用accept方法进行接收数据
Socket socket=serverSocket.accept();
//开启一个线程去处理
new Thread(()->{
try{
ObjectInputStream ois=new ObjectInputStream(socket.getInputStream());
ObjectOutputStream oos=new ObjectOutputStream(socket.getOutputStream());
//读取客户端这边传过来的id
/* Integer id=ois.readInt();
User userByUserId=userService.getUserByUserId(id);
//写入User对象给客户端
oos.writeObject(userByUserId);*/
//这边更新的就是 接收解析request请求和封装response对象
//读取客户端传过来的request 这里是调用的ois的这个方法 可以再看看
RPCRequest rpcRequest= (RPCRequest) ois.readObject();
//再对这个request进行解析吧 反射调用对应方法
//这个就相当于是找到这个方法
Method method=userService.getClass().getMethod(rpcRequest.getMethodName(),rpcRequest.getParamsTypes());
//这个应该是用来执行方法的吧
Object invoke=method.invoke(userService,rpcRequest.getParams());
//然后封装 写入response对象
oos.writeObject(RPCResponse.success(invoke));
oos.flush();
}catch (Exception e){
e.printStackTrace();
System.out.println("从IO中读取数据错误");
}
}).start();
}
} catch (IOException e) {
//创建ServerSocket产生的异常
e.printStackTrace();
System.out.println("服务器启动失败");
}
}
}
动态代理
客户端根据不同的service进行动态代理,代理对象增强的公共行为:把不同的service方法封装成统一的request对象格式,并建立和server的通信
package RPCVersion01;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
/**
* 这里负责底层和服务端的通信,发送的是request,接收的是response对象
* 客户端发起一次请求调用,Socket建立连接,发起请求Request,得到响应 Response
* request是封装好的(上层进行封装),不同的service需要进行不同的封装,客户端只知道service接口
* 需要一层动态代理根据反射封装不同的service
*/
public class IOClient {
public static RPCResponse sendRequest(String host,int port,RPCRequest request) {
//通过request来封装response
try {
Socket socket = new Socket(host, port);
ObjectOutputStream objectOutputStream=new ObjectOutputStream(socket.getOutputStream());
ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream());
//输出请求
System.out.println(request);
//再把请求写入
objectOutputStream.writeObject(request);
objectOutputStream.flush();
//读取回应
RPCResponse rpcResponse= (RPCResponse) objectInputStream.readObject();
return rpcResponse;
}catch (Exception e){
e.printStackTrace();
}
}
}
封装request对象
package RPCVersion01;
import lombok.AllArgsConstructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@AllArgsConstructor
public class ClientProxy implements InvocationHandler {
//传入参数Service接口的class对象,反射封装成一个request
private String host;
private int port;
//jdk动态代理,每一次代理对象调用方法,会经过此方法增强(反射获取request对象,socket发送到客户端)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//request的构建使用了Lombok中的builder方法
RPCRequest rpcRequest=RPCRequest.builder().interfaceName(method.getDeclaringClass().getName()).methodName(method.getName()
).params(args).paramsTypes(method.getParameterTypes()).build();
//数据传输 调用那一个静态方法
RPCResponse response=IOClient.sendRequest(host,port,rpcRequest);
//返回数据
return response.getData();
}
<T>T getProxy(Class<T> clazz){
//this指的是 ClientProxy这边这个
Object o= Proxy.newProxyInstance(clazz.getClassLoader(),new Class[]{clazz},this);
return (T) o;
}
}
客户端调用不同的方法
package RPCVersion01;
public class RPCClientProxy {
public static void main(String[] args) {
//不知道 host和port是不是可以随便写
//是我自己傻了 把127写成了 107
ClientProxy clientProxy=new ClientProxy("127.0.0.1",8899);
UserService proxy=clientProxy.getProxy(UserService.class);
//服务方法1
User userByUserId=proxy.getUserByUserId(10);
System.out.println("从服务端得到的user为"+userByUserId);
//服务的方法2
User user=User.builder().userName("张三").id(100).sex(true).build();
Integer integer=proxy.insertUserId(user);
System.out.println("向服务器插入的数据为"+integer);
}
}
emmm这个代码没跑成功,需要去解决一下
修改了这个地方,但需要解决一下。。。
修改了一下 代码运行成功
总结
定义了更加通用的消息格式,request和response,从此可能调用不同的方法和返回各种类型的数据
使用动态代理进行不同服务方法的request的封装
客户端更加松耦合,不在于特定的service,host,port绑定
存在的痛点
服务端现在直接绑定的是UserService服务,如果还有其他服务接口暴露呢
服务端以BOI的方式性能是否太低
服务端功能太复杂,监听,处理,需要松耦合