基於 Socket 的 RPC 實現

基礎版本

定義一個User類。java

import java.io.Serializable;

public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    int id;
    String name;

    public User(int id, String name) {
        this.id = id;
        this.name = name;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

定義一個接口,返回User對象。spring

public interface IUserService {
    User findUserById(int id);
}

實現該接口,返回User對象。socket

public class IUserServiceImpl implements IUserService {
    @Override
    public User findUserById(int id) {
        return new User(id, "Alice");
    }
}

定義一個服務端,創建Socket鏈接,根據傳入的ID值返回User對象信息。ide

import com.zebro.IUserService;
import com.zebro.IUserServiceImpl;
import com.zebro.User;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    private static boolean running = true;
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(8888);
        //循環監聽
        while(running){
            Socket client = server.accept();
            process(client);
            client.close();
        }
        server.close();
    }
    
    public static void process(Socket socket) throws Exception {
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
        
        //讀取客戶端傳入的ID
        int id = dis.readInt();
        IUserService service = new IUserServiceImpl();
        User user = service.findUserById(id);
        dos.writeInt(user.getId());
        dos.writeUTF(user.getName());
        dos.flush();
    }
}

編寫一個客戶端,用於發送ID和接收返回的User對象信息。優化

import java.io.*;
import java.net.Socket;

public class Client {
    public static void main(String[] args) throws Exception {
        Socket socket = new Socket("127.0.0.1", 8888);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        
        //發送給服務端
        dos.writeInt(123);
        socket.getOutputStream().write(baos.toByteArray());
        socket.getOutputStream().flush();
        
        //接收服務端返回的結果
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        int id = dis.readInt();
        String name = dis.readUTF();
        
        //組裝
        User user = new User(id,name);
        System.out.println(user);
        
        dos.close();
        socket.close();
    }
}

這時候客戶端不須要知道服務端的具體方法名也能取得數據。this

優化版本1

簡化客戶端的調用方式,引入客戶端存根stub。.net

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class Stub {
    public User findUserById(int id) throws IOException {
        Socket socket = new Socket("127.0.0.1", 8888);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        DataOutputStream dos = new DataOutputStream(baos);
        
        //發送給服務端
        dos.writeInt(id);
        socket.getOutputStream().write(baos.toByteArray());
        socket.getOutputStream().flush();
        
        //接收服務端返回的結果
        DataInputStream dis = new DataInputStream(socket.getInputStream());
        int idtmp = dis.readInt();
        if(idtmp != id) System.out.println("error");
        String name = dis.readUTF();
        User user = new User(id,name);
        
        return user;
    }
}
import java.io.IOException;

public class Client {
    public static void main(String[] args) throws IOException {
        Stub stub = new Stub();
        System.out.println(stub.findUserById(123));
    }
}

這時候客戶端不須要知道服務端的具體方法名也能取得數據。代理

優化版本2

上述版本中,若是服務端方法較多,客戶端存根鬚要提供大量的方法和返回值類型封裝,引入動態代理優化相關邏輯。code

import com.zebro.User;
import com.zebro.IUserService;

import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;

public class Stub {
    public static IUserService getStub(){
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 8888);
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                DataOutputStream dos = new DataOutputStream(baos);

                dos.writeInt((Int)args);

                //發送給服務端
                socket.getOutputStream().write(baos.toByteArray());
                socket.getOutputStream().flush();
                
                //接收服務端返回的結果
                DataInputStream dis = new DataInputStream(socket.getInputStream());
                int id = dis.readInt();
                String name = dis.readUTF();
                Object user = new User(id,name);

                return user;
            }
        };

        //經過動態代理,實例化一個代理對象
        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
        System.out.println(o.getClass().getName());
        System.out.println(o.getClass().getInterfaces()[0]);
        return (IUserService) o;
    }
}
import com.zebro.IUserService;

public class Client {
    public static void main(String[] args) {
        IUserService stub = Stub.getStub();
        System.out.println(stub.findUserById(123));
    }
}

這時候客戶端經過IUserService接口,能夠知道服務端的具體方法名,也能取得數據。server

優化版本3

上述版本中,客戶端不管調用什麼方法,服務端均調用findUserById處理邏輯並返回User對象,修改成動態方法優化相關邏輯。

import java.io.*;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;

public class Stub {
    static IUserService getStub(){
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 8888);
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //支持動態方法名
                oos.writeUTF(method.getName());
                oos.writeObject(method.getParameterTypes());
                oos.writeObject(args);
                oos.flush();

                //接收服務端返回的結果,object讀入
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                User user = (User)ois.readObject();
                
                oos.close();
                socket.close();
                return user;
            }
        };

        Object o = Proxy.newProxyInstance(IUserService.class.getClassLoader(), new Class[]{IUserService.class}, h);
        System.out.println(o.getClass().getName());
        System.out.println(o.getClass().getInterfaces()[0]);
        return (IUserService) o;
    }
}
import com.zebro.IUserService;
import com.zebro.IUserServiceImpl;
import com.zebro.User;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    private static boolean running = true;
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(8088);
        while(running){
            Socket client = server.accept();
            process(client);
            client.close();
        }
        server.close();
    }
    
    public static void process(Socket socket) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

        //服務端支持動態方法和參數的調用
        String methodName = ois.readUTF();
        Class[] parameterTypes = (Class[]) ois.readObject();
        Object[] parameters = (Object[]) ois.readObject();
        
        //服務類型暫時仍是寫死的,不夠靈活
        IUserService service = new IUserServiceImpl();
        Method method = service.getClass().getMethod(methodName, parameterTypes);
        User user = (User)method.invoke(service, parameters);
        oos.writeObject(user);
        oos.flush();
    }
}

優化版本4

上述版本中,客戶端和服務端都只支持IUserService的方法調用,而且返回User對象,修改成支持任意接口方法的調用優化相關邏輯。

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;

public class Stub {
    static Object getStub(Class c){
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Socket socket = new Socket("127.0.0.1", 8888);
                ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

                //服務類型
                oos.writeUTF(c.getName());
                oos.writeUTF(method.getName());
                oos.writeObject(method.getParameterTypes());
                oos.writeObject(args);
                oos.flush();

                //接收服務端返回的結果,object讀入
                ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
                Object obj = ois.readObject();
                
                //改成返回通用對象
                return obj;
            }
        };
        
        //這裏要寫成通用的c,而不是固定的接口
        Object o = Proxy.newProxyInstance(c.getClassLoader(), new Class[]{c}, h);
        System.out.println(o.getClass().getName());
        System.out.println(o.getClass().getInterfaces()[0]);
        return o;
    }
}
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

public class Server {
    private static boolean running = true;
    private static HashMap<String,Class> registerTable = new HashMap<>();
    
    static{
        registerTable.put(IUserService.class.getName(),IUserServiceImpl.class);
        registerTable.put(IProductService.class.getName(), IProductServiceImpl.class);
    }
    
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket(8888);
        while(running){
            Socket client = server.accept();
            process(client);
            client.close();
        }
        server.close();
    }
    
    public static void process(Socket socket) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
        ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());

        //爲了適應客戶端通用化而作的改動
        String clazzName = ois.readUTF();
        String methodName = ois.readUTF();
        Class[] parameterTypes = (Class[]) ois.readObject();
        Object[] parameters = (Object[]) ois.readObject();

        //從註冊表中查到服務類,若是使用spring甚至還能夠直接根據配置注入bean而後根據bean查找。
        Object service = registerTable.get(clazzName).newInstance();
        Method method = service.getClass().getMethod(methodName, parameterTypes);
        Object o = method.invoke(service, parameters);
        oos.writeObject(o);
        oos.flush();
    }
}
import com.zebro.IProductService;
import com.zebro.IUserService;

public class Client {
    public static void main(String[] args) {
        IUserService userService = (IUserService) Stub.getStub(IUserService.class);
        IProductService productService = (IProductService)Stub.getStub(IProductService.class);
        
        System.out.println(userService.findUserById(123));
        System.out.println(productService.findProductByName("Bob"));
    }
}
相關文章
相關標籤/搜索