徒手打造 - RPC遠程通訊框架v1+v2

手寫RPC框架V1

gitee地址:https://gitee.com/kylin1991_admin/rpc_handwriting

Api.jar

1、寫一個IHelloService接口,類似dubbo
public interface IHelloService {
    String sayHello(String context);
  }
2、一個RpcRequest請求模型
@Data
public class RpcRequest implements Serializable {
    private String className;
    private String methodName;
    private Object[] parameters;
}

Server

1、Server 服務註冊
  public class Server {
      public static void main(String[] args) {
          IHelloService helloService = new IHelloServiceImpl();
          RpcProxyServer rpcProxyServer = new RpcProxyServer(helloService,8080);
          rpcProxyServer.publisher();
      }
  }
2、RpcProxyServer 服務發佈
public class RpcProxyServer {
    private Object service;
    private int port;

    public RpcProxyServer(Object service, int port) {
        this.service = service;
        this.port = port;
    }

    public void publisher() {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                Socket socket = serverSocket.accept();
                executor.execute(new ProcessorHandler(service,socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static final Executor executor = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors(),
            60L,
            TimeUnit.MINUTES,
            new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors()),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
    );
}
3、請求處理ProcessorHandler
public class ProcessorHandler implements Runnable {
    private Object service;
    private Socket socket;

    public ProcessorHandler(Object service, Socket socket) {
        this.service = service;
        this.socket = socket;
    }

    @Override
    public void run() {
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())) {

            // 接受請求
            RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();

            // 反射調用並且返回結果
            objectOutputStream.writeObject(invoke(rpcRequest));
            objectOutputStream.flush();


        } catch (IOException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Object invoke(RpcRequest rpcRequest, Object service) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class clazz = Class.forName(rpcRequest.getClassName());
        Class<?>[] parametersTypes = new Class<?>[rpcRequest.getParameters().length];
        for (int i = 0; i < parametersTypes.length; i++) {
            parametersTypes[i] = rpcRequest.getParameters()[i].getClass();
        }
        Method method = clazz.getMethod(rpcRequest.getMethodName(), parametersTypes);
        return method.invoke(service, rpcRequest.getParameters());
    }
}

Client

1、Client 發起請求
public class Client {
    public static void main(String[] args) {
        RpcProxyClient rpcProxyClient = new RpcProxyClient();
        IHelloService helloService = rpcProxyClient.proxyClient(IHelloService.class,"localhost",8080);
        System.out.println(helloService.sayHello("lilei"));;
    }
}
2、遠程調用(採用jdk動態代理)
  • RpcProxyClient
public class RpcProxyClient {
    public <T> T proxyClient(Class<?> interfacesCls, String host, int port) {
        return (T) Proxy.newProxyInstance(
                interfacesCls.getClassLoader(),
                new Class<?>[]{interfacesCls},
                new RemoteInvocationHandler(host,port));
    }
}
  • RemoteInvocationHandler 用於遠程方法調用
public class RemoteInvocationHandler implements InvocationHandler {
    private String host;
    private int port;

    public RemoteInvocationHandler(String host, int port) {
        this.host = host;
        this.port = port;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        //包裝請求
        RpcRequest rpcRequest = new RpcRequest();
        rpcRequest.setClassName(method.getDeclaringClass().getName());
        rpcRequest.setMethodName(method.getName());
        rpcRequest.setParameters(args);

        //發送請求,並且獲得結果
        RpcNetTransport rpcNetTransport = new RpcNetTransport(host, port);
        return rpcNetTransport.send(rpcRequest);
    }
}
3、RpcNetTransport 調用
public class RpcNetTransport {
    private String host;
    private int port;

    public RpcNetTransport(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public Object send(RpcRequest rpcRequest) {
        Object result = null;
        try (Socket socket = new Socket(host, port);
             ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream())) {

            objectOutputStream.writeObject(rpcRequest);
            objectOutputStream.flush();

            result = objectInputStream.readObject();

        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
        return result;
    }
}

測試

1、啓動Server

2、啓動Client

測試成功!

問題

  • 是否可以用註解來實現?

手寫RPC框架V2

  • 通過註解來實現服務發佈,和服務調用

重寫Server

1、加入spring的容器
     <!--  爲了方便用spring的註解 Component -->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.4.RELEASE</version>		
        </dependency>
2、創建配置類SpringConfig
@Configuration
public class SpringConfig {
    @Bean(name = "rpcServer")
    public RpcServer rpcServer() {
        return new RpcServer(8080);
    }
}
3、創建註解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component // 被Spring進行掃描,並管理
public @interface RpcService {
    Class<?> value();
}
4、RpcServer類
  • 實現ApplicationContextAware
  • 實現InitializingBean
  • setApplicationContext 中獲取被RpcService註解修飾的類。然後根據類引用路徑作爲name,對象作爲值,保存到一個map中
  • 把map作爲傳入參數傳入線程processor
@Configuration
public class RpcProxyServer implements ApplicationContextAware, InitializingBean {
    private static final Executor executor = new ThreadPoolExecutor(
            Runtime.getRuntime().availableProcessors(),
            Runtime.getRuntime().availableProcessors(),
            60L,
            TimeUnit.MINUTES,
            new ArrayBlockingQueue<>(Runtime.getRuntime().availableProcessors()),
            Executors.defaultThreadFactory(),
            new ThreadPoolExecutor.AbortPolicy()
    );
    private int port;
    private Map<String, Object> handlerMap = new HashMap<>();

    public RpcServer(int port) {
        this.port = port;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        try {
            ServerSocket serverSocket = new ServerSocket(port);
            while (true) {
                Socket socket = serverSocket.accept();
                executor.execute(new ProcessorHandler(handlerMap, socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        Map<String, Object> serviceBeanMap = applicationContext.getBeansWithAnnotation(RpcService.class);
        if (!serviceBeanMap.isEmpty()) {
            for (Object serviceBean : serviceBeanMap.values()) {
                RpcService rpcService = serviceBean.getClass().getAnnotation(RpcService.class);
                String serviceName = rpcService.value().getName();
                handlerMap.put(serviceName, serviceBean);
            }
        }
    } 
}
5、修改ProcessorHandler
  • 接受handlerMap集合
  • 根據類名從集合中取出參數
public class ProcessorHandler implements Runnable { 
    private Socket socket;
    private Map<String, Object> handlerMap;

    public ProcessorHandler(Map<String, Object> handlerMap, Socket socket) {
        this.handlerMap = handlerMap;
        this.socket = socket;
    }

    @Override
    public void run() {
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
             ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream())) {

            // 接受請求
            RpcRequest rpcRequest = (RpcRequest) objectInputStream.readObject();

            // 反射調用並且返回結果
            objectOutputStream.writeObject(invoke(rpcRequest));
            objectOutputStream.flush();


        } catch (IOException | ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
            e.printStackTrace();
        } finally {
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private Object invoke(RpcRequest rpcRequest) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        String className = rpcRequest.getClassName();
        Object service = handlerMap.get(className);
        if (service == null) {
            throw new RuntimeException("service not found :" + className);
        }
        Class clazz = Class.forName(className);
        Class<?>[] parametersTypes = new Class<?>[rpcRequest.getParameters().length];
        for (int i = 0; i < parametersTypes.length; i++) {
            parametersTypes[i] = rpcRequest.getParameters()[i].getClass();
        }
        Method method = clazz.getMethod(rpcRequest.getMethodName(), parametersTypes);
        return method.invoke(service, rpcRequest.getParameters());
    }
}
6、測試是否可行
@ComponentScan("org.example")
public class Server {
    public static void main( String[] args ) {
        AnnotationConfigApplicationContext configApplicationContext =new AnnotationConfigApplicationContext(Server.class);
    }
}
重寫客戶端
1、加入spring的容器
     <!--  爲了方便用spring的註解 Component -->
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.4.RELEASE</version>		
        </dependency>
2、修改RpcProxyClient
public class RpcProxyClient {
    private final String host;
    private final int port;

    public RpcProxyClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public <T> T proxyClient(Class<?> interfacesCls) {
        return (T) Proxy.newProxyInstance(interfacesCls.getClassLoader(), new Class<?>[]{interfacesCls}, new RemoteInvocationHandler(host, port));
    }
}
3、創建SpringConfig
@Configuration
public class SpringConfig {
    private static final RpcProxyClient PROXY_CLIENT = new RpcProxyClient("localhost", 8080);

    @Bean
    public IHelloService iHelloService() {
        return PROXY_CLIENT.proxyClient(IHelloService.class);
    }
}
4、測試是否可行
@ComponentScan("org.example")
public class Client {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Client.class); 
        
        IHelloService iHelloService = context.getBean(IHelloService.class);

        String result = iHelloService.sayHello("lilei");
        System.out.println(result);
    }
}

備註,如果客戶端向實現更簡化的註解,那麼可以利用BeanDefinition來實現,具體看鏈接

如何把接口加入到Spring容器(任何類/接口都可以用這個方式,例如動態代理)

源碼gitee地址

支持多版本的實現

//todo 有時間在弄吧

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章