RMI簡單介紹
前言
這篇部落格主要介紹RMI(Remote Method Invocation)遠端方法呼叫,這個東西現在用的不多,只是作為分散式基礎,進行一個簡單瞭解,為了引出RPC的相關概念,並對RPC做一個簡單認識,方便後面學習Dubbo
RMI簡介
RMI全稱是remote method invocation 遠端方法呼叫,一種用於遠端過程呼叫的應用程式程式設計介面,是純java的網路分散式應用系統的核心解決方案之一。RMI使用Java遠端訊息交換協議JRMP(Java Remote Messaging Protocol)進行通訊,JRMP是專門為Java物件制定的,對非Java語言開發的應用系統支援不足,不能與非java語言的物件進行通訊。
RMI簡單實現
目前瞭解的RMI好像有兩種實現方式,一種是單獨執行登錄檔,另一種是在伺服器端執行登錄檔,由於本身這裡的RMI只是為了方便自己理解RPC框架,這裡不再糾結於那種方式,而是簡單採用手動操作,能完成呼叫就算成功,至於深入理解,後面會補上。
1、建立服務端專案rmi-server,建立相應的包,然後編寫對外的服務介面
/**
*
* @author liman
* @createtime 2018年8月16日
* @contract 15528212893
* @comment:
* 建立遠端介面,需要繼承至remote
*/
public interface PersonService extends Remote{
public List<PersonEntity> GetList() throws RemoteException;
}
該介面需要實現Remote介面,這個介面是個標記介面,由於遠端方法呼叫的本質是網路通訊,只是隱藏了底層實現。網路通訊出現異常需要處理,所以所有的方法都必須要丟擲RemoteException以說明該方法可能丟擲網路通訊異常。
2、編寫遠端方法介面實現類
package com.learn.rmi.self.JRMP;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.LinkedList;
import java.util.List;
/**
*
* @author liman
* @createtime 2018年8月16日
* @contract 15528212893
* @comment:
* 遠端服務介面的實現類,這個實現類需要繼承UnicastRemoteObject並實現PersonService介面
*/
public class PersonServiceImpl extends UnicastRemoteObject implements PersonService{
protected PersonServiceImpl() throws RemoteException {
super();
}
public List<PersonEntity> GetList() throws RemoteException {
System.out.println("Get Person Start!");
List<PersonEntity> personList=new LinkedList<PersonEntity>();
PersonEntity person1=new PersonEntity();
person1.setAge(25);
person1.setId(0);
person1.setName("Leslie");
personList.add(person1);
PersonEntity person2=new PersonEntity();
person2.setAge(25);
person2.setId(1);
person2.setName("Rose");
personList.add(person2);
return personList;
}
}
該方法的方法引數和返回值會在網路上傳輸,所以必須要實現序列化介面。
3、建立客戶端專案,rmi-client
將前兩部的服務端介面程式碼(PersonService)和實體(PersonEntity)拷貝到客戶端中,需要保證package路徑與服務端一致。首先承認這步操作有點騷氣,一般這個步驟在之前的開發中都會有java編譯器自帶的RMI註冊服務完成。
4、編寫服務端的服務釋出程式
package com.learn.rmi.self.JRMP;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
/**
*
* @author liman
* @createtime 2018年8月16日
* @contract 15528212893
* @comment:
* 服務端註冊通訊埠
*/
public class ServerProgram {
public static void main(String[] args) {
try {
PersonService personService = new PersonServiceImpl();
//註冊通訊埠
LocateRegistry.createRegistry(6600);
//註冊通訊路徑
Naming.rebind("rmi://127.0.0.1:6600/PersonService", personService);
System.out.println("Service start");
} catch (Exception e) {
e.printStackTrace();
}
}
}
LocateRegistry.createRegistry(6600);——註冊通訊埠。Naming.rebind()——註冊通訊路徑。
5、編寫客戶端程式碼
在確認將服務端的service介面和對應的實體類拷貝過來之後,編寫客戶端程式碼
package com.learn.rmi.self.JRMP;
import java.rmi.Naming;
import java.util.List;
/**
*
* @author liman
* @createtime 2018年8月16日
* @contract 15528212893
* @comment:
* 客戶端進行測試
*/
public class ClientProgram {
public static void main(String[] args) {
try {
PersonService personService = (PersonService)Naming.lookup("rmi://127.0.0.1:6600/PersonService");
List<PersonEntity> personList = personService.GetList();
for(PersonEntity person:personList) {
System.out.println("ID:"+person.getId()+" Age:"+person.getAge()+" Name:"+person.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
6、測試執行
先執行服務端程式碼,完成之後控制檯輸出如下結果,表示服務端啟動成功
然後啟動客戶端:
客戶端沒有維護PersonService的邏輯,只是保留了服務端的介面資訊,客戶端能成功呼叫相應的服務。不過有幾個細節需要注意。
1、涉及到的序列化物件需要實現Serializable介面,這裡的就是PersonEntity物件。
2、客戶端和服務端PersonEntity中的serialVersionUID的值要一致,否則會丟擲二者不一致的異常。關於serialVersionUID這個欄位的作用,在序列化一文中有過總結,但是不夠全面,這裡可以參看這篇文章——serialVersionUID欄位的作用。
RMI底層通訊(取消註冊中心)
其實RMI的原理和RPC框架很像,前幾天在看原始碼的過程中,終於成功被原始碼繞暈,看來還是先只能簡單實現,原始碼的理解後面再深入。
分為客戶端和服務端,底層實質就是序列化和反序列化的通訊。
客戶端程式碼
1、客戶端測試程式碼
主要通過獲取服務端遠端的物件和呼叫結果
package com.learn.rmi.rpc;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*/
public class ClientDemo {
public static void main(String[] args) {
RpcClientProxy rpcClientProxy = new RpcClientProxy();
ILearnHello learnHello = rpcClientProxy.clientProxy(ILearnHello.class,"localhost",8888);
System.out.println(learnHello.sayHello("liman"));
}
}
2、RpcClientProxy代理類
package com.learn.rmi.rpc;
import com.learn.rmi.ref.rpc.RemoteInvocationHandler;
import java.lang.reflect.Proxy;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*
* 客戶端的代理
*/
public class RpcClientProxy {
public <T> T clientProxy(final Class<T> interfaceClass,final String host,final int port){
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader(),
new Class[]{interfaceClass}, new RemoteInvocationHandler(host,port));
}
}
3、RemoteInvocationHandler類,設定請求物件中需要呼叫的目標方法和引數,並完成請求的傳送和結果獲取
package com.learn.rmi.rpc;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*
* 需要發起客戶端和服務端的遠端呼叫。
*/
public class RemoteInvocationHandler implements InvocationHandler{
private String host;
private int port;
public RemoteInvocationHandler(String host, int port) {
this.host = host;
this.port = port;
}
/**
* 需要跟遠端發起呼叫
* 設定了需要呼叫的遠端方法資訊,然後釋出到遠端
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequest request = new RpcRequest();
request.setClassName(method.getDeclaringClass().getName());
request.setMethodName(method.getName());
request.setParameters(args);
TCPTransport tcpTransport = new TCPTransport(this.host,this.port);
return tcpTransport.send(request);
}
}
4、TCPTransport,真正的請求傳送
package com.learn.rmi.rpc;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
* <p>
* 專門管理socket
*/
public class TCPTransport {
private String host;
private int port;
public TCPTransport(String host, int port) {
this.host = host;
this.port = port;
}
//建立一個新的連線
private Socket newSocket() {
System.out.println("建立一個新連線");
Socket socket = null;
try {
socket = new Socket(host, port);
return socket;
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException("建立連線異常!");
}
}
public Object send(RpcRequest request) {
Socket socket = null;
try {
socket = newSocket();
ObjectOutputStream outputStream = new ObjectOutputStream
(socket.getOutputStream());
outputStream.writeObject(request);
outputStream.flush();
//處理返回的結果
ObjectInputStream inputStream = new ObjectInputStream
(socket.getInputStream());
Object result = inputStream.readObject();
inputStream.close();
outputStream.close();
return result;
} catch (Exception e) {
throw new RuntimeException("發起服務呼叫異常");
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5、遠端目標的介面
package com.learn.rmi.rpc;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*/
public interface ILearnHello {
public String sayHello(String msg);
}
6、請求物件,客戶端主要完成的任務就是封裝一個RPCRequest請求物件,然後傳送到遠端伺服器,這不操作在真正的RPC框架中並不是這麼實現的,後續需要深入學習這一點。
package com.learn.rmi.rpc;
import java.io.Serializable;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*
* 傳輸物件
*
*/
public class RpcRequest implements Serializable{
private static final long serialVersionUID = 903071532920235263L;
private String className;
private String methodName;
private Object[] parameters;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
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;
}
}
服務端程式碼
1、業務介面
package com.learn.rmi.rpc;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*/
public interface ILearnHello {
public String sayHello(String msg);
}
2、業務介面實現類,這個和介面都需要放在與客戶端同一個目錄名稱下
package com.learn.rmi.rpc;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*/
public class LearnHelloImpl implements ILearnHello{
@Override
public String sayHello(String msg) {
return "Hello "+msg+" this is remote service";
}
}
3、RPCRequest,由於這裡是最簡陋的實現,需要服務端維護一個RPCRequest物件,同樣需要與客戶端放在相同目錄下
package com.learn.rmi.rpc;
import java.io.Serializable;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*
* 傳輸物件
*
*/
public class RpcRequest implements Serializable{
private static final long serialVersionUID = 903071532920235263L;
private String className;
private String methodName;
private Object[] parameters;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
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;
}
}
4、將服務釋出到指定的埠上(即使在指定的埠上處理請求)
package com.learn.rmi.rpc;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*
* 負責將服務釋出出去
*/
public class RpcServer {
//定義一個執行緒池
private final ExecutorService executorService = Executors.newCachedThreadPool();
/**
* 將指定的服務釋出出去
* @param service 待發布的服務
* @param port 埠
*/
public void publisher(final Object service,int port){
ServerSocket serverSocket = null;
try {
//啟動一個服務監聽
serverSocket = new ServerSocket(port);
System.out.println("服務開啟,等待客戶端請求");
//迴圈監聽服務
while(true){
//迴圈處理客戶端請求
Socket socket = serverSocket.accept();
executorService.execute(new ProcessorHandler(socket,service));
}
} catch (IOException e) {
e.printStackTrace();
}finally{
if(serverSocket!=null){
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
5、ProcessHandler類,真正的處理請求程式碼
package com.learn.rmi.rpc;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.Socket;
/**
* Created by liman on 2018/8/18.
* QQ:657271181
* e-mail:liman65727@sina.com
*/
public class ProcessorHandler implements Runnable{
//待發布的服務
private Socket socket;
private Object service;
public ProcessorHandler(Socket socket, Object service) {
this.socket = socket;
this.service = service;
}
@Override
public void run() {
//處理客戶端的請求,這裡採用多執行緒的方式完成
ObjectInputStream objectInputStream = null;
try {
objectInputStream = new ObjectInputStream(socket.getInputStream());
//獲取遠端傳輸的物件,通過反序列化獲取
RpcRequest request = (RpcRequest) objectInputStream.readObject();
//處理結果
Object result = invoke(request);
//將結果輸出到客戶端
ObjectOutputStream outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeObject(result);
//各種流的簡單關閉,這裡就沒有在finally中關閉
outputStream.flush();
objectInputStream.close();
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}finally{
if(objectInputStream!=null){
try {
objectInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 通過反射獲取客戶端需要呼叫的方法,並將結果返回
* @param request
* @return
*/
private Object invoke(RpcRequest request) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Object[] args = request.getParameters();
Class<?>[] types = new Class[args.length];
for(int i = 0;i<args.length;i++){
types[i] = args[i].getClass();
}
//獲取方法資訊
Method method = service.getClass().getMethod(request.getMethodName(),types);
return method.invoke(service,args);
}
}
6、服務端測試類
package com.learn.rmi.rpc;
/**
* Created by liman on 2018/8/19.
* QQ:657271181
* e-mail:liman65727@sina.com
*/
public class ServerDemo {
public static void main(String[] args) {
ILearnHello iLearnHello = new LearnHelloImpl();
RpcServer rpcServer = new RpcServer();
rpcServer.publisher(iLearnHello,8889);
}
}
執行結果
服務端執行結果
客戶端執行結果
說明
上述針對RMI的總結十分簡單,後面針對RMI的實現也是十分簡單,直接省去了註冊中心這一步。後續需要學習深入
相關文章
- Map簡單介紹
- SVG簡單介紹SVG
- Clickjacking簡單介紹
- 【Pandas】簡單介紹
- ActiveMQ簡單介紹MQ
- JSON簡單介紹JSON
- RPC簡單介紹RPC
- Python簡單介紹Python
- KVM簡單介紹
- HTML簡單介紹HTML
- HTML 簡單介紹HTML
- JavaScript 簡單介紹JavaScript
- CSS 簡單介紹CSS
- ajax簡單介紹
- AOP的簡單介紹
- 簡單介紹克隆 JavaScriptJavaScript
- 禪道簡單介紹
- Apache Curator簡單介紹Apache
- spark簡單介紹(一)Spark
- Flutter key簡單介紹Flutter
- Ansible(1)- 簡單介紹
- Webpack 的簡單介紹Web
- Git_簡單介紹Git
- jQuery Validate簡單介紹jQuery
- JSON物件簡單介紹JSON物件
- <svg>元素簡單介紹SVG
- 簡單介紹 ldd 命令
- Flownet 介紹 及光流的簡單介紹
- form表單的簡單介紹ORM
- mt19937 簡單介紹
- mitmproxy中libmproxy簡單介紹MITIBM
- 函子的簡單介紹
- 元學習簡單介紹
- 簡單介紹JavaScript閉包JavaScript
- 檔案管理簡單介紹
- 簡單介紹靜態路由路由
- canvas標籤簡單介紹Canvas
- Git發展簡單介紹Git