RPC(Remote Procedure Call)是指远程过程调用,但是分布式服务器节点由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。客户端机器A想要服务端机器B执行一个给定的任务(方法名),这个任务的执行环境(参数)由客户端A给出。任务的执行在服务端B发生,客户端A只关心任务结果并不关心执行过程。那么如何实现这个功能呢?
    实现远程调用需要涉及网络通信Socket、IO流和动态代理 :::info 服务端 ::: 首先定义一个接口,表示服务端可以帮忙执行的任务列表

    1. public interface HelloService {
    2. String hello(String name);
    3. String bye();
    4. }

    然后定义一个实现类,表示服务端真正提供的服务

    1. public class HelloServiceImpl implements HelloService {
    2. @Override
    3. public String hello(String name) {
    4. return "Hello" + name;
    5. }
    6. @Override
    7. public String bye() {
    8. return "Bye Bye!";
    9. }
    10. }

    定义服务端进程,接收每一个客户端发来的请求,根据网络传入参数调用服务类对应的服务

    1. package rpc;
    2. import java.io.IOException;
    3. import java.io.ObjectInputStream;
    4. import java.io.ObjectOutputStream;
    5. import java.lang.reflect.InvocationTargetException;
    6. import java.lang.reflect.Method;
    7. import java.net.ServerSocket;
    8. import java.net.Socket;
    9. import java.util.concurrent.ExecutorService;
    10. import java.util.concurrent.Executors;
    11. public class RpcServer {
    12. private ExecutorService threadPool;
    13. private static final int DEFAULT_THREAD_NUM = 10;
    14. public RpcServer() { //构造函数生成一个线程池
    15. threadPool = Executors.newFixedThreadPool(DEFAULT_THREAD_NUM);
    16. }
    17. public void register(Object service, int port) { //服务端主动注册服务
    18. try {
    19. System.out.println("server starts...");
    20. // TCP socket_server
    21. ServerSocket server = new ServerSocket(port);
    22. Socket socket = null; //接收客户端发来的请求
    23. while ((socket = server.accept()) != null) {
    24. System.out.println("client connected");
    25. threadPool.execute(new Processor(socket, service)); //线程池拿出一个线程执行任务 任务一定要实现Runnable接口
    26. }
    27. } catch (IOException e) {
    28. e.printStackTrace();
    29. }
    30. }
    31. class Processor implements Runnable {
    32. Socket socket;
    33. Object service;
    34. public Processor(Socket socket, Object service) {
    35. this.socket = socket;
    36. this.service = service;
    37. }
    38. public void process() {
    39. }
    40. @Override
    41. public void run() { //真正的服务
    42. try {
    43. // 解码获取客户端的请求参数信息
    44. ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
    45. String methodName = in.readUTF(); // 获取方法名
    46. Class<?>[] parameterTypes = (Class<?>[]) in.readObject(); // 参数类型
    47. Object[] parameters = (Object[]) in.readObject(); // 参数
    48. // 从注册的service获取服务类,
    49. // 并且根据客户端传入的方法名以及参数调用服务类的相关方法
    50. Method method = service.getClass().getMethod(methodName, parameterTypes);
    51. try {
    52. // 方法调用invoke
    53. Object result = method.invoke(service, parameters);
    54. // 初始化输出即返回值
    55. ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
    56. // 结果返回值,供客户端接收
    57. out.writeObject(result);
    58. } catch (IllegalAccessException e) {
    59. e.printStackTrace();
    60. } catch (InvocationTargetException e) {
    61. e.printStackTrace();
    62. }
    63. } catch (IOException e) {
    64. e.printStackTrace();
    65. } catch (ClassNotFoundException e) {
    66. e.printStackTrace();
    67. } catch (NoSuchMethodException e) {
    68. e.printStackTrace();
    69. }
    70. }
    71. }
    72. }

    开启服务进程

    1. package rpc;
    2. public class Main {
    3. public static void main(String[] args) {
    4. HelloService helloService = new HelloServiceImpl();
    5. RpcServer server = new RpcServer();
    6. server.register(helloService, 50001);
    7. }
    8. }

    :::info 客户端 ::: 定义客户端进程,通过接口的代理向服务端发送任务请求

    1. package rpc;
    2. import java.io.ObjectInputStream;
    3. import java.io.ObjectOutputStream;
    4. import java.lang.reflect.InvocationHandler;
    5. import java.lang.reflect.Method;
    6. import java.lang.reflect.Proxy;
    7. import java.net.Socket;
    8. public class RpcClient {
    9. public static void main(String[] args) {
    10. // 生成接口的代理类对象 (代理类字节码文件是运行时生成的)
    11. HelloService helloService = getClient(HelloService.class, "127.0.0.1", 50001);
    12. // 代理类通过内部invoke方法调用接口方法hello,bye
    13. System.out.println(helloService.hello("chenqihang"));
    14. System.out.println(helloService.bye());
    15. }
    16. @SuppressWarnings("unchecked")
    17. public static <T> T getClient(Class<T> clazz, String ip, int port) {
    18. return (T) Proxy.newProxyInstance(RpcClient.class.getClassLoader(), new Class<?>[]{clazz}, new InvocationHandler() {
    19. @Override
    20. public Object invoke(Object arg0, Method arg1, Object[] arg2) throws Throwable {
    21. //连上服务端
    22. Socket socket = new Socket(ip, port);
    23. //客户端期望访问远程HelloService服务,因此将HelloService类信息编码发送到服务端
    24. ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
    25. out.writeUTF(arg1.getName()); //访问远程方法名:hello bye
    26. out.writeObject(arg1.getParameterTypes()); //发送方法参数:chenqihang " "
    27. out.writeObject(arg2);
    28. //Java反射是本地调用,HelloService服务在服务端的本地,因此
    29. //arg1.invoke(service,args)需要在服务端被调用并返回结果给客户端
    30. ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
    31. return in.readObject();
    32. }
    33. });
    34. }
    35. }

    先开启服务端进程,再执行客户端进程。通过上述代码就实现了客户端进程访问服务端进程Say Hello和Say Bye的功能 :::info RPC框架 ::: 当然这只是RPC的一个小的执行框架,相信聪明的同学们已经发现似乎上述过程有些技术是多余的呢,比如代理技术,直接通过反射机制获取信息再传递到服务端似乎也能完成需要的功能,没错这是对的,但是抛弃动态代理的代码是不是通用性就降低了呢?一个好的RPC框架既需要满足需要的功能,还需要方便用户使用。
    为了提高效率和方便使用国内外的互联网公司及研究机构做了大量的工作。