最近看到一個遠程調用的簡單實現,于是加上自己的理解分享給大家。
遠程調用是典型的CS模型,Server端提供服務,客戶端調用得到結果
先看服務端提供服務的方法
1 /**
2 * 提供服務
3 *
4 * @param service 服務實現
5 * @param port 端口(可以雙發約定)
6 * @throws Exception
7 */
8 public static void provide(final Object service, final int port) throws Exception {
9 //參數檢查
10 if (service == null) {
11 throw new IllegalArgumentException("The service can't be null!");
12 }
13 if (port > 65535) {
14 throw new IllegalArgumentException("The host can't greater than 65535!");
15 }
16 //開啟一個ServerSocket接收請求
17 ServerSocket serverSocket = new ServerSocket(port);
18 //死循環等待請求
19 while (true) {
20 //接受到請求,獲取socket
21 final Socket socket = serverSocket.accept();
22 try {
23 //開啟一個線程處理
24 new Thread(new Runnable() {
25 @Override
26 public void run() {
27 try {
28 try {
29 //重socket中獲取輸入流
30 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
31 try {
32 //獲取方法名
33 String methodName = ois.readUTF();
34 //獲取方法參數數組
35 Class[] methodParameterTypes = (Class[]) ois.readObject();
36 //獲取參數值數組
37 Object[] arguments = (Object[]) ois.readObject();
38 //根據方法名和方法參數獲取方法(根據方法名和方法參數可以唯一定位到一個方法)
39 Method method = service.getClass().getMethod(methodName, methodParameterTypes);
40 if (method == null) {
41 throw new NoSuchMethodException();
42 }
43 //執行方法
44 Object result = method.invoke(service, arguments);
45 System.out.println("Method:" + methodName + ";Arguments:" + arguments + " invoke!");
46 //獲取socket輸出流
47 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
48 try {
49 //輸出結果
50 oos.writeObject(result);
51 } finally {
52 oos.close();
53 }
54 } finally {
55 ois.close();
56 }
57 } finally {
58 socket.close();
59 }
60 } catch (Exception e) {
61 //記個日志啥的
62 e.printStackTrace();
63 }
64 }
65 }).start();
66 } catch (Exception e) {
67 //記個日志啥的
68 e.printStackTrace();
69 }
70 }
71 }
然后是消費的方法
**
* 消費服務
*
* @param clazz 接口類
* @param host 發布服務機器的host
* @param port 發布服務機器的port
* @return
*/
public static Object consume(final Class clazz, final String host, final int port) {
//參數檢查
if (clazz == null) {
throw new IllegalArgumentException("The clazz can't be null!");
}
if (host == null || host.isEmpty()) {
throw new IllegalArgumentException("The host can't be null or empty!");
}
if (port > 65535) {
throw new IllegalArgumentException("The host can't greater than 65535!");
}
//生成代理,每次調用方法其實是調用遠程的服務
Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
//建立socket鏈接
Socket socket = new Socket(host, port);
try {
//獲取輸出流
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
try {
String methodName = method.getName();
Class[] methodParameterTypes = method.getParameterTypes();
//輸出要調用的方法名
oos.writeUTF(methodName);
//輸出要調用的方法參數列表
oos.writeObject(methodParameterTypes);
//輸出要調用的方法參數
oos.writeObject(arguments);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
try {
//獲取結果
Object result = ois.readObject();
//可能返回的對象是異常
if (result instanceof Throwable) {
throw (Throwable) result;
}
return result;
} finally {
ois.close();
}
} finally {
oos.close();
}
} finally {
socket.close();
}
}
});
return proxy;
}
一般端口可以雙方約定,而host可以采用configServer的方法解決,也就是開啟一個服務,當啟動一個服務的時候到configServer注冊一下(服務名+host),如果是多臺服務器提供服務,host就是一個list,調用方發起調用的時候首先到configServer根據服務名獲取host列表,然后選一個host發起調用!configServer的優點是可以做到很多控制,比如流量控制,權重控制,調用host列表維護(死掉就剔除,重試機制)等等,這樣調用方不用關心我調用的是哪臺機器,只用關心我調用哪個方法。但也有壞處,一旦configServer掛掉了.......(其實也可以通過MS或調用方本地緩存調用列表解決)。
一般由調用方提供一個接口包(算是一個雙方的約定),接口類中定義了提供發提供的方法
如我們發布一個簡單的服務
1 public interface Girl {
2 //提供服務
3 String server(String name);
4 }
實現
1 public class GirlImpl implements Girl{
2 @Override
3 public String server(String name) {
4 return name+"亞美爹";
5 }
6 }
發布服務
這里你可以寫在一個main方法中,也可以配置一個Spring的bean,并配置init方法,然后在init方法中開啟
1 Girl beautifulGirl=new GirlImpl();
2 try {
//在本機的1111端口上開啟Girl的服務
3 Utils.provide(beautifulGirl, 1111);
4 } catch (Exception e) {
5 e.printStackTrace();
6 }
消費
1 try {
2 //從此你就獲得了一個漂亮妹子,她可以給你提供各種服務
3 Girl beautifulGirl= (Girl)Utils.consume(Girl.
class, "127.0.0.1", 3333);
4 //你可以來一個循環,或者來一個死循環,一直
哈哈
5 beautifulGirl.server("yourName");
6 }
catch (Exception e) {
7 e.printStackTrace();
8 }
總結:
其實遠程調用也就是獲取服務的一個代理,每當你調用服務的方法事,他都會想服務方傳去方法,方法參數列表,參數,前兩個用于唯一確定一個方法,后一個用于方法調用。
這里實現的很簡答,當然還有很復雜的,比如Spring的實現,淘寶的HSF等等
以上為個人理解,如果有錯的地方,歡迎指正。