使用Socket&反射&Java流操做進行方法的遠程調用(模擬RPC遠程調用)

寫在前面

閱讀本文首先得具有基本的Socket、反射、Java流操做的基本API使用知識;不然本文你可能看不懂。。。java

服務端的端口監聽

進行遠程調用,那就必須得有客戶端和服務端。服務端負責提供服務,客戶端來對服務端進行方法調用。因此如今咱們清楚了: 須要一個服務端、一個客戶端面試

那麼咱們說幹就幹,咱們先創建一個服務端:數組

  • 經過Socket監聽本地服務器的一個端口(8081)
  • 調用socket的accept方法等待客戶端的鏈接(accpet方法原理)
/**
 *  RPC服務端
 * @author wushuaiping
 * @date 2018/3/15 下午12:23
 */
public class ObjectServerSerializ {
    public static void main(String[] args) {

        try {

            // 啓動服務端,並監聽8081端口
            ServerSocket serverSocket = new ServerSocket(8081);
            // 服務端啓動後,等待客戶端創建鏈接
            Socket accept = serverSocket.accept();
            } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

客戶端與服務端創建鏈接

咱們服務端監聽了端口後,那麼咱們須要使用客戶端去訪問目標服務端的這個端口,代碼以下:服務器

/**
 *  RPC客戶端,這裏發起調用請求。
 *   模擬RPC框架調用過程
 * @author wushuaiping
 * @date 2018/3/15 下午12:22
 */
public class ObjectClientSerializ {
        public static void main(String[] args)  {

            try {

                // 使用Socket與指定IP的主機端口進行鏈接。
                Socket socket = new Socket("localhost", 8081);
            } catch (Exception e) {
                e.printStackTrace();
        }
    }
}

業務方法

與服務端創建鏈接後,那咱們進行下一步。由於咱們要模擬RPC遠程調用,那麼咱們的有一個業務方法:網絡

業務方法接口框架

/**
 * 業務方法接口
 */
public interface HelloService {

	String sayHello(String str);
}

業務方法實現類socket

遠程調用必需要實現序列化接口(Serializable)。測試

/**
 * 
 * @author wushuaiping
 *
 */
public class HelloServiceImpl implements Serializable, HelloService {

	/**
	 * 
	 */
	private static final long serialVersionUID = 203100359025257718L;

	/**
	 * 
	 */
	public String sayHello(String str) {
		System.out.println("執行方法體,入參=" + str);
		return str;
	}

}

數據傳輸模型對象

咱們有了服務方法後,首先想到的是,咱們若是將序列化後的對象傳輸到服務端之後,服務端如何知道這是哪一個對象?不可能使用Object來調用方法吧,因此咱們須要一個能封裝業務類方法信息的數據傳輸對象。那麼該數據傳輸對象須要具有哪些信息?服務端調用確定得用反射來調用方法,因此咱們這個數據傳輸對象就得知足一下條件:this

  • 第一,反射調用時必須知道方法名 String methodName
  • 第二,反射調用時必須知道方法參數類型 Object[] parameterTypes
  • 第三,反射調用時必須知道參數 Object[] parameters
  • 第四,反射調用時必須知道哪一個對象在調用 Object invokeObject

知足以上條件後,就能夠進行反射調用方法了,可是,咱們經過服務端調用後,咱們須要知道服務端返回的數據信息。那麼該對象還須要一個參數:.net

  • 第五,須要一個返回對象 Object result

經過上述分析,咱們創建了該對象:

/**
 *  數據傳輸模型
 * @author wushuaiping
 * @date 2018/3/15 下午12:25
 */
public class TransportModel implements Serializable{
    /**
     *
     */
    private static final long serialVersionUID = -6338270997494457923L;

    //返回結果
    private Object result;
    //對象
    private Object object;
    //方法名
    private String methodName;
    //參數
    private Class<?>[] parameterTypes;

    private Object[] parameters;

    public void setParameterTypes(Class<?>[] parameterTypes) {
        this.parameterTypes = parameterTypes;
    }

    public Class<?>[] getParameterTypes() {
        return parameterTypes;
    }

    public void setResult(Object result) {
        this.result = result;
    }

    public Object getResult() {
        return result;
    }

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }
}

客戶端設置相應調用信息

有了數據傳輸模型後,咱們將須要的對象信息封裝進數據傳輸模型,咱們就能夠真正的開始對服務端的服務進行調用了!

/**
 *  RPC客戶端,這裏發起調用請求。
 *   模擬RPC框架調用過程
 * @author wushuaiping
 * @date 2018/3/15 下午12:22
 */
public class ObjectClientSerializ {
        public static void main(String[] args)  {

            try {

                // 使用Socket與指定IP的主機端口進行鏈接。
                Socket socket = new Socket("localhost", 8081);

                // 建立一個業務對象,模擬客戶端發起調用。
                HelloService helloService = new HelloServiceImpl();

                // 該傳輸模型對象存儲了客戶端發起調用的業務對象的一些信息。
                TransportModel model = new TransportModel();

                // 設置客戶端的調用對象
                model.setObject(helloService);
                // 設置須要調用的方法
                model.setMethodName("sayHello");
                // 得到業務對象的字節碼信息
                Class class1 = helloService.getClass();

                // 在業務對象的字節碼信息中獲取"sayHello"而且方法入參爲String的方法
                Method method = class1.getMethod("sayHello",String.class);

                // 設置傳輸模型對象中的調用信息。
                // 設置方法參數類型
                model.setParameterTypes(method.getParameterTypes());
                // 設置方法參數
                model.setParameters(new Object[]{"The first step of RPC"});

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
}

將數據傳輸模型對象發送到服務端

在設置好相關調用信息後,如今終於能夠去服務端調用了,可是咱們不可能直接將數據傳輸模型對象「給」服務端,在網絡中傳輸數據都是以流(比特流)的形式傳輸的, 因此咱們還要將數據傳輸模型對象轉爲流,傳輸給服務端。

/**
 *  RPC客戶端,這裏發起調用請求。
 *   模擬RPC框架調用過程
 * @author wushuaiping
 * @date 2018/3/15 下午12:22
 */
public class ObjectClientSerializ {
        public static void main(String[] args)  {

            try {

                // 使用Socket與指定IP的主機端口進行鏈接。
                Socket socket = new Socket("localhost", 8081);

                // 建立一個業務對象,模擬客戶端發起調用。
                HelloService helloService = new HelloServiceImpl();

                // 該傳輸模型對象存儲了客戶端發起調用的業務對象的一些信息。
                TransportModel model = new TransportModel();

                // 設置客戶端的調用對象
                model.setObject(helloService);
                // 設置須要調用的方法
                model.setMethodName("sayHello");
                // 得到業務對象的字節碼信息
                Class class1 = helloService.getClass();

                // 在業務對象的字節碼信息中獲取"sayHello"而且方法入參爲String的方法
                Method method = class1.getMethod("sayHello",String.class);

                // 設置傳輸模型對象中的調用信息。
                // 設置方法參數類型
                model.setParameterTypes(method.getParameterTypes());
                // 設置方法參數
                model.setParameters(new Object[]{"The first step of RPC"});

                // 把存儲了業務對象信息的數據傳輸模型對象轉爲流,也就是序列化對象。方便在網絡中傳輸。
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(model);
                oos.flush();
                byte[] byteArray = bos.toByteArray();

                // 得到一個socket的輸出流。經過該流能夠將數據傳輸到服務端。
                OutputStream outputStream = socket.getOutputStream();

                // 往輸出流中寫入須要進行傳輸的序列化後的流信息
                outputStream.write(byteArray);
                outputStream.flush();

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
}

獲取服務端返回的信息

當咱們把數據序列化後以流的方式傳輸給了服務端。確定不是大功告成了,由於咱們還得知道服務端給咱們返回了什麼東西:

/**
 *  RPC客戶端,這裏發起調用請求。
 *   模擬RPC框架調用過程
 * @author wushuaiping
 * @date 2018/3/15 下午12:22
 */
public class ObjectClientSerializ {
        public static void main(String[] args)  {

            try {

                // 使用Socket與指定IP的主機端口進行鏈接。
                Socket socket = new Socket("localhost", 8081);

                // 建立一個業務對象,模擬客戶端發起調用。
                HelloService helloService = new HelloServiceImpl();

                // 該傳輸模型對象存儲了客戶端發起調用的業務對象的一些信息。
                TransportModel model = new TransportModel();

                // 設置客戶端的調用對象
                model.setObject(helloService);
                // 設置須要調用的方法
                model.setMethodName("sayHello");
                // 得到業務對象的字節碼信息
                Class class1 = helloService.getClass();

                // 在業務對象的字節碼信息中獲取"sayHello"而且方法入參爲String的方法
                Method method = class1.getMethod("sayHello",String.class);

                // 設置傳輸模型對象中的調用信息。
                // 設置方法參數類型
                model.setParameterTypes(method.getParameterTypes());
                // 設置方法參數
                model.setParameters(new Object[]{"The first step of RPC"});

                // 把存儲了業務對象信息的數據傳輸模型對象轉爲流,也就是序列化對象。方便在網絡中傳輸。
                ByteArrayOutputStream bos = new ByteArrayOutputStream();
                ObjectOutputStream oos = new ObjectOutputStream(bos);
                oos.writeObject(model);
                oos.flush();
                byte[] byteArray = bos.toByteArray();

                // 得到一個socket的輸出流。經過該流能夠將數據傳輸到服務端。
                OutputStream outputStream = socket.getOutputStream();

                // 往輸出流中寫入須要進行傳輸的序列化後的流信息
                outputStream.write(byteArray);
                outputStream.flush();

                // 由於socket創建的是長鏈接,因此能夠獲取到將流數據傳到服務端後,返回的信息。
                // 因此咱們須要經過輸入流,來獲取服務端返回的流數據信息。
                InputStream inputStream = socket.getInputStream();
                ObjectInputStream ois = new ObjectInputStream(inputStream);

                // 將獲得的流數據讀成Object對象,強轉爲咱們的數據傳輸模型對象。最後獲得服務端返回的結果。
                TransportModel readObject = (TransportModel)ois.readObject();
                System.out.println("調用返回結果="+readObject.getResult());
                socket.close();

                System.out.println("客戶端調用結束");

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
}

此時,咱們客戶端的調用算是大功告成了。接下來咱們應該去服務端接收客戶端發送過來的數據了。

服務端接收客戶端數據

客戶端接收到的數據是以流方式存在的,因此須要反序列化轉流爲Java對象。

/**
 *  RPC服務端
 * @author wushuaiping
 * @date 2018/3/15 下午12:23
 */
public class ObjectServerSerializ {
    public static void main(String[] args) {

        try {

            // 啓動服務端,並監聽8081端口
            ServerSocket serverSocket = new ServerSocket(8081);

            // 服務端啓動後,等待客戶端創建鏈接
            Socket accept = serverSocket.accept();

            // 獲取客戶端的輸入流,並將流信息讀成Object對象。
            // 而後強轉爲咱們的數據傳輸模型對象,由於咱們客戶端也是用的該對象進行傳輸,因此強轉沒有問題。
            InputStream inputStream = accept.getInputStream();
            ObjectInputStream ois = new ObjectInputStream(inputStream);
            TransportModel transportModel = (TransportModel) ois.readObject();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服務端經過反射調用方法

由於須要調用的對象方法等相關數據都封裝在數據傳輸模型對象裏面,因此咱們只須要把裏面的參數拿出來,再經過反射去掉用服務端存在的本地方法便可。

/**
 *  RPC服務端
 * @author wushuaiping
 * @date 2018/3/15 下午12:23
 */
public class ObjectServerSerializ {
    public static void main(String[] args) {

        try {

            // 啓動服務端,並監聽8081端口
            ServerSocket serverSocket = new ServerSocket(8081);

            // 服務端啓動後,等待客戶端創建鏈接
            Socket accept = serverSocket.accept();

            // 獲取客戶端的輸入流,並將流信息讀成Object對象。
            // 而後強轉爲咱們的數據傳輸模型對象,由於咱們客戶端也是用的該對象進行傳輸,因此強轉沒有問題。
            InputStream inputStream = accept.getInputStream();
            ObjectInputStream ois = new ObjectInputStream(inputStream);
            TransportModel transportModel = (TransportModel) ois.readObject();

            // 由於客戶端在把流信息發過來以前,已經把相關的調用信息封裝進咱們的數據傳輸模型對象中了
            // 因此這裏咱們能夠直接拿到這些對象的信息,而後經過反射,對方法進行調用。
            Object object = transportModel.getObject();
            String methodName = transportModel.getMethodName();
            Class<?>[] parameterTypes = transportModel.getParameterTypes();
            Object[] parameters = transportModel.getParameters();

            // 經過方法名和方法參數類型,獲得一個方法對象
            Method method = object.getClass().getMethod(methodName,parameterTypes);

            // 而後經過這個方法對象去掉用目標方法,返回的是這個方法執行後返回的數據
            Object res = method.invoke(object, parameters);

            System.out.println("提供服務端執行方法返回結果:"+res);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服務端將數據返回給客戶端

服務端經過反射調用完目標方法後,咱們還須要將調用目標方法後獲得的數據返回給客戶端。

/**
 *  RPC服務端
 * @author wushuaiping
 * @date 2018/3/15 下午12:23
 */
public class ObjectServerSerializ {
    public static void main(String[] args) {

        try {

            // 啓動服務端,並監聽8081端口
            ServerSocket serverSocket = new ServerSocket(8081);

            // 服務端啓動後,等待客戶端創建鏈接
            Socket accept = serverSocket.accept();

            // 獲取客戶端的輸入流,並將流信息讀成Object對象。
            // 而後強轉爲咱們的數據傳輸模型對象,由於咱們客戶端也是用的該對象進行傳輸,因此強轉沒有問題。
            InputStream inputStream = accept.getInputStream();
            ObjectInputStream ois = new ObjectInputStream(inputStream);
            TransportModel transportModel = (TransportModel) ois.readObject();

            // 由於客戶端在把流信息發過來以前,已經把相關的調用信息封裝進咱們的數據傳輸模型對象中了
            // 因此這裏咱們能夠直接拿到這些對象的信息,而後經過反射,對方法進行調用。
            Object object = transportModel.getObject();
            String methodName = transportModel.getMethodName();
            Class<?>[] parameterTypes = transportModel.getParameterTypes();
            Object[] parameters = transportModel.getParameters();

            // 經過方法名和方法參數類型,獲得一個方法對象
            Method method = object.getClass().getMethod(methodName,parameterTypes);

            // 而後經過這個方法對象去掉用目標方法,返回的是這個方法執行後返回的數據
            Object res = method.invoke(object, parameters);

            System.out.println("提供服務端執行方法返回結果:"+res);

            // 得到服務端的輸出流
            OutputStream outputStream = accept.getOutputStream();

            // 創建一個字節數組輸出流對象。把數據傳輸模型對象序列化。方便進行網絡傳輸
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);

            // 建立一個數據傳輸模型對象,將服務端的返回數據傳到客戶端。
            TransportModel transportModel1 = new TransportModel();
            transportModel1.setResult(res);
            oos.writeObject(transportModel1);

            outputStream.write(bos.toByteArray());
            outputStream.flush();
            bos.close();
            outputStream.close();
            serverSocket.close();
            System.out.println("服務端關閉");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

測試

先啓動服務端的main方法,在啓用客戶端的main方法。以後咱們會看到以下輸出:

調用返回結果=The first step of RPC
客戶端調用結束

寫在最後

至此,方法的遠程調用已經完成~~ 這篇文章寫得有點倉促,明天還有面試。今天就先這樣了~ 晚安~

相關文章
相關標籤/搜索