RPC理解
本文是根据B站马士兵所做的学习笔记。
RPC ( Remote Procedure Call ):远程方法调用,它所关注的是分布式通信问题。
一、代码解释
三大组件:Client端、Stub端、Server端
Server端有相应服务对应的接口和实现类,例如IUserService接口和UserServiceImpl实现类
public interface IUserService {
public User findUserById(Integer id);
}
public class UserServiceImpl implements IUserService {
public User findUserById(Integer id) {
return new User(id,"huangying");
}
}
1.最原始方式:二进制传递
客户端:
public class Client {
public static void main(String[] args) throws IOException {
Socket s = new Socket("127.0.0.1", 8888);
//客户端将消息写出去
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(123);//将123转化为二进制格式
s.getOutputStream().write(baos.toByteArray());
s.getOutputStream().flush();
//客户端接收消息
DataInputStream dis = new DataInputStream(s.getInputStream());
int id = dis.readInt();
String name=dis.readUTF();
User user = new User(id, name);
System.out.println(user);
dos.close();
s.close();
}
}
服务端:
public class Server {
private static boolean running=true;
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);//服务端对接口进行监听
while(running){
Socket s = ss.accept();
process(s);
s.close();
}
ss.close();
}
private static void process(Socket s) throws IOException {
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
DataInputStream dis = new DataInputStream(in);
DataOutputStream dos = new DataOutputStream(out);
//服务端接收消息
int id = dis.readInt();
UserServiceImpl service = new UserServiceImpl();
User user = service.findUserById(id);
//服务端向客户端发送消息
dos.writeInt(user.getId());
dos.writeUTF(user.getName());
dos.flush();
}
}
缺点:这是一种最原始的通信方式,client必须知道传输对象的一切细节;当对象属性改变时代码就要变;传输代码和业务代码混合在一起。
2.引入Stub代理
客户端:
public class Client {
public static void main(String[] args) throws IOException {
Stub stub = new Stub();
System.out.println(stub.findUserById(123));
}
}
Stub代理:
public class Stub {
public User findUserById(Integer id) throws IOException {
Socket s = new Socket("127.0.0.1", 8888);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(123);
s.getOutputStream().write(baos.toByteArray());
s.getOutputStream().flush();
DataInputStream dis = new DataInputStream(s.getInputStream());
int receiveId = dis.readInt();
String name=dis.readUTF();
User user = new User(receiveId, name);
dos.close();
s.close();
return user;
}
}
优点:引入Stub代理,向用户提供方法,从而隐藏网络传输和业务操作的细节,简化了客户端的操作流程。
缺点:只能代理一个方法:findUserById(),返回的对象仅是User类。 随着业务增多,Stub中会堆积很多的函数。
3.引入动态代理
客户端:
public class Client {
public static void main(String[] args) {
IUserService service = Stub.getStub();//不是通过new生成对象,而是生成动态代理对象
System.out.println(service.findUserById(123));//通过动态代理对象调用方法时,实际上是调用invoke方法
}
}
服务端:
public class Stub {
public static IUserService getStub(){
//对调用的方法进行泛化
InvocationHandler h=new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket s = new Socket("127.0.0.1", 8888);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream dos = new DataOutputStream(baos);
dos.writeInt(123);
s.getOutputStream().write(baos.toByteArray());
s.getOutputStream().flush();
DataInputStream dis = new DataInputStream(s.getInputStream());
int id = dis.readInt();
String name = dis.readUTF();
User user = new User(id, name);
dos.close();
s.close();
return user;
}
};
//动态代理对象需要和被代理对象实现同一个接口IUserService
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
return (IUserService)o;
}
}
优点:在2中随着业务增多,Stub中会堆积很多的函数。通过引入动态代理,外部使用返回的动态代理对象去调用某个功能时,内部就会调用invoke方法,在Stub中不用写每一个业务函数的细节。
缺点:仅是粗略引入动态代理,在Stub中方法参数是写死的:dos.writeInt(123);,可以对方法进行进一步泛化。
4.动态代理:进一步泛化
Stub端
public class Stub {
public static IUserService getStub(){
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket s = new Socket("127.0.0.1", 8888);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
oos.writeUTF(methodName);//Stub向服务端发送调用方法的方法名
oos.writeObject(parameterTypes);//Stub向服务端发送调用方法的参数类型(受重载影响)
oos.writeObject(args);//Stub向服务端发送调用方法的具体参数
oos.flush();
DataInputStream dis = new DataInputStream(s.getInputStream());
int id = dis.readInt();
String name = dis.readUTF();
User user = new User(id, name);
oos.close();
s.close();
return user;
}
};
//生成动态代理对象:它与被代理对象实现同一个接口,因此需要的参数有接口的类加载器、接口的类信息
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
//返回动态代理对象
return (IUserService)o;
}
}
Service端
public class Service {
private static boolean running=true;
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);//服务端对接口进行监听
while(running){
Socket s = ss.accept();
}
}
private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
ObjectInputStream oos = new ObjectInputStream(in);
DataOutputStream dos = new DataOutputStream(out);
String methodName = oos.readUTF();//读取客户端调用的方法名
Class[] parameterTypes=(Class[])oos.readObject();//读取客户端调用的方法的参数类型
Object[] args=(Object[])oos.readObject();//读取客户端调用的方法的参数
IUserService service = new UserServiceImpl();
Method method = service.getClass().getMethod(methodName, parameterTypes);//获得对应的方法
User user = (User) method.invoke(service, args);
dos.writeInt(user.getId());
dos.writeUTF(user.getName());
dos.flush();
}
}
优点:Stub端不是向服务端写死参数,而是告知服务端客户的请求(调用方法的名字和参数类型),服务端处理请求,向客户端发送结果。
缺点:服务端分别向客户端发送对象的各个属性,当对象的属性改变时,代码也要相应改变。
5.直接写/读对象
Stub端
public class Stub {
public static IUserService getStub(){
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket s = new Socket("127.0.0.1", 8888);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
oos.writeUTF(methodName);
oos.writeObject(parameterTypes);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
User user = (User)ois.readObject();//直接从二进制流中读入一个User对象
oos.close();
s.close();
return user;
}
};
Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
return (IUserService)o;
}
}
Service端
public class Service {
private static boolean running=true;
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);//服务端对接口进行监听
while(running){
Socket s = ss.accept();
}
}
private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
ObjectInputStream ois = new ObjectInputStream(in);
String methodName = ois.readUTF();
Class[] parameterTypes=(Class[])ois.readObject();
Object[] args=(Object[])ois.readObject();
IUserService service = new UserServiceImpl();
Method method = service.getClass().getMethod(methodName, parameterTypes);
User user = (User) method.invoke(service, args);
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(user);//直接写出对象
oos.flush();
}
}
优点:客户端直接读到对象,服务端直接写出对象。
缺点:
6.对方法接口进行泛化
Client端
public class Client {
public static void main(String[] args) {
//对接口进行泛化,返回实现该接口的动态代理类对象
IUserService service = (IUserService) Stub.getStub(IUserService.class);
System.out.println(service.findUserById(123));
}
}
Stub端
public class Stub {
public static Object getStub(final Class clazz){
InvocationHandler h=new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket s = new Socket("127.0.0.1", 8888);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
String clazzName = clazz.getName();//写出接口的名字
String methodName = method.getName();
Class[] parameterTypes = method.getParameterTypes();
oos.writeUTF(clazzName);
oos.writeUTF(methodName);
oos.writeObject(parameterTypes);
oos.writeObject(args);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
Object o = ois.readObject();
oos.close();
s.close();
return o;
}
};
//生成clazz接口对应的动态对象
Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, h);
return o;
}
}
Service端
public class Service {
private static boolean running=true;
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8888);//服务端对接口进行监听
while(running){
Socket s = ss.accept();
}
}
private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
InputStream in = s.getInputStream();
OutputStream out = s.getOutputStream();
ObjectInputStream ois = new ObjectInputStream(in);
String clazzName=ois.readUTF();
String methodName = ois.readUTF();
Class[] parameterTypes=(Class[])ois.readObject();
Object[] args=(Object[])ois.readObject();
//根据claaName找到具体的实现类,可以根据Spring注入来解决
Class clazz=null;
clazz=UserServiceImpl.class;
Method method = clazz.getMethod(methodName, parameterTypes);
Object o = (Object) method.invoke(clazz.newInstance(), args);
ObjectOutputStream oos = new ObjectOutputStream(out);
oos.writeObject(o);
oos.flush();
}
}
优点:对服务端的服务接口进行泛化,使得它能产生相应服务接口的动态代理对象,所以传入的参数为接口的class对象。