日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

深究Java中的RMI底层原理

發布時間:2023/12/19 java 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 深究Java中的RMI底层原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

原博客地址:http://blog.csdn.net/sinat_34596644/article/details/52599688

前言:隨著一個系統被用戶認可,業務量、請求量不斷上升,那么單機系統必然就無法滿足了,于是系統就慢慢走向分布式了,隨之而來的是系統之間“溝通”的障礙。一般來說,解決系統之間的通信可以有兩種方式:即遠程調用和消息。RMI(Remote Method Invocation)就是遠程調用的一種方式,也是這篇文章主要介紹的。


一、RMI的一個簡單示例

這個示例拆分為服務端和客戶端,放在兩個idea項目中,并且通過了單機和雙機兩種環境的測試,是真正意義上的分布式應用。

項目結構

服務端應用: Server

主程序: ? ? ? ?com.jnu.wwt.entry.Server

服務接口: ? ? com.jnu.wwt.service.IOperation

服務實現: ? ? com.jnu.wwt.service.impl.OperationImpl

客戶端應用: Client

主程序: ? ? ? ?com.jnu.wwt.entry.Client

服務接口: ? ?com.jnu.wwt.service.IOperation


源碼:

Server.java

/** * Created by wwt on 2016/9/14. */ public class Server {public static void main(String args[]) throws Exception{//以1099作為LocateRegistry接收客戶端請求的端口,并注冊服務的映射關系 Registry registry=LocateRegistry.createRegistry(1099); IOperation iOperation=new OperationImpl(); Naming.rebind("rmi://127.0.0.1:1099/Operation",iOperation); System.out.println("service running..."); }}

IOperation.java(服務端和客戶端各需要一份)

/** * 服務端接口必須實現java.rmi.Remote * Created by wwt on 2016/9/14. */ public interface IOperation extends Remote{/** * 遠程接口上的方法必須拋出RemoteException,因為網絡通信是不穩定的,不能吃掉異常 * @param a * @param b * @return */ int add(int a, int b) throws RemoteException; }

OperationImpl.java

/** * Created by wwt on 2016/9/14. */ public class OperationImpl extends UnicastRemoteObject implements IOperation{public OperationImpl() throws RemoteException {super(); }@Override public int add(int a, int b) throws RemoteException{return a+b; }}

Client.java

/** * Created by wwt on 2016/9/15. */ public class Client {public static void main(String args[]) throws Exception{IOperation iOperation= (IOperation) Naming.lookup("rmi://127.0.0.1:1099/Operation"); System.out.println(iOperation.add(1,1)); }}

運行結果

先運行Server應用,服務就起來了。然后切換到Client應用,點擊運行,Client調用Server的服務,返回結果。





二、RMI做了些什么

現在我們先忘記Java中有RMI這種東西。假設我們需要自己實現上面例子中的效果,怎么辦呢?可以想到的步驟是:

  • 編寫服務端服務,并將其通過某個服務機的端口暴露出去供客戶端調用。
  • 編寫客戶端程序,客戶端通過指定服務所在的主機和端口號、將請求封裝并序列化,最終通過網絡協議發送到服務端。
  • 服務端解析和反序列化請求,調用服務端上的服務,將結果序列化并返回給客戶端。
  • 客戶端接收并反序列化服務端返回的結果,反饋給用戶。

這是大致的流程,我們不難想到,RMI其實也是幫我們封裝了一些細節而通用的部分,比如序列化和反序列化,連接的建立和釋放等,下面是RMI的具體流程:


這里涉及到幾個新概念:

Stub和Skeleton:這兩個的身份是一致的,都是作為代理的存在??蛻舳说姆Q作Stub,服務端的稱作Skeleton。要做到對程序員屏蔽遠程方法調用的細節,這兩個代理是必不可少的,包括網絡連接等細節。

Registry:顧名思義,可以認為Registry是一個“注冊所”,提供了服務名到服務的映射。如果沒有它,意味著客戶端需要記住每個服務所在的端口號,這種設計顯然是不優雅的。


三、走進RMI原理之前,先來看看用到的類及其層次結構和主要的方法。





哪里看不懂隨時回來看看結構。。。開始了



四、一步步解剖RMI的底層原理


  • 服務端啟動Registry服務
Registry registry=LocateRegistry.createRegistry(1099);從上面這句代碼入手,追溯下去,可以發現服務端創建了一個RegistryImpl對象,這里做了一個判斷。如果服務端指定的端口號是1099并且系統開啟了安全管理器,那么可以在限定的權限集內(listen和accept)繞過系統的安全校驗。反之則必須進行安全校驗。這里純粹是為了效率起見。真正做的事情在setUp()方法中,繼續看下去。public RegistryImpl(final int var1) throws RemoteException {if(var1 == 1099 && System.getSecurityManager() != null) {try {AccessController.doPrivileged(new PrivilegedExceptionAction() {public Void run() throws RemoteException {LiveRef var1x = new LiveRef(RegistryImpl.id, var1); RegistryImpl.this.setup(new UnicastServerRef(var1x)); return null; }}, (AccessControlContext)null, new Permission[]{new SocketPermission("localhost:" + var1, "listen,accept")}); } catch (PrivilegedActionException var3) {throw (RemoteException)var3.getException(); }} else {LiveRef var2 = new LiveRef(id, var1); this.setup(new UnicastServerRef(var2)); }}setUp()方法將指向正在初始化的RegistryImpl對象的遠程引用ref(RemoteRef)賦值為傳入的UnicastServerRef對象,這里涉及了向上轉型。然后繼續移交UnicastServerRef的exportObject()方法。private void setup(UnicastServerRef var1) throws RemoteException {this.ref = var1; var1.exportObject(this, (Object)null, true); }進入UnicastServerRef的exportObject()方法??梢钥吹?#xff0c;這里首先為傳入的RegistryImpl創建一個代理,這個代理我們可以推斷出就是后面服務于客戶端的RegistryImpl的Stub對象。然后將UnicastServerRef的skel(skeleton)對象設置為當前RegistryImpl對象。最后用skeleton、stub、UnicastServerRef對象、id和一個boolean值構造了一個Target對象,也就是這個Target對象基本上包含了全部的信息。調用UnicastServerRef的ref(LiveRef)變量的exportObject()方法。public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {Class var4 = var1.getClass(); Remote var5; try {var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse); } catch (IllegalArgumentException var7) {throw new ExportException("remote object implements illegal remote interface", var7); }if(var5 instanceof RemoteStub) {this.setSkeleton(var1); }Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3); this.ref.exportObject(var6); this.hashToMethod_Map = (Map)hashToMethod_Maps.get(var4); return var5; }到上面為止,我們看到的都是一些變量的賦值和創建工作,還沒有到連接層,這些引用對象將會被Stub和Skeleton對象使用。接下來就是連接層上的了。追溯LiveRef的exportObject()方法,很容易找到了TCPTransport的exportObject()方法。這個方法做的事情就是將上面構造的Target對象暴露出去。調用TCPTransport的listen()方法,listen()方法創建了一個ServerSocket,并且啟動了一條線程等待客戶端的請求。接著調用父類Transport的exportObject()將Target對象存放進ObjectTable中。public void exportObject(Target var1) throws RemoteException {synchronized(this) {this.listen(); ++this.exportCount; }boolean var2 = false; boolean var12 = false; try {var12 = true; super.exportObject(var1); var2 = true; var12 = false; } finally {if(var12) {if(!var2) {synchronized(this) {this.decrementExportCount(); }}}}if(!var2) {synchronized(this) {this.decrementExportCount(); }}}到這里,我們已經將RegistryImpl對象創建并且起了服務等待客戶端的請求。
  • 客戶端獲取服務端Rgistry代理

IOperation iOperation= (IOperation) Naming.lookup("rmi://127.0.0.1:1099/Operation"); 從上面的代碼看起,容易追溯到LocateRegistry的getRegistry()方法。這個方法做的事情是通過傳入的host和port構造RemoteRef對象,并創建了一個本地代理??梢酝ㄟ^Debug功能發現,這個代理對象其實是RegistryImpl_Stub對象。這樣客戶端便有了服務端的RegistryImpl的代理(取決于ignoreStubClasses變量)。但注意此時這個代理其實還沒有和服務端的RegistryImpl對象關聯,畢竟是兩個VM上面的對象,這里我們也可以猜測,代理和遠程的Registry對象之間是通過socket消息來完成的。

public static Registry getRegistry(String host, int port, RMIClientSocketFactory csf)throws RemoteException {Registry registry = null; if (port <= 0)port = Registry.REGISTRY_PORT; if (host == null || host.length() == 0) {// If host is blank (as returned by "file:" URL in 1.0.2 used in // java.rmi.Naming), try to convert to real local host name so // that the RegistryImpl's checkAccess will not fail. try {host = java.net.InetAddress.getLocalHost().getHostAddress(); } catch (Exception e) {// If that failed, at least try "" (localhost) anyway... host = ""; }} LiveRef liveRef =new LiveRef(new ObjID(ObjID.REGISTRY_ID), new TCPEndpoint(host, port, csf, null), false); RemoteRef ref =(csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef); return (Registry) Util.createProxy(RegistryImpl.class, ref, false); }


  • 服務端創建服務對象
從OperationImpl的構造函數看起。調用了父類UnicastRemoteObject的構造方法,追溯到UnicastRemoteObject的私有方法exportObject()。這里做了一個判斷,判斷服務的實現是不是UnicastRemoteObject的子類,如果是,則直接賦值其ref(RemoteRef)對象為傳入的UnicastServerRef對象。反之則調用UnicastServerRef的exportObject()方法。這里我們是第一種情況。 private static Remote exportObject(Remote obj, UnicastServerRef sref)throws RemoteException {// if obj extends UnicastRemoteObject, set its ref. if (obj instanceof UnicastRemoteObject) {((UnicastRemoteObject) obj).ref = sref; }return sref.exportObject(obj, null, false); }

  • 將服務實現綁定到服務端的Registry上,使得客戶端只需與Registry交互。
Naming.rebind("rmi://127.0.0.1:1099/Operation",iOperation);從上面這行代碼開始看,容易發現Naming的方法全部都是調用的Registry的方法。這里通過host和port找到我們第一步啟動的服務端Registry服務對象,追溯到其rebind()方法,可以看到,其實做的事情很是簡單,就是把名字和服務實現存進一個Map里面。 public void rebind(String var1, Remote var2) throws RemoteException, AccessException {checkAccess("Registry.rebind"); this.bindings.put(var1, var2); }
  • 客戶端查找遠程服務

接下來就是重頭戲了,從下面代碼看起。

IOperation iOperation= (IOperation) Naming.lookup("rmi://127.0.0.1:1099/Operation"); 追溯下去,獲取到遠程Registry對象的代理對象之后,調用RegistryImpl_Stub的lookUp()方法。主要代碼如下。做的事情是利用上面通過服務端host和port等信息創建的RegistryImpl_stub對象構造RemoteCall調用對象,operations參數中是各個Registry中聲明的操作,2指明了是lookUp()操作。接下來分步驟看看...

try {RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L); try {ObjectOutput var3 = var2.getOutputStream(); var3.writeObject(var1); } catch (IOException var18) {throw new MarshalException("error marshalling arguments", var18); }super.ref.invoke(var2); Remote var23; try {ObjectInput var6 = var2.getInputStream(); var23 = (Remote)var6.readObject(); } catch (IOException var15) {throw new UnmarshalException("error unmarshalling return", var15); } catch (ClassNotFoundException var16) {throw new UnmarshalException("error unmarshalling return", var16); } finally {super.ref.done(var2); }return var23; }

調用 RegistryImpl_Stub的ref(RemoteRef)對象的newCall()方法,將RegistryImpl_Stub對象傳了進去,不要忘了構造它的時候我們將服務器的主機端口等信息傳了進去,也就是我們把服務器相關的信息也傳進了newCall()方法。newCall()方法做的事情簡單來看就是建立了跟遠程RegistryImpl的Skeleton對象的連接。(不要忘了上面我們說到過服務端通過TCPTransport的exportObject()方法等待著客戶端的請求)

public RemoteCall newCall(RemoteObject var1, Operation[] var2, int var3, long var4) throws RemoteException {clientRefLog.log(Log.BRIEF, "get connection"); Connection var6 = this.ref.getChannel().newConnection(); try {clientRefLog.log(Log.VERBOSE, "create call context"); if(clientCallLog.isLoggable(Log.VERBOSE)) {this.logClientCall(var1, var2[var3]); }StreamRemoteCall var7 = new StreamRemoteCall(var6, this.ref.getObjID(), var3, var4); try {this.marshalCustomCallData(var7.getOutputStream()); } catch (IOException var9) {throw new MarshalException("error marshaling custom call data"); }return var7; } catch (RemoteException var10) {this.ref.getChannel().free(var6, false); throw var10; } } 連接建立之后自然就是發送請求了。我們知道客戶端終究只是擁有Registry對象的代理,而不是真正地位于服務端的Registry對象本身,他們位于不同的虛擬機實例之中,無法直接調用。必然是通過消息進行交互的??纯磗uper.ref.invoke()這里做了什么?容易追溯到StreamRemoteCall的executeCall()方法??此票镜卣{用,但其實很容易從代碼中看出來是通過tcp連接發送消息到服務端。由服務端解析并且處理調用。

try {if(this.out != null) {var2 = this.out.getDGCAckHandler(); }this.releaseOutputStream(); DataInputStream var3 = new DataInputStream(this.conn.getInputStream()); byte var4 = var3.readByte(); if(var4 != 81) {if(Transport.transportLog.isLoggable(Log.BRIEF)) {Transport.transportLog.log(Log.BRIEF, "transport return code invalid: " + var4); }throw new UnmarshalException("Transport return code invalid"); }this.getInputStream(); var1 = this.in.readByte(); this.in.readID(); } 至此,我們已經將客戶端的服務查詢請求發出了。

  • 服務端接收客戶端的服務查詢請求并返回給客戶端結果
這里我用的方法是直接斷點在服務端的Thread的run()方法中,因為我們知道服務端已經用線程跑起了服務(當然我是先斷點在Registry_Impl的lookUp()方法并查找調用棧找到源頭的)。一步一步我們找到了Transport的serviceCall()方法,這個方法是關鍵。瞻仰一下主要的代碼,到ObjectTable.getTarget()為止做的事情是從socket流中獲取ObjId,并通過ObjId和Transport對象獲取Target對象,這里的Target對象已經是服務端的對象。再借由Target的派發器Dispatcher,傳入參數服務實現和請求對象RemoteCall,將請求派發給服務端那個真正提供服務的RegistryImpl的lookUp()方法,這就是Skeleton移交給具體實現的過程了,Skeleton負責底層的操作。 try {ObjID var40; try {var40 = ObjID.read(var1.getInputStream()); } catch (IOException var34) {throw new MarshalException("unable to read objID", var34); }Transport var41 = var40.equals(dgcID)?null:this; Target var5 = ObjectTable.getTarget(new ObjectEndpoint(var40, var41)); final Remote var38; if(var5 != null && (var38 = var5.getImpl()) != null) {final Dispatcher var6 = var5.getDispatcher(); var5.incrementCallCount(); boolean var8; try {transportLog.log(Log.VERBOSE, "call dispatcher"); final AccessControlContext var7 = var5.getAccessControlContext(); ClassLoader var42 = var5.getContextClassLoader(); Thread var9 = Thread.currentThread(); ClassLoader var10 = var9.getContextClassLoader(); try {var9.setContextClassLoader(var42); currentTransport.set(this); try {AccessController.doPrivileged(new PrivilegedExceptionAction() {public Void run() throws IOException {Transport.this.checkAcceptPermission(var7); var6.dispatch(var38, var1); return null; }}, var7); return true; } catch (PrivilegedActionException var32) {throw (IOException)var32.getException(); }} finally {var9.setContextClassLoader(var10); currentTransport.set((Object)null); }} catch (IOException var35) {transportLog.log(Log.BRIEF, "exception thrown by dispatcher: ", var35); var8 = false; } finally {var5.decrementCallCount(); }return var8; }throw new NoSuchObjectException("no such object in table"); }
看看RegistryImpl的lookUp()實現。做了同步控制,并通過服務名從Map中取出服務對象。返回給客戶端。還記得我們在bindings中存放的其實是OperationImpl的真正實現,并非是Stub對象。 public Remote lookup(String var1) throws RemoteException, NotBoundException {Hashtable var2 = this.bindings; synchronized(this.bindings) {Remote var3 = (Remote)this.bindings.get(var1); if(var3 == null) {throw new NotBoundException(var1); } else {return var3; }} }
  • 客戶端獲取通過lookUp()查詢獲得的客戶端OperationImpl的Stub對象
這里就不多說了。。多說無益。心好累。憑什么服務端返回給客戶端的是服務的實現,但是客戶端獲取到的是Stub對象呢?用同樣的斷點的方法,我們可以發現問題出在MarshalInputStream的resolveProxyClass()上,里面其實也是創建了一個代理。這就是那個Stub類。
  • 客戶端進行真正地遠程服務調用
到目前為止,客戶端已經有了Stub對象。就可以和服務端進行愉快地交流了。細心的朋友可能發現這個例子中的服務實現OperationImpl繼承了UnicastRemoteObject,就像前面說的,它似乎不會像RegistryImpl一樣在服務端生成Skeleton對象。(對于非UnicastRemoteObject的則會生成Skeleton沒啥爭議)。我的理解是必然會進行一些處理生成Skeleton對象。因為Registry只是用來查找服務,最終調用服務還是得要客戶端與服務的連接。這個連接必然由Skeleton為我們屏蔽了。
小結:前面我們做了很多工作,大量工作用于起Registry服務和如何查找客戶端需要調用的服務。但事實上,這個Registry可以服務于很多的其他服務。一旦客戶端和服務端通過Stub和Skeleton建立了socket連接,后面的操作直接通過這個連接完成就結了!

五、看看Skeleton和Stub如何為我們屏蔽底層連接細節

Stub類:

  • public?class?Person_Stub?implements?Person?{????????
  • ????private?Socket?socket;????????
  • ????public?Person_Stub()?throws?Throwable?{????????
  • ????????//?connect?to?skeleton????????
  • ????????socket?=?new?Socket("computer_name",?9000);????????
  • ????}????????
  • ????public?int?getAge()?throws?Throwable?{????????
  • ????????//?pass?method?name?to?skeleton????????
  • ????????ObjectOutputStream?outStream?=????????
  • ????????????new?ObjectOutputStream(socket.getOutputStream());????????
  • ????????outStream.writeObject("age");????????
  • ????????outStream.flush();????????
  • ????????ObjectInputStream?inStream?=????????
  • ????????????new?ObjectInputStream(socket.getInputStream());????????
  • ????????return?inStream.readInt();????????
  • ????}????????
  • ????public?String?getName()?throws?Throwable?{????????
  • ????????//?pass?method?name?to?skeleton????????
  • ????????ObjectOutputStream?outStream?=????????
  • ????????????new?ObjectOutputStream(socket.getOutputStream());????????
  • ????????outStream.writeObject("name");????????
  • ????????outStream.flush();????????
  • ????????ObjectInputStream?inStream?=????????
  • ????????????new?ObjectInputStream(socket.getInputStream());????????
  • ????????return?(String)inStream.readObject();????????
  • ????}??
  • } ??
  • 可以看到,Stub對象做的事情是建立到服務端Skeleton對象的Socket連接。將客戶端的方法調用轉換為字符串標識傳遞給Skeleton對象。并且同步阻塞等待服務端返回結果。

    Skeleton類:

  • public?class?Person_Skeleton?extends?Thread?{????????
  • ????private?PersonServer?myServer;????????
  • ????public?Person_Skeleton(PersonServer?server)?{????????
  • ????????//?get?reference?of?object?server????????
  • ????????this.myServer?=?server;????????
  • ????}????????
  • ????public?void?run()?{????????
  • ????????try?{????????
  • ????????????//?new?socket?at?port?9000????????
  • ????????????ServerSocket?serverSocket?=?new?ServerSocket(9000);????????
  • ????????????//?accept?stub's?request????????
  • ????????????Socket?socket?=?serverSocket.accept();????????
  • ????????????while?(socket?!=?null)?{????????
  • ????????????????//?get?stub's?request????????
  • ????????????????ObjectInputStream?inStream?=????????
  • ????????????????????new?ObjectInputStream(socket.getInputStream());????????
  • ????????????????String?method?=?(String)inStream.readObject();????????
  • ????????????????//?check?method?name????????
  • ????????????????if?(method.equals("age"))?{????????
  • ????????????????????//?execute?object?server's?business?method????????
  • ????????????????????int?age?=?myServer.getAge();????????
  • ????????????????????ObjectOutputStream?outStream?=????????
  • ????????????????????????new?ObjectOutputStream(socket.getOutputStream());????????
  • ????????????????????//?return?result?to?stub????????
  • ????????????????????outStream.writeInt(age);????????
  • ????????????????????outStream.flush();????????
  • ????????????????}????????
  • ????????????????if(method.equals("name"))?{????????
  • ????????????????????//?execute?object?server's?business?method????????
  • ????????????????????String?name?=?myServer.getName();????????
  • ????????????????????ObjectOutputStream?outStream?=????????
  • ????????????????????????new?ObjectOutputStream(socket.getOutputStream());????????
  • ????????????????????//?return?result?to?stub????????
  • ????????????????????outStream.writeObject(name);????????
  • ????????????????????outStream.flush();????????
  • ????????????????}????????
  • ????????????}????????
  • ????????}?catch(Throwable?t)?{????????
  • ????????????t.printStackTrace();????????
  • ????????????System.exit(0);????????
  • ????????}????????
  • ????}?????????????
  • } ?
  • Skeleton對象做的事情是將服務實現傳入構造參數,獲取客戶端通過socket傳過來的方法調用字符串標識,將請求轉發到具體的服務上面。獲取結果之后返回給客戶端。

    六、總結: 本來是Java一個很簡單的用法,用了將近3天看了這部分的內容,感覺收獲還是比較大的。阿里實習的時候,一個師兄曾經說過,看源代碼需要看到什么程度?老實說這玩意看起來真的太累了。??偹闶强赐炅?。我覺得這是走向分布式的比較重要的一步。由于篇幅的關系,沒有涉及到TCP連接的細節。有興趣的可以看看源碼。 “看到你覺得你能說服自己就可以了”。
    七、附注: 這里參考了這篇文章以及其引用的各篇文章。java RMI原理詳解
    致敬和感謝!

    總結

    以上是生活随笔為你收集整理的深究Java中的RMI底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。