手写一个简易版本的RPC

前言

在1024程序员节前夕,学习技术是对节日最好的庆祝。

手写一个简易版的RPC,可以把模糊抽象的概念具像化,属于落地层面了。

1. RPC基本原理

RPC原理

 2. 四个版本的迭代

注:api表示服务端和客户端都有的包,server表示服务端专有的包,client表示客户端专有的包。

后一个版本在前一个版本基础上改进。

(1)版本一:根据商品id列表查询商品列表

// api
@Data
public class Product implements Serializable {
    private final static long serialVersionUID = 8552999810899286984L;
    private int id;
    private String desc;
    private double price;

    public Product() {

    }

    public Product(int id, String desc, double price) {
        this.id = id;
        this.desc = desc;
        this.price = price;
    }

    @Override
    public String toString() {
        return "Product{" +
                "id=" + id +
                ", desc='" + desc + '\'' +
                ", price=" + price +
                '}';
    }
}
// api

public interface ProductListService {

    List<Product> getProductList(List<Integer> ids);
}
// server
public class ProductListServiceImpl implements ProductListService {
    Map<Integer, Product> productMap = new HashMap<>();
    public ProductListServiceImpl() {
        productMap.put(1, new Product(1, "精致茶壶", 59.9));
        productMap.put(2, new Product(2, "机械键盘", 119));
        productMap.put(3, new Product(3, "ipad专用键盘", 29.8));
        productMap.put(4, new Product(4, "DIY手工填涂画布", 23.5));
        productMap.put(6, new Product(6, "机器人DIY", 399));
        productMap.put(8, new Product(8, "无人机", 1289));
        productMap.put(9, new Product(9, "洗发水", 49.9));
        productMap.put(10, new Product(10, "沐浴露", 29.9));
    }

    @Override
    public List<Product> getProductList(List<Integer> ids) {
        List<Product> resList = new ArrayList<>();
        ids.forEach(id->resList.add(productMap.get(id)));
        return resList;
    }
}
// server
public class Server extends Thread {
    private ProductListService productListService;

    public Server(int port) throws IOException, ClassNotFoundException {
        productListService = new ProductListServiceImpl();
        ServerSocket serverSocket = new ServerSocket(port);
        while(true) {
            System.out.println("等待连接,ip=127.0.0.1, port=9999");
            Socket socket = serverSocket.accept();
            process(socket);
            socket.close();
        }
    }

    private void process(Socket socket) throws IOException, ClassNotFoundException {
        System.out.println("ip Address:"+socket.getInetAddress());
        System.out.println("port:"+socket.getPort());
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        List<Integer> list = (List<Integer>) ois.readObject();
        List<Product> productList = productListService.getProductList(list);
        oos.writeObject(productList);
        oos.flush();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        new Server(9999).start();
    }
}
// client
public class Client {

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Socket socket = new Socket("127.0.0.1", 9999);
        System.out.println("客户端:连接成功!");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream dos = new ObjectOutputStream(baos);
        dos.writeObject(Arrays.asList(1,2,3,4));
        socket.getOutputStream().write(baos.toByteArray());
        dos.flush();
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        List<Product> list = (List<Product>) ois.readObject();
        System.out.println(list);
        socket.close();

    }
}

先启动server端,再启动client端,运行结果:

客户端:连接成功!
[Product{id=1, desc='精致茶壶', price=59.9}, Product{id=2, desc='机械键盘', price=119.0}, Product{id=3, desc='ipad专用键盘', price=29.8}, Product{id=4, desc='DIY手工填涂画布', price=23.5}]

分析:客户端只是为了调用一个getProductList()方法,就要编写大量无关的socket代码,明显做了很多无关的事情,改进空间巨大。

(2)版本二:使用动态代理帮助客户端处理网络相关细节

// client
public class Stub {
    public ProductListService getSub() {
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 9999);
                System.out.println("客户端:连接成功!");
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                ObjectOutputStream dos = new ObjectOutputStream(baos);
                dos.writeObject(args[0]);
                socket.getOutputStream().write(baos.toByteArray());
                dos.flush();
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                List<Product> list = (List<Product>) ois.readObject();
                socket.close();
                return list;
            }
        };
        return (ProductListService) Proxy.newProxyInstance(ProductListService.class.getClassLoader(), new Class[]{ProductListService.class}, invocationHandler);
    }
}
// client
public class Client {

    public static void main(String[] args) {
        ProductListService sub = new Stub().getSub();
        System.out.println(sub.getProductList(Arrays.asList(1,2,4,9)));
    }
}

先启动server端,再启动client端,运行结果:

客户端:连接成功!
[Product{id=1, desc='精致茶壶', price=59.9}, Product{id=2, desc='机械键盘', price=119.0}, Product{id=4, desc='DIY手工填涂画布', price=23.5}, Product{id=9, desc='洗发水', price=49.9}]

分析:这样一写,客户端代码只有简洁的两行。但仍有不足,这限定死了只能调用getProductList方法,参数只能是ArrayList<Integer>类型。比如想调用其他的方法,就还得再封装一次网络底层。

(3)版本三:网络传输时将方法名、参数类型、参数值一一存放到Map中

// api
public interface ProductListService {

    List<Product> getProductList(List<Integer> ids);

    Product getProduct(int id);
}
// server
public class ProductListServiceImpl implements ProductListService {
    Map<Integer, Product> productMap = new HashMap<>();
    public ProductListServiceImpl() {
        productMap.put(1, new Product(1, "精致茶壶", 59.9));
        productMap.put(2, new Product(2, "机械键盘", 119));
        productMap.put(3, new Product(3, "ipad专用键盘", 29.8));
        productMap.put(4, new Product(4, "DIY手工填涂画布", 23.5));
        productMap.put(6, new Product(6, "机器人DIY", 399));
        productMap.put(8, new Product(8, "无人机", 1289));
        productMap.put(9, new Product(9, "洗发水", 49.9));
        productMap.put(10, new Product(10, "沐浴露", 29.9));
    }

    @Override
    public List<Product> getProductList(List<Integer> ids) {
        List<Product> resList = new ArrayList<>();
        ids.forEach(id->resList.add(productMap.get(id)));
        return resList;
    }

    @Override
    public Product getProduct(int id) {
        return productMap.get(id);
    }
}

 

// server
public class Server3 extends Thread {
    private ProductListService productListService;

    public Server3(int port) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        productListService = new ProductListServiceImpl();
        ServerSocket serverSocket = new ServerSocket(port);
        while(true) {
            System.out.println("等待连接,ip=127.0.0.1, port=9999");
            Socket socket = serverSocket.accept();
            process(socket);
            socket.close();
        }
    }

    private void process(Socket socket) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("ip Address:"+socket.getInetAddress());
        System.out.println("port:"+socket.getPort());
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        Map<String, Object> map = (Map<String, Object>) ois.readObject();
        /**
         * map.put("args", args);
         * map.put("argsTypes", method.getParameterTypes());
         * map.put("methodName", method.getName());
         */
        Object[] args = (Object[]) map.get("args");
        String methodName = (String) map.get("methodName");
        Class[] argsTypes = (Class[]) map.get("argsTypes");

        Method method = ProductListService.class.getMethod(methodName, argsTypes);
        Object res = method.invoke(productListService, args);
        oos.writeObject(res);
        oos.flush();
    }
public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        new Server3(1999).start();
    }
}
// client
public class Stub {
    public ProductListService getSub() {
        InvocationHandler invocationHandler = (proxy, method, args) -> {
            Socket socket = new Socket("127.0.0.1", 1999);
            System.out.println("客户端:连接成功!");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream dos = new ObjectOutputStream(baos);
            Map<String, Object> map = new HashMap<>();
            map.put("args", args);
            map.put("argsTypes", method.getParameterTypes());
            map.put("methodName", method.getName());
            dos.writeObject(map);
            socket.getOutputStream().write(baos.toByteArray());
            dos.flush();
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Object obj = ois.readObject();
            socket.close();
            return obj;
        };
        return (ProductListService) Proxy.newProxyInstance(ProductListService.class.getClassLoader(), new Class[]{ProductListService.class}, invocationHandler);
    }

}
// client
public class Client {
    // 可调用任意方法
    public static void main(String[] args) {
        ProductListService sub = new Stub().getSub();
        System.out.println(sub.getProductList(Arrays.asList(7,8,9,10)));
        System.out.println(sub.getProduct(1));
    }
}

先启动server端,再启动client端,运行结果:

客户端:连接成功!
[null, Product{id=8, desc='无人机', price=1289.0}, Product{id=9, desc='洗发水', price=49.9}, Product{id=10, desc='沐浴露', price=29.9}]
客户端:连接成功!
Product{id=1, desc='精致茶壶', price=59.9}

  分析:现在可以调用ProductListService服务的各种方法啦!假如再新增一个方法deleteProduct(id)

// api
public interface ProductListService {

    List<Product> getProductList(List<Integer> ids);

    Product getProduct(int id);

    int deleteProduct(int id);

}
// server
public class ProductListServiceImpl implements ProductListService {
    Map<Integer, Product> productMap = new HashMap<>();
    public ProductListServiceImpl() {
        productMap.put(1, new Product(1, "精致茶壶", 59.9));
        productMap.put(2, new Product(2, "机械键盘", 119));
        productMap.put(3, new Product(3, "ipad专用键盘", 29.8));
        productMap.put(4, new Product(4, "DIY手工填涂画布", 23.5));
        productMap.put(6, new Product(6, "机器人DIY", 399));
        productMap.put(8, new Product(8, "无人机", 1289));
        productMap.put(9, new Product(9, "洗发水", 49.9));
        productMap.put(10, new Product(10, "沐浴露", 29.9));
    }

    @Override
    public List<Product> getProductList(List<Integer> ids) {
        List<Product> resList = new ArrayList<>();
        ids.forEach(id->resList.add(productMap.get(id)));
        return resList;
    }

    @Override
    public Product getProduct(int id) {
        return productMap.get(id);
    }

    @Override
    public int deleteProduct(int id) {
        Product remove = productMap.remove(id);
        return remove==null?0:1;
    }
}

先启动server端,再启动client端,运行结果: 

客户端:连接成功!
[null, Product{id=8, desc='无人机', price=1289.0}, Product{id=9, desc='洗发水', price=49.9}, Product{id=10, desc='沐浴露', price=29.9}]
客户端:连接成功!
0
客户端:连接成功!
null

分析:似乎ProductListService服务的方法新增删除不会影响网络底层的变化,但细心的读者可能发现了,如果需要再新增一个UserService呢,网络底层仍得变。

迭代四:先找服务,再确定方法

// api
public interface UserService {

    String sayHello(String name);

    User getUserById();
}
// server
public class Server4 extends Thread {
    private Map<String, Class> routeClassMap;
    private Map<String, Object> serviceMap;

    public void init() {
        routeClassMap = new HashMap<>();
        routeClassMap.put("rpc.ProductListService", ProductListService.class);
        routeClassMap.put("service.UserService", UserService.class);
        serviceMap = new HashMap<>();
        serviceMap.put("rpc.ProductListService", new ProductListServiceImpl());
        serviceMap.put("service.UserService", new UserServiceImpl());
    }

    public Server4(int port) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        init();
        ServerSocket serverSocket = new ServerSocket(port);
        while(true) {
            System.out.println("等待连接,ip=127.0.0.1, port="+port);
            Socket socket = serverSocket.accept();
            process(socket);
            socket.close();
        }
    }

    private void process(Socket socket) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        System.out.println("ip Address:"+socket.getInetAddress());
        System.out.println("port:"+socket.getPort());
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
        Map<String, Object> map = (Map<String, Object>) ois.readObject();
        /**
         * map.put("args", args);
         * map.put("argsTypes", method.getParameterTypes());
         * map.put("methodName", method.getName());
         */
        Object[] args = (Object[]) map.get("args");
        String methodName = (String) map.get("methodName");
        Class[] argsTypes = (Class[]) map.get("argsTypes");
        String serviceName = (String) map.get("serviceName");


        Method method = routeClassMap.get(serviceName).getMethod(methodName, argsTypes);
        Object res = method.invoke(serviceMap.get(serviceName), args);
        oos.writeObject(res);
        oos.flush();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        new Server4(2999).start();
    }
}
// client
public class Stub<T> {
    Class<T> serviceClazz;
    public Stub(Class<T> serviceClazz) {
        this.serviceClazz = serviceClazz;
    }

    public T getSub() {
        InvocationHandler invocationHandler = (proxy, method, args) -> {
            Socket socket = new Socket("127.0.0.1", 2999);
            System.out.println("客户端:连接成功!");
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream dos = new ObjectOutputStream(baos);
            Map<String, Object> map = new HashMap<>();
            map.put("serviceName", method.getDeclaringClass().getName());
            map.put("args", args);
            map.put("argsTypes", method.getParameterTypes());
            map.put("methodName", method.getName());
            dos.writeObject(map);
            socket.getOutputStream().write(baos.toByteArray());
            dos.flush();
            ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
            Object obj = ois.readObject();
            socket.close();
            return obj;
        };
        return (T) Proxy.newProxyInstance(serviceClazz.getClassLoader(), new Class[]{serviceClazz}, invocationHandler);
    }

}
// client
public class Client {
    // 可调用任意服务、任意方法
    public static void main(String[] args) {
        ProductListService sub = new Stub<>(ProductListService.class).getSub();
        System.out.println(sub.getProductList(Arrays.asList(7,8,9,10)));
        UserService sub1 = new Stub<>(UserService.class).getSub();
        System.out.println(sub1.getUserById());
        System.out.println(sub1.sayHello("jj"));

    }
}

先启动server端,再启动client端,运行结果: 

客户端:连接成功!
[null, Product{id=8, desc='无人机', price=1289.0}, Product{id=9, desc='洗发水', price=49.9}, Product{id=10, desc='沐浴露', price=29.9}]
客户端:连接成功!
User(id=1, name=Jack)
客户端:连接成功!
hello:jj

分析:这样写就可以支持各种不同的服务、不同方法、不同参数调用了。

只不过本文使用了原始的BIO同步阻塞模型,其实可以用netty提供的nio模型来做rpc的底层,出于学习的目的,才有了本文的四次迭代演进。

Reference

1_RPC基本理论_哔哩哔哩_bilibili 马士兵老师亲自授课

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学无止境jl

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值