RPC(Remote Procedure Call)

远程方法调用,这只是个统称,重点在于方法调用

RPC只是个概念,不是什么框架,协议,只是说远程调用的一种方式,是多种概念中的一种

从单机到分布式—》需要使用分布式通信—》最基本的传输:二进制数据传输TCP/IP


User:实体类,存放用户信息

IUserService:业务接口

UserServiceImpl:业务实现类

Client:客户

Server:服务

客户与服务关系:服务间的方法调用

xxxOutputStream与xxxInputStream的注意点

先放入的先取,不然会乱

客户端

Socket s = new Socket(“127.0.0.1”, 8888);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
oos.writeUTF(xxx); <—
oos.writeObject(yyy); <—

服务端

InputStream in = s.getInputStream();
ObjectInputStream ois = new ObjectInputStream(in);
xxx = ois.readUTF(); <—
yyy = (Class[])ois.readObject(); <—


公共的

/**
 * 业务接口
 */
public interface IUserService {
    public User findUserById(Integer id);
}
/**
 * 实体类
 */
@Setter@Getter
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class User implements Serializable {
    private static  final  long serialVersionUID = 1L;

    private Integer id;
    private String name;
}

第一个版本

/**
 * 客户
 */
public class Client {
    public static void main(String[] args) throws IOException {
        // 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
        Socket s = new Socket("127.0.0.1", 8888);
        System.out.println("时间:" + System.currentTimeMillis() +"  连接服务端");

        // 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 将Java基本数据类型写到底层输出流
        DataOutputStream dos = new DataOutputStream(baos);
        // 往缓冲区写入数据
        dos.writeInt(123);

        // 把字节缓冲区字节数组写出去
        s.getOutputStream().write(baos.toByteArray());
        s.getOutputStream().flush();

        System.out.println("时间:" + System.currentTimeMillis() +"  数据已发送到服务端");

        // 获取返回的字节
        DataInputStream dis = new DataInputStream(s.getInputStream());
        int id = dis.readInt();
        String name = dis.readUTF();
        System.out.println("时间:" + System.currentTimeMillis() +"  从服务端返回结果");
        User user = new User(id, name);

        System.out.println("时间:" + System.currentTimeMillis() +"  " + user);

        dos.close();
        s.close();
    }
}
/**
 * 服务
 */
public class Server {
    private static boolean running = true;

    /**
     * 服务器建立通信,监听连接
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        // 服务器建立通信ServerSocket
        ServerSocket ss = new ServerSocket(8888);
        while (running){
            // 监听访问服务器的客户端,返回Socket对象(客户端)
            Socket s = ss.accept();
            System.out.println("时间:" + System.currentTimeMillis() +"  客户端访问");
            process(s);
            s.close();
        }
    }

    /**
     * 获取客户端发送过来的二进制id,转成java数据类型,
     * 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
     * @param s
     * @throws IOException
     */
    private static void process(Socket s) throws IOException {
        // 获取客户端的字节输入流
        InputStream in = s.getInputStream();
        // 获取客户端的字节输出流
        OutputStream out = s.getOutputStream();
        // 从底层输入流中读取基本 Java 数据类型
        DataInputStream dis = new DataInputStream(in);
        // 将 Java 基本数据类型写入底层输出流
        DataOutputStream dos = new DataOutputStream(out);

        // 获取Int类型的数据
        int id = dis.readInt();
        IUserService service = new UserServiceImpl();
        // 模拟调用业务方法,根据id查询用户信息
        User user = service.findUserById(id);

        System.out.println("时间:" + System.currentTimeMillis() +"  查询的结果:" + user);

        System.out.println("时间:" + System.currentTimeMillis() +"  将信息返回给用户端");
        // 把信息写入字节输出流中,用于往客户端传输
        dos.writeInt(user.getId());
        dos.writeUTF(user.getName());
        // 用于刷新此流,并强制将字节写入基本流
        dos.flush();
    }
}
/**
 * 服务,相当于service层
 */
public class UserServiceImpl implements IUserService {
    public User findUserById(Integer id) {
        // 模拟从数据库中查询数据
        return new User(id,"hao");
    }
}

Client

时间:1611390201028  连接服务端
时间:1611390201057  数据已发送到服务端
时间:1611390201091  从服务端拿到结果
时间:1611390201092  User(id=123, name=hao)

Server

时间:1611390201028  客户端访问
时间:1611390201059  查询的结构:User(id=123, name=hao)
时间:1611390201090  将信息返回给用户端

第二个版本

把客户那边的代码抽取出来,放到Stub类中,用于屏蔽网络细节

其余代码不变

/**
 * 客户
 */
public class Client {
    public static void main(String[] args) throws IOException {
        Stub stub = new Stub();
        User user = stub.findUserById(123);
        System.out.println("时间:" + System.currentTimeMillis() +"  " + user);
    }
}
/**
 * 代理类,RPC最最最最最初期
 */
public class Stub {
    public User findUserById(Integer id) throws IOException {
        // 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
        Socket s = new Socket("127.0.0.1", 8888);
        System.out.println("时间:" + System.currentTimeMillis() +"  连接服务端");

        // 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 将Java基本数据类型写到底层输出流
        DataOutputStream dos = new DataOutputStream(baos);
        // 往缓冲区写入数据
        dos.writeInt(123);

        // 把字节缓冲区字节数组写出去
        s.getOutputStream().write(baos.toByteArray());
        s.getOutputStream().flush();

        System.out.println("时间:" + System.currentTimeMillis() +"  数据已发送到服务端");

        // 获取返回的字节
        DataInputStream dis = new DataInputStream(s.getInputStream());
        int receivedId = dis.readInt();
        String name = dis.readUTF();
        System.out.println("时间:" + System.currentTimeMillis() +"  从服务端拿到结果");
        User user = new User(receivedId, name);

        dos.close();
        s.close();
        return user;
    }
}

Client

时间:1611391197261  连接服务端
时间:1611391197292  数据已发送到服务端
时间:1611391197321  从服务端拿到结果
时间:1611391197323  User(id=123, name=hao)

Server

时间:1611391197260  客户端访问
时间:1611391197293  查询的结构:User(id=123, name=hao)
时间:1611391197320  将信息返回给用户端

第三个版本

把Stub类改造成JDK动态代理,修改客户调用代码,其余代码不变

/**
 * 客户
 */
public class Client {
    public static void main(String[] args) {
        IUserService service = Stub.getStub();
        User user = service.findUserById(123);
        System.out.println("时间:" + System.currentTimeMillis() +"  " + user);
    }
}
/**
 * JDK动态代理
 */
public class Stub {
    public static IUserService getStub(){
        InvocationHandler h = new InvocationHandler() {
            /**
             * @param proxy:代理对象是谁
             * @param method:被调用了哪个方法
             * @param args:传过来的参数是什么
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
                Socket s = new Socket("127.0.0.1", 8888);
                System.out.println("时间:" + System.currentTimeMillis() +"  连接服务端");

                // 字节数组输出流在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组缓冲区中
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                // 将Java基本数据类型写到底层输出流
                DataOutputStream dos = new DataOutputStream(baos);
                // 往缓冲区写入数据
                dos.writeInt(123);

                // 把字节缓冲区字节数组写出去
                s.getOutputStream().write(baos.toByteArray());
                s.getOutputStream().flush();

                System.out.println("时间:" + System.currentTimeMillis() +"  数据已发送到服务端");

                // 获取返回的字节
                DataInputStream dis = new DataInputStream(s.getInputStream());
                int receivedId = dis.readInt();
                String name = dis.readUTF();
                System.out.println("时间:" + System.currentTimeMillis() +"  从服务端拿到结果");
                User user = new User(receivedId, name);

                dos.close();
                s.close();
                return user;
            }
        };
        /**
         * 参数一:加载代理类的类加载器
         * 参数二:被代理类实现的接口
         * 参数三:InvocationHandler
         */
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        return (IUserService)o;
    }
}

Client

时间:1611393079568  连接服务端
时间:1611393079599  数据已发送到服务端
时间:1611393079629  从服务端拿到结果
时间:1611393079630  User(id=123, name=hao)

Server

时间:1611393079569  客户端访问
时间:1611393079600  查询的结构:User(id=123, name=hao)
时间:1611393079628  将信息返回给用户端

第四个版本

修改了动态代理中代码,使用反射获取方法信息,实现动态请求方法和传递参数

修改Stub、Server代码,其余不变

/**
 * JDK动态代理
 */
public class Stub {
    public static IUserService getStub(){
        InvocationHandler h = new InvocationHandler() {
            /**
             * @param proxy:代理对象是谁
             * @param method:被调用了哪个方法
             * @param args:传过来的参数是什么
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
                Socket s = new Socket("127.0.0.1", 8888);
                System.out.println("时间:" + System.currentTimeMillis() +"  连接服务端");

                // 该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作
                ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());

                // 获取被调用方法的名字
                String methodName = method.getName();
                // 获取被调用方法的参数类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                System.out.println("时间:" + System.currentTimeMillis() +"  发送数据");
                oos.writeUTF(methodName);
                oos.writeObject(parameterTypes);
                oos.writeObject(args);
                oos.flush();

                // 获取返回的字节
                DataInputStream dis = new DataInputStream(s.getInputStream());
                int receivedId = dis.readInt();
                String name = dis.readUTF();
                System.out.println("时间:" + System.currentTimeMillis() +"  从服务端拿到结果");
                User user = new User(receivedId, name);

                oos.close();
                s.close();
                return user;
            }
        };
        /**
         * 参数一:加载代理类的类加载器
         * 参数二:被代理类实现的接口
         * 参数三:InvocationHandler
         */
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        return (IUserService)o;
    }
}
/**
 * 服务
 */
public class Server {
    private static boolean running = true;

    /**
     * 服务器建立通信,监听连接
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        // 服务器建立通信ServerSocket
        ServerSocket ss = new ServerSocket(8888);
        while (running){
            // 监听访问服务器的客户端,返回Socket对象(客户端)
            Socket s = ss.accept();
            System.out.println("时间:" + System.currentTimeMillis() +"  客户端访问");
            process(s);
            s.close();
        }
    }

    /**
     * 获取客户端发送过来的二进制id,转成java数据类型,
     * 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
     * @param s
     * @throws IOException
     */
    private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 获取客户端的字节输入流
        InputStream in = s.getInputStream();
        // 获取客户端的字节输出流
        OutputStream out = s.getOutputStream();
        // 从底层输入流中读取 Java 对象类型
        ObjectInputStream oos = new ObjectInputStream(in);
        // 将 Java 基本数据类型写入底层输出流
        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);

        System.out.println("时间:" + System.currentTimeMillis() +"  查询的结果:" + user);

        System.out.println("时间:" + System.currentTimeMillis() +"  将信息返回给用户端");
        // 把信息写入字节输出流中,用于往客户端传输
        dos.writeInt(user.getId());
        dos.writeUTF(user.getName());
        // 用于刷新此流,并强制将字节写入基本流
        dos.flush();
    }
}

Client

时间:1611395269591  连接服务端
时间:1611395269625  发送数据
时间:1611395269685  从服务端拿到结果
时间:1611395269686  User(id=123, name=hao)

Server

时间:1611395269591  客户端访问
时间:1611395269656  查询的结构:User(id=123, name=hao)
时间:1611395269685  将信息返回给用户端

第五个版本

Stub从服务中,从ObjectInputStream中获取返回值

Server服务中,往ObjectOutputStream中获放入返回值

其余代码不变

/**
 * JDK动态代理
 */
public class Stub {
    public static IUserService getStub(){
        InvocationHandler h = new InvocationHandler() {
            /**
             * @param proxy:代理对象是谁
             * @param method:被调用了哪个方法
             * @param args:传过来的参数是什么
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
                Socket s = new Socket("127.0.0.1", 8888);
                System.out.println("时间:" + System.currentTimeMillis() +"  连接服务端");

                // 该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作
                ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());

                // 获取被调用方法的名字
                String methodName = method.getName();
                // 获取被调用方法的参数类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                System.out.println("时间:" + System.currentTimeMillis() +"  发送数据");
                oos.writeUTF(methodName);
                oos.writeObject(parameterTypes);
                oos.writeObject(args);
                oos.flush();

                // 获取返回的字节
                ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
                User user = (User) ois.readObject();
                System.out.println("时间:" + System.currentTimeMillis() +"  从服务端拿到结果");

                oos.close();
                s.close();
                return user;
            }
        };
        /**
         * 参数一:加载代理类的类加载器
         * 参数二:被代理类实现的接口
         * 参数三:InvocationHandler
         */
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        return (IUserService)o;
    }
}
/**
 * 服务
 */
public class Server {
    private static boolean running = true;

    /**
     * 服务器建立通信,监听连接
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        // 服务器建立通信ServerSocket
        ServerSocket ss = new ServerSocket(8888);
        while (running){
            // 监听访问服务器的客户端,返回Socket对象(客户端)
            Socket s = ss.accept();
            System.out.println("时间:" + System.currentTimeMillis() +"  客户端访问");
            process(s);
            s.close();
        }
    }

    /**
     * 获取客户端发送过来的二进制id,转成java数据类型,
     * 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
     * @param s
     * @throws IOException
     */
    private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 获取客户端的字节输入流
        InputStream in = s.getInputStream();
        // 获取客户端的字节输出流
        OutputStream out = s.getOutputStream();
        // 从底层输入流中读取 Java 对象类型
        ObjectInputStream ois = new ObjectInputStream(in);
        // 将 Java 基本数据类型写入底层输出流
        DataOutputStream dos = new DataOutputStream(out);

        // 获取被调用方法的名字
        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);

        System.out.println("时间:" + System.currentTimeMillis() +"  查询的结果:" + user);

        System.out.println("时间:" + System.currentTimeMillis() +"  将信息返回给用户端");
        // 把信息写入字节输出流中,用于往客户端传输
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(user);
        // 用于刷新此流,并强制将字节写入基本流
        dos.flush();
    }
}

Client

时间:1611405600697  连接服务端
时间:1611405600727  发送数据
时间:1611405600788  从服务端拿到结果
时间:1611405600788  User(id=123, name=hao)

Server

时间:1611405600698  客户端访问
时间:1611405600751  查询的结构:User(id=123, name=hao)
时间:1611405600784  将信息返回给用户端

第六个版本

完成这就是RPC的一个小小实现了

修改为调用指定类的指定方法

  1. 客户传入字节码对象,Stub中根据对象获取类名称(服务名)
  2. 在Server中根据服务名,在服务注册表找到具体的类
  3. 调用类中的方法
/**
 * 客户
 */
public class Client {
    public static void main(String[] args) {
        IUserService service = (IUserService) Stub.getStub(IUserService.class);
        User user = service.findUserById(123);
        System.out.println("时间:" + System.currentTimeMillis() +"  " + user);
    }
}
/**
 * JDK动态代理
 */
public class Stub {
    public static Object getStub(Class clazz){
        InvocationHandler h = new InvocationHandler() {
            /**
             * @param proxy:代理对象是谁
             * @param method:被调用了哪个方法
             * @param args:传过来的参数是什么
             * @return
             * @throws Throwable
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 在这里主机是指远程机器名称或者IP地址,端口是指远程应用的端口号
                Socket s = new Socket("127.0.0.1", 8888);
                System.out.println("时间:" + System.currentTimeMillis() +"  连接服务端");

                // 该流可以将一个对象写出,或者读取一个对象到程序中,也就是执行了序列化和反序列化操作
                ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());

                // 获取class的名字
                String clazzName = clazz.getName();
                // 获取被调用方法的名字
                String methodName = method.getName();
                // 获取被调用方法的参数类型
                Class<?>[] parameterTypes = method.getParameterTypes();
                System.out.println("时间:" + System.currentTimeMillis() +"  发送数据");
                oos.writeUTF(clazzName);
                oos.writeUTF(methodName);
                oos.writeObject(parameterTypes);
                oos.writeObject(args);
                oos.flush();

                // 获取返回的字节
                ObjectInputStream ois = new ObjectInputStream(s.getInputStream());
                User user = (User) ois.readObject();
                System.out.println("时间:" + System.currentTimeMillis() +"  从服务端拿到结果");

                oos.close();
                s.close();
                return user;
            }
        };
        /**
         * 参数一:加载代理类的类加载器
         * 参数二:被代理类实现的接口
         * 参数三:InvocationHandler
         */
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(),new Class[]{IUserService.class},h);
        return (IUserService)o;
    }
}
/**
 * 服务
 */
public class Server {
    private static boolean running = true;

    /**
     * 服务器建立通信,监听连接
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        // 服务器建立通信ServerSocket
        ServerSocket ss = new ServerSocket(8888);
        while (running){
            // 监听访问服务器的客户端,返回Socket对象(客户端)
            Socket s = ss.accept();
            System.out.println("时间:" + System.currentTimeMillis() +"  客户端访问");
            process(s);
            s.close();
        }
    }

    /**
     * 获取客户端发送过来的二进制id,转成java数据类型,
     * 根据id查询用户信息,把用户信息转成二进制数据,往客户端发送
     * @param s
     * @throws IOException
     */
    private static void process(Socket s) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
        // 获取客户端的字节输入流
        InputStream in = s.getInputStream();
        // 获取客户端的字节输出流
        OutputStream out = s.getOutputStream();
        // 从底层输入流中读取 Java 对象类型
        ObjectInputStream ois = new ObjectInputStream(in);
        // 将 Java 基本数据类型写入底层输出流
        DataOutputStream dos = new DataOutputStream(out);

        // 获取clazzName
        String clazzName = ois.readUTF();
        // 获取被调用方法的名字
        String methodName = ois.readUTF();
        // 获取被调用方法的参数类型集合
        Class[] parameterTypes = (Class[])ois.readObject();
        // 获取被调用方法的参数集合
        Object[] args = (Object[]) ois.readObject();

        Class clazz = null;
        // 根据服务名字(clazzName),从服务注册表找到具体的类
        // 模拟找到
        clazz = UserServiceImpl.class;

        // 根据名字,参数类型获取具体的方法对象
        Method method = clazz.getMethod(methodName,parameterTypes);
        // 调用方法,传入代理类对象,具体参数
        Object o = (Object) method.invoke(clazz.newInstance(),args);

        System.out.println("时间:" + System.currentTimeMillis() +"  查询的结果:" + o);

        System.out.println("时间:" + System.currentTimeMillis() +"  将信息返回给用户端");
        // 把信息写入字节输出流中,用于往客户端传输
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(o);
        // 用于刷新此流,并强制将字节写入基本流
        dos.flush();
    }
}

Client

时间:1611408464339  连接服务端
时间:1611408464381  发送数据
时间:1611408464447  从服务端拿到结果
时间:1611408464448  User(id=123, name=hao)

Server

时间:1611408464340  客户端访问
时间:1611408464409  查询的结果:User(id=123, name=hao)
时间:1611408464440  将信息返回给用户端

第七个版本

序列化、网络协议