很長時間以來都沒有怎麼好好搞清楚RPC(即Remote Procedure Call,遠程過程調用)和HTTP調用的區別,不都是寫一個服務而後在客戶端調用麼?這裏請容許我迷之一笑~Naive!本文簡單地介紹一下兩種形式的C/S架構,先說一下他們最本質的區別,就是RPC主要是基於TCP/IP協議的,而HTTP服務主要是基於HTTP協議的,咱們都知道HTTP協議是在傳輸層協議TCP之上的,因此效率來看的話,RPC固然是要更勝一籌啦!下面來具體說一說RPC服務和HTTP服務。javascript
爲何RPC呢?就是沒法在一個進程內,甚至一個計算機內經過本地調用的方式完成的需求,好比好比不一樣的系統間的通信,甚至不一樣的組織間的通信。因爲計算能力須要橫向擴展,須要在多臺機器組成的集羣上部署應用。
RPC的協議有不少,好比最先的CORBA,Java RMI,Web Service的RPC風格,Hessian,Thrift,甚至Rest API。php
關於RPC
RPC框架,首先了解什麼叫RPC,爲何要RPC,RPC是指遠程過程調用,也就是說兩臺服務器A,B,一個應用部署在A服務器上,想要調用B服務器上應用提供的函數/方法,因爲不在一個內存空間,不能直接調用,須要經過網絡來表達調用的語義和傳達調用的數據。css
好比說,一個方法多是這樣定義的:
Employee getEmployeeByName(String fullName)
那麼:html
在說RPC和HTTP的區別以前,我覺的有必要了解一下OSI的七層網絡結構模型(雖然實際應用中基本上都是五層),它能夠分爲如下幾層: (從上到下)java
實際應用過程當中,五層協議結構裏面是沒有表示層和會話層的。應該說它們和應用層合併了。咱們應該將重點放在應用層和傳輸層這兩個層面。由於HTTP是應用層協議,而TCP是傳輸層協議。好,知道了網絡的分層模型之後咱們能夠更好地理解爲何RPC服務相比HTTP服務要Nice一些!ios
從三個角度來介紹RPC服務:分別是RPC架構,同步異步調用以及流行的RPC框架。nginx
先說說RPC服務的基本架構吧。容許我可恥地盜一幅圖哈~咱們能夠很清楚地看到,一個完整的RPC架構裏面包含了四個核心的組件,分別是Client ,Server,Client Stub以及Server Stub,這個Stub你們能夠理解爲存根。分別說說這幾個組件:程序員
RPC主要是用在大型企業裏面,由於大型企業裏面系統繁多,業務線複雜,並且效率優點很是重要的一塊,這個時候RPC的優點就比較明顯了。實際的開發當中是這麼作的,項目通常使用maven來管理。好比咱們有一個處理訂單的系統服務,先聲明它的全部的接口(這裏就是具體指Java中的interface
),而後將整個項目打包爲一個jar
包,服務端這邊引入這個二方庫,而後實現相應的功能,客戶端這邊也只須要引入這個二方庫便可調用了。爲何這麼作?主要是爲了減小客戶端這邊的jar
包大小,由於每一次打包發佈的時候,jar
包太多老是會影響效率。另外也是將客戶端和服務端解耦,提升代碼的可移植性。web
什麼是同步調用?什麼是異步調用?同步調用
就是客戶端等待調用執行完成並返回結果。異步調用
就是客戶端不等待調用執行完成返回結果,不過依然能夠經過回調函數等接收到返回結果的通知。若是客戶端並不關心結果,則能夠變成一個單向的調用。這個過程有點相似於Java中的callable
和runnable
接口,咱們進行異步執行的時候,若是須要知道執行的結果,就可使用callable
接口,而且能夠經過Future
類獲取到異步執行的結果信息。若是不關心執行的結果,直接使用runnable
接口就能夠了,由於它不返回結果,固然啦,callable
也是能夠的,咱們不去獲取Future
就能夠了。ajax
目前流行的開源RPC框架仍是比較多的。下面重點介紹三種:
偷偷告訴你
集團內部已經不怎麼使用dubbo啦,如今用的比較多的叫HSF,又名「好舒服」。後面有可能會開源,你們拭目以待。
其實在好久之前,我對於企業開發的模式一直定性爲HTTP接口開發,也就是咱們常說的RESTful風格的服務接口。的確,對於在接口很少、系統與系統交互較少的狀況下,解決信息孤島初期常使用的一種通訊手段;優勢就是簡單、直接、開發方便。利用現成的http協議進行傳輸。咱們記得以前本科實習在公司作後臺開發的時候,主要就是進行接口的開發,還要寫一大份接口文檔,嚴格地標明輸入輸出是什麼?說清楚每個接口的請求方法,以及請求參數須要注意的事項等。好比下面這個例子:POST http://www.httpexample.com/restful/buyer/info/share
接口可能返回一個JSON字符串或者是XML文檔。而後客戶端再去處理這個返回的信息,從而能夠比較快速地進行開發。可是對於大型企業來講,內部子系統較多、接口很是多的狀況下,RPC框架的好處就顯示出來了,首先就是長連接,沒必要每次通訊都要像http同樣去3次握手什麼的,減小了網絡開銷;其次就是RPC框架通常都有註冊中心,有豐富的監控管理;發佈、下線接口、動態擴展等,對調用方來講是無感知、統一化的操做。
引自:https://blog.csdn.net/kkkloveyou/article/details/51874354
本文介紹了什麼是遠程過程調用(RPC),RPC 有哪些經常使用的方法,RPC 經歷了哪些發展階段,以及比較了各類 RPC 技術的優劣。
RPC 是遠程過程調用(Remote Procedure Call)的縮寫形式,Birrell 和 Nelson 在 1984 發表於 ACM Transactions on Computer Systems 的論文《Implementing remote procedure calls》對 RPC 作了經典的詮釋。RPC 是指計算機 A 上的進程,調用另一臺計算機 B 上的進程,其中 A 上的調用進程被掛起,而 B 上的被調用進程開始執行,當值返回給 A 時,A 進程繼續執行。調用方能夠經過使用參數將信息傳送給被調用方,然後能夠經過傳回的結果獲得信息。而這一過程,對於開發人員來講是透明的。
圖1 描述了數據報在一個簡單的RPC傳遞的過程
注:上述論文,能夠在線閱讀 http://www.cs.virginia.edu/~zaher/classes/CS656/birrel.pdf。
遠程過程調用採用客戶機/服務器(C/S)模式。請求程序就是一個客戶機,而服務提供程序就是一臺服務器。和常規或本地過程調用同樣,遠程過程調用是同步操做,在遠程過程結果返回以前,須要暫時停止請求程序。使用相同地址空間的低權進程或低權線程容許同時運行多個遠程過程調用。
讓咱們看看本地過程調用是如何實現的。考慮下面的 C 語言的調用:
count = read(fd, buf, nbytes);
其中,fd 爲一個整型數,表示一個文件。buf 爲一個字符數組,用於存儲讀入的數據。 nbytes 爲另外一個整型數,用於記錄實際讀入的字節數。若是該調用位於主程序中,那麼在調用以前堆棧的狀態如圖2(a)所示。爲了進行調用,調用方首先把參數反序壓入堆棧,即爲最後一個參數先壓入,如圖2(b)所示。在 read 操做運行完畢後,它將返回值放在某個寄存器中,移出返回地址,並將控制權交回給調用方。調用方隨後將參數從堆棧中移出,使堆棧還原到最初的狀態。
圖2 過程調用中的參數傳遞
RPC 背後的思想是儘可能使遠程過程調用具備與本地調用相同的形式。假設程序須要從某個文件讀取數據,程序員在代碼中執行 read 調用來取得數據。在傳統的系統中, read 例程由連接器從庫中提取出來,而後連接器再將它插入目標程序中。 read 過程是一個短過程,通常經過執行一個等效的 read 系統調用來實現。即,read 過程是一個位於用戶代碼與本地操做系統之間的接口。
雖然 read 中執行了系統調用,但它自己依然是經過將參數壓入堆棧的常規方式調用的。如圖2(b)所示,程序員並不知道 read 幹了啥。
RPC 是經過相似的途徑來得到透明性。當 read 其實是一個遠程過程時(好比在文件服務器所在的機器上運行的過程),庫中就放入 read 的另一個版本,稱爲客戶存根(client stub)。這種版本的 read 過程一樣遵循圖2(b)的調用次序,這點與原來的 read 過程相同。另外一個相同點是其中也執行了本地操做系統調用。惟一不一樣點是它不要求操做系統提供數據,而是將參數打包成消息,然後請求此消息發送到服務器,如圖3所示。在對 send 的調用後,客戶存根調用 receive 過程,隨即阻塞本身,直到收到響應消息。
圖3 客戶與服務器之間的RPC原理
當消息到達服務器時,服務器上的操做系統將它傳遞給服務器存根(server stub)。服務器存根是客戶存根在服務器端的等價物,也是一段代碼,用來將經過網絡輸入的請求轉換爲本地過程調用。服務器存根通常先調用 receive ,而後被阻塞,等待消息輸入。收到消息後,服務器將參數由消息中提取出來,而後以常規方式調用服務器上的相應過程(如圖3所示)。從服務器角度看,過程好像是由客戶直接調用的同樣:參數和返回地址都位於堆棧中,一切都很正常。服務器執行所要求的操做,隨後將獲得的結果以常規的方式返回給調用方。以 read 爲例,服務器將用數據填充 read 中第二個參數指向的緩衝區,該緩存區是屬於服務器存根內部的。
調用完後,服務器存根要將控制權教會給客戶發出調用的過程,它將結果(緩衝區)打包成消息,隨後調用 send 將結果返回給客戶。過後,服務器存根通常會再次調用 receive,等待下一個輸入的請求。
客戶機器接收到消息後,客戶操做系統發現該消息屬於某個客戶進程(實際上該進程是客戶存根,知識操做系統沒法區分兩者)。操做系統將消息複製到相應的緩存區中,隨後解除對客戶進程的阻塞。客戶存根檢查該消息,將結果提取出來並複製給調用者,然後以一般的方式返回。當調用者在 read 調用進行完畢後從新得到控制權時,它所知道的惟一事就是已經獲得了所需的數據。它不指導操做是在本地操做系統進行,仍是遠程完成。
整個方法,客戶方能夠簡單地忽略不關心的內容。客戶所涉及的操做只是執行普通的(本地)過程調用來訪問遠程服務,它並不須要直接調用 send 和 receive 。消息傳遞的全部細節都隱藏在雙方的庫過程當中,就像傳統庫隱藏了執行實際系統調用的細節同樣。
概況來講,遠程過程調用包含以下步驟:
以上步驟就是將客戶過程對客戶存根發出的本地調用轉換成對服務器過程的本地調用,而客戶端和服務器都不會意識到中間步驟的存在。
RPC 的主要好處是雙重的。首先,程序員可使用過程調用語義來調用遠程函數並獲取響應。其次,簡化了編寫分佈式應用程序的難度,由於 RPC 隱藏了全部的網絡代碼存根函數。應用程序沒必要擔憂一些細節,好比 socket、端口號以及數據的轉換和解析。在 OSI 參考模型,RPC 跨越了會話層和表示層。
要實現遠程過程調用,需考慮如下幾個問題。
傳遞值參數比較簡單,下圖圖展現了一個簡單 RPC 進行遠程計算的例子。其中,遠程過程 add(i,j) 有兩個參數 i 和 j, 其結果是返回 i 和 j 的算術和。
圖4 經過RPC進行遠程計算的步驟
經過 RPC 進行遠程計算的步驟有:
固然,這裏只是作了簡單的演示,在實際分佈式系統中,還須要考慮其餘狀況,由於不一樣的機器對於數字、字符和其餘類型的數據項的表示方式常有差別。好比整數型,就有 Big Endian 和 Little Endian 之分。
傳遞引用參數相對來講比較困難。單純傳遞參數的引用(也包含指針)是徹底沒有意義的,由於引用地址傳遞給遠程計算機,其指向的內存位置可能跟遠程系統上徹底不一樣。若是你想支持傳遞引用參數,你必須發送參數的副本,將它們放置在遠程系統內存中,向他們傳遞一個指向服務器函數的指針,而後將對象發送回客戶端,複製它的引用。若是遠程過程調用必須支持引用複雜的結構,好比樹和鏈表,他們須要將結構複製到一個無指針的表示裏面(好比,一個扁平的樹),並傳輸到在遠程端來重建數據結構。
在本地系統上不存在數據不相容的問題,由於數據格式老是相同的。而在分佈式系統中,不一樣遠程機器上可能有不一樣的字節順序,不一樣大小的整數,以及不一樣的浮點表示。對於 RPC,若是想與異構系統通訊,咱們就須要想出一個「標準」來對全部數據類型進行編碼,並能夠做爲參數傳遞。例如,ONC RPC 使用 XDR (eXternal Data Representation) 格式 。這些數據表示格式可使用隱式或顯式類型。隱式類型,是指只傳遞值,而不傳遞變量的名稱或類型。常見的例子是 ONC RPC 的 XDR 和 DCE RPC 的 NDR。顯式類型,指須要傳遞每一個字段的類型以及值。常見的例子是 ISO 標準 ASN.1 (Abstract Syntax Notation)、JSON (JavaScript Object Notation)、Google Protocol Buffers、以及各類基於 XML 的數據表示格式。
有些實現只容許使用一個協議(例如 TCP )。大多數 RPC 實現支持幾個,並容許用戶選擇。
相比於本地過程調用,遠程過程調用出錯的機會將會更多。因爲本地過程調用沒有過程調用失敗的概念,項目使用遠程過程調用必須準備測試遠程過程調用的失敗或捕獲異常。
調用一個普通的過程語義很簡單:當咱們調用時,過程被執行。遠程過程徹底一次性調用成功是很是難以實現。執行遠程過程能夠有以下結果:
RPC 系統一般會提供至少一次或最多一次的語義,或者在二者之間選擇。若是須要了解應用程序的性質和遠程過程的功能是否安全,能夠經過屢次調用同一個函數來驗證。若是一個函數能夠運行任何次數而不影響結果,這是冪等(idempotent)函數的,如天天的時間、數學函數、讀取靜態數據等。不然,它是一個非冪等(nonidempotent)函數,如添加或修改一個文件)。
毫無疑問,一個遠程過程調用將會比常規的本地過程調用慢得多,由於產生了額外的步驟以及網絡傳輸自己存在延遲。然而,這並不該該阻止咱們使用遠程過程調用。
使用 RPC,咱們必須關注各類安全問題:
遠程過程調用有諸多的優勢:
任何 RPC 實現都須要提供一組支持庫。這些包括:
因此,判斷一種通訊方式是不是 RPC,就看它是否提供上述的 API。
Sun 公司是第一個提供商業化 RPC 庫和 RPC 編譯器。在1980年代中期 Sun 計算機提供 RPC,並在 Sun Network File System(NFS) 獲得支持。該協議被主要以 Sun 和 AT&T 爲首的 Open Network Computing (開放網絡計算)做爲一個標準來推進。這是一個很是輕量級 RPC 系統可用在大多數 POSIX 和類 POSIX 操做系統中使用,包括 Linux、SunOS、OS X 和各類發佈版本的 BSD。這樣的系統被稱爲 Sun RPC 或 ONC RPC。
ONC RPC 提供了一個編譯器,須要一個遠程過程接口的定義來生成客戶機和服務器的存根函數。這個編譯器叫作 rpcgen。在運行此編譯器以前,程序員必須提供接口定義。包含函數聲明的接口定義,經過版本號進行分組,並被一個獨特的程序編碼來標識。該程序編碼可以讓客戶來肯定所需的接口。版本號是很是有用的,即便客戶沒有更新到最新的代碼仍然能夠鏈接到一個新的服務器,只要該服務器還支持舊接口。
參數經過網絡轉化成一種隱式類型序列化格式被稱爲 XDR (eXternal Data Representation)。這將確保參數可以發送到異構系統能夠被正常使用,及時這些系統可能使用了不一樣的字節順序,不一樣大小的整數,或不一樣的浮點或字符串表示。最後,Sun RPC 提供了一個實現必要的支持 RPC 協議和 socket 例程的運行時庫。
全部的程序員都須要寫是一個客戶端程序(client.c),服務器功能(server.c)和 RPC 接口定義(date.x)。當 RPC 接口定義(後綴爲.x 的文件,例如 date.x)是用 rpcgen 編譯的,會建立三個或四個文件。下面是 date.x 的例子:
建立客戶端和服務器可執行文件的第一步是定義在文件 date.x 裏的編譯數據。以後,客戶端和服務器端函數可能被編譯,並連接各自 rpcgen 生成的存根函數。
在舊版本里,傳輸協議只能將字符串「tcp」或字符串「udp」來指定各自的 IP 服務 RPC,且僅限於 Linux 實現的 RPC。爲了使接口更加靈活,UNIX 系統從版本 4 (SunOS 從版本 5)開始網絡選擇程序容許一個更普通的規範。他們搜索文件(/etc/netconfig),來查找第一個知足您需求的提供者。最後一個參數能夠是:
每一個遠程過程調用最初僅限於接受一個輸入參數。系統只是後來修改成支持多個參數。支持單一參數 RPC 在一些 rpcgen 的版本中仍然是默認的,好比蘋果的 OS X。傳遞多個參數必須經過定義一個結構,包含所需的參數,初始化它,並傳遞這個結構。
遠程過程調用返回一個指針指向結果而不是指望的結果。服務器函數必須修改來能接受一個 RPC 定義(.x 文件)中聲明的值的指針做爲輸入,並返回一個結果值的指針。在服務器上,一個指針必須是指向靜態數據的指針。不然,當過程返回或釋放過程的框架所指出的區域將未定義。在客戶端,返回指針可讓咱們區分一個失敗的 RPC(空指針)和一個空返回從服務器(間接空指針)。
RPC 過程的名稱若在 RPC 定義文件中作了定義,則會轉換爲小寫,並在後綴價下劃線,後跟一個版本號。例如,BIN_DATE 轉成爲引用函數 bin_date_1 。您的服務器必須實現 bin_date_1。
當咱們啓動服務器,服務器存根代碼(程序)在後臺運行運行。它建立一個 socket 並可綁定任何本地端口到 socket。而後調用一個在 RPC 庫的函數 svc_register,來註冊程序編號和版本。這個是用來聯繫 port mapper(端口映射器)。port mapper 是一個獨立的進程,一般是在系統啓動時啓動。它跟蹤端口號、版本號以及程序編號。在 UNIX 系統版本4中,這個進程稱爲 rpcbind。在 Linux 、OS X 和 BSD 系統,它被稱爲 portmap。
圖5 ONC RPC 中的函數查找
當咱們開始客戶端程序時,它首先用遠程系統的名稱、程序編號、版本號和協議來調用 clnt_create 。它接觸遠程系統上的端口映射器,來爲系統找到合適的端口。
而後客戶端調用 RPC 存根函數(在本例中爲 bin_date_1)。該函數發送一條消息(如,數據報)到服務器(使用早些時候發現的端口號)並等待響應。對於數據報服務來講,若沒有接收到響應,它將從新發送一個固定的次數請求。
消息接着被遠程系統接收到,它調用服務器函數(bin_date_1)並將返回值返回給客戶端存根。客戶端存根然後返回到客戶端發出調用的代碼。
DCE(Distributed Computing Environment,分佈式計算環境)是一組由OFS(Open Software Foundation,開放軟件基金會)設計的組件,用來提供支持分佈式應用和分佈式環境。與 X/Open 合併後,這組織成爲了 The Open Group (開放式開發組)。DCE 提供的組件包括一個分佈式文件服務、時間服務、目錄服務以及其餘服務。固然,咱們感興趣的是 DCE 的遠程過程調用。它很是相似於 Sun RPC。接口是由 Interface Definition Notation (IDN) 定義的。相似於 Sun RPC,接口定義就像函數原型。
Sun RPC 不足之處在於,服務器的標識是一個「獨特」的 32-bit 數字。雖然這是一個比在 socket 中 16-bit 可用空間更大的空間,但仍然沒法知足數字惟一性的需求。DCE RPC 考慮到了這一缺陷,它無需程序員來處理編碼。在編寫應用程序時的第一步是從 uuidgen 程序得到一個唯一的 ID。這個程序會生成一個包含 ID 接口的原型 IDN 文件,並保證永遠不會再次使用。它是一個 128-bit 的值,其中包含一個位置代碼和建立時間的編碼。而後用戶編輯原型文件,填寫遠程過程聲明。
在這一步後,IDN 的編譯器 dceidl(相似於 rpcgen)會生成一個頭、客戶機存根和服務器存根。
Sun RPC 的另外一個缺陷是,客戶端必須知道服務器在哪臺機器上。當它要訪問時,必需要詢問機器上的 RPC 名稱服務程序編碼所對應的端口號。DCE 支持將多個機器組織成爲管理實體,稱爲 cells。cell 目錄服務器使得每臺機器知道如何與另一臺負責維護 cell 信息服務機器交互。
在 Sun RPC 中,服務器只能用本地名稱服務(端口映射器)來註冊其程序編號到端口映射。而在 DCE 中,服務器用 RPC 守護進程(名稱服務器)來註冊其端點(端口)到本地機器,而且用 cell 目錄服務器註冊其程序名字到機器的映射。當客戶機想要與一個 RPC 服務器創建通訊,它首先要求其 cell 目錄服務器來定位服務器所在的機器。而後客戶端從 RPC 守護進程處得到機器上服務器進程的端口號。DCE 的跨 cell 還支持更復雜的搜索。
DCE RPC 定義了 NDR (Network Data Representation) 用於對網絡進行編碼來封送信息。與用一個單一的規範來表示不一樣的數據類型相比,NDR 支持多規範(multi-canonical)格式。容許客戶端來選擇使用哪一種格式,理想的狀況是不須要將它從本地類型來轉換。若是這不一樣於服務器的本地數據表示,服務器將仍然須要轉換,但多規範格式能夠避免當客戶端和服務器都共享相同的本地格式的狀況下轉換爲其餘外部格式。例如,在一個規定了大端字節序網絡數據格式的狀況下,客戶端和服務器只支持小端字節序,那麼客戶端必須將每一個數據從小端字節序轉爲大端字節序,而當服務器接受到消息後,將每一個數據轉回小端字節序。多規範網絡數據表示將容許客戶端發送網絡消息包含小端字節序格式的數據。
圖6 DCE RPC 中的函數查找
面向對象的語言開始在1980年代末興起,很明顯,當時的 Sun ONC 和 DCE RPC 系統都沒有提供任何支持諸如從遠程類實例化遠程對象、跟蹤對象的實例或提供支持多態性。現有的 RPC 機制雖然能夠運做,但他們仍然不支持自動、透明的方式的面向對象編程技術。
1992年4月,微軟發佈 Windows 3.1 包括一種機制稱爲 OLE (Object Linking and Embedding)。這容許一個程序動態連接其餘庫來支持的其餘功能。如將一個電子表格嵌入到 Word 文檔。OLE 演變成了 COM (Component Object Model)。一個 COM 對象是一個二進制文件。使用 COM 服務的程序來訪問標準化接口的 COM 對象而不是其內部結構。COM 對象用全局惟一標識符(GUID)來命名,用類的 ID 來識別對象的類。幾種方法來建立一個 COM 對象(例如 CoGetInstanceFromFile)。COM 庫在系統註冊表中查找相應的二進制代碼(一個 DLL 或可執行文件),來建立對象,並給調用者返回一個接口指針。COM 的着眼點是在於同一臺計算機上不一樣應用程序之間的通信需求.
DCOM( Distributed Component Object Model)是 COM 的擴展,它支持不一樣的兩臺機器上的組件間的通訊,並且不論它們是運行在局域網、廣域網、仍是 Internet 上。藉助 DCOM 你的應用程序將可以進行任意空間分佈。DCOM 於1996年在 Windows NT4.0 中引入的,後來改名爲 COM+。因爲 DCOM 是爲了支持訪問遠程 COM 對象,須要建立一個對象的過程,此時須要提供服務器的網絡名以及類 ID。微軟提供了一些機制來實現這一點。最透明的方式是遠程計算機的名稱固定在註冊表(或 DCOM 類存儲)裏,與特定類 ID 相關聯。以此方式,應用程序不知道它正在訪問一個遠程對象,而且可使用與訪問本地 COM 對象相同的接口指針。另外一方面,應用程序也可指定一個機器名做爲參數。
因爲 DCOM 是 COM 這個組件技術的無縫升級,因此你可以從你現有的有關 COM 得知識中獲益,你的之前在 COM 中開發的應用程序、組件、工具均可以移入分佈式的環境中。DCOM 將爲你屏蔽底層網絡協議的細節,你只須要集中精力於你的應用。
DCOM 最大的缺點是這是微軟獨家的解決辦法,在跨防火牆方面的工做作得不是很好(大多數RPC系統也有相似的問題),由於防火牆必須容許某些端口來容許 ORPC 和 DCOM 經過。
雖然 DCE 修復的一些 Sun RPC 的缺點,但某些缺陷依然存在。例如,若是服務器沒有運行,客戶端是沒法鏈接到遠程過程進行調用的。管理員必需要確保在任何客戶端試圖鏈接到服務器以前將服務器啓動。若是一個新服務或接口添加到了系統,客戶端是不能發現的。最後,面嚮對象語言指望在函數調用中體現多態性,即不一樣類型的數據的函數的行爲應該有所不一樣,而這點偏偏是傳統的 RPC 所不支持的。
CORBA (Common Object Request Broker Architecture) 就是爲了解決上面提到的各類問題。是由 OMG 組織制訂的一種標準的面向對象應用程 序體系規範。或者說 CORBA體系結構是對象管理組織(OMG)爲解決分佈式處理環境(DCE)中,硬件和軟件系統的互連而提出的一種解決方案。OMG 成立於1989年,做爲一個非營利性組織,集中致力於開發在技術上具備先進性、在商業上具備可行性而且獨立於廠商的軟件互聯規範,推廣面向對象模型技術,加強軟件的可移植性(Portability)、可重用性(Reusability)和互操做性(Interoperability)。該組織成立之初,成員包括 Unisys、Sun、Cannon、Hewlett-Packard 和 Philips 等在業界享有聲譽的軟硬件廠商,目前該組織擁有800多家成員。
CORBA 體系的主要內容包括如下幾部分:
當客戶端發出請求時,ORB 作了以下事情:
IDL(Interface Definition Language) 是用於指定類的名字、屬性和方法。它不包含對象的實現。IDL 編譯器生成代碼來處理編組、解封以及ORB與網絡之間的交互。它會生成客戶機和服務器存根。IDL 是編程語言中立,支持包括C、C++、Java、Perl、Python、Ada、COBOL、Smalltalk、Objective C 和 LISP 等語言。一個示例IDL以下所示:
Module StudentObject {
Struct StudentInfo {
String name;
int id; float gpa; }; exception Unknown {}; interface Student { StudentInfo getinfo(in string name) raises(unknown); void putinfo(in StudentInfo data); }; };
IDL數據類型包括:
編程中最多見的實現方式是經過對象引用來實現請求。下面是一個使用 IDL 的例子:
Student st = ... // get object reference try { StudentInfo sinfo = st.getinfo("Fred Grampp"); } catch (Throwable e) { ... // error }
在 CORBA 規範中,沒有明確說明不一樣廠商的中間件產品要實現全部的服務功能,而且容許廠商開發本身的服務類型。所以, 不一樣廠商的 ORB 產品對 CORBA 服務的支持能力不一樣,使咱們在針對待開發系統的功能進行中間件產品選擇時,有更多的選擇餘地。
CORBA 的不足有:
更多有關 CORBA 的優缺點,能夠參閱 Michi Henning 的《The rise and fall of CORBA》。
CORBA 旨在提供一組全面的服務來管理在異構環境中(不一樣語言、操做系統、網絡)的對象。Java 在其最初只支持經過 socket 來實現分佈式通訊。1995年,做爲 Java 的締造者,Sun 公司開始建立一個 Java 的擴展,稱爲 Java RMI(Remote Method Invocation,遠程方法調用)。Java RMI 容許程序員建立分佈式應用程序時,能夠從其餘 Java 虛擬機(JVM)調用遠程對象的方法。
一旦應用程序(客戶端)引用了遠程對象,就能夠進行遠程調用了。這是經過 RMI 提供的命名服務(RMI 註冊中心)來查找遠程對象,來接收做爲返回值的引用。Java RMI 在概念上相似於 RPC,但能在不一樣地址空間支持對象調用的語義。
與大多數其餘諸如 CORBA 的 RPC 系統不一樣,RMI 只支持基於 Java 來構建,但也正是這個緣由, RMI 對於語言來講更加整潔,無需作額外的數據序列化工做。Java RMI 的設計目標應該是:
分佈式對象模型與本地 Java 對象模型類似點在於:
不一樣點在於:
全部的遠程接口都繼承自 java.rmi.Remote
接口。例如:
public interface bankaccount extends Remote { public void deposit(float amount) throws java.rmi.RemoteException; public void withdraw(float amount) throws OverdrawnException, java.rmi.RemoteException; }
注意,每一個方法必須在 throws 裏面聲明 java.rmi.RemoteException
。 只要客戶端調用遠程方法出現失敗,這個異常就會拋出。
Java.rmi.server.RemoteObject
類提供了遠程對象實現的語義包括hashCode、equals 和 toString。 java.rmi.server.RemoteServer
及其子類提供讓對象實現遠程可見。java.rmi.server.UnicastRemoteObject
類定義了客戶機與服務器對象實例創建一對一的鏈接.
Java RMI 經過建立存根函數來工做。存根由 rmic 編譯器生成。自 Java 1.5 以來,Java 支持在運行時動態生成存根類。編譯器 rmic 會提供各類編譯選項。
引導名稱服務提供了用於存儲對遠程對象的命名引用。一個遠程對象引用能夠存儲使用類 java.rmi.Naming
提供的基於 URL 的方法。例如,
BankAccount acct = new BankAcctImpl(); String url = "rmi://java.sun.com/account"; // bind url to remote object java.rmi.Naming.bind(url, acct); // look up account acct = (BankAccount)java.rmi.Naming.lookup(url);
圖7 Java RMI 工做流程
RMI 是一個三層架構(圖8)。最上面是 Stub/Skeleton layer(存根/骨架層)。方法調用從 Stub、Remote Reference Layer (遠程引用層)和 Transport Layer(傳輸層)向下,傳遞給主機,而後再次經傳 Transport Layer 層,向上穿過 Remote Reference Layer 和 Skeleton ,到達服務器對象。 Stub 扮演着遠程服務器對象的代理的角色,使該對象可被客戶激活。Remote Reference Layer 處理語義、管理單一或多重對象的通訊,決定調用是應發往一個服務器仍是多個。Transport Layer 管理實際的鏈接,而且追蹤能夠接受方法調用的遠程對象。服務器端的 Skeleton 完成對服務器對象實際的方法調用,並獲取返回值。返回值向下經 Remote Reference Layer 、服務器端的 Transport Layer 傳遞迴客戶端,再向上經 Transport Layer 和 Remote Reference Layer 返回。最後,Stub 程序得到返回值。
要完成以上步驟須要有如下幾個步驟:
圖8 Java RMI 架構
根據 Java 虛擬機的垃圾回收機制原理,在分佈式環境下,服務器進程須要知道哪些對象再也不由客戶端引用,從而能夠被刪除(垃圾回收)。在 JVM中,Java 使用引用計數。當引用計數歸零時,對象將會垃圾回收。在RMI,Java 支持兩種操做:dirty 和 clean。本地 JVM 按期發送一個 dirty 到服務器來講明該對象仍在使用。按期重發 dirty 的週期是由服務器租賃時間來決定的。當客戶端沒有須要更多的本地引用遠程對象時,它發送一個 clean 調用給服務器。不像 DCOM,服務器不須要計算每一個客戶機使用的對象,只是簡單的作下通知。若是它租賃時間到期以前沒有接收到任何 dirty 或者 clean 的消息,則能夠安排將對象刪除。
因爲互聯網的興起,Web 瀏覽器成爲占主導地位的用於訪問信息的模型。如今的應用設計的首要任務大多數是提供用戶經過瀏覽器來訪問,而不是編程訪問或操做數據。
網頁設計關注的是內容。解析展示方面每每是繁瑣的。傳統 RPC 解決方案能夠工做在互聯網上,但問題是,他們一般嚴重依賴於動態端口分配,每每要進行額外的防火牆配置。
Web Services 成爲一組協議,容許服務被髮布、發現,並用於技術無關的形式。即服務不該該依賴於客戶的語言、操做系統或機器架構。
Web Services 的實現通常是使用 Web 服務器做爲服務請求的管道。客戶端訪問該服務,首先是經過一個 HTTP 協議發送請求到服務器上的 Web 服務器。Web 服務器配置識別 URL 的一部分路徑名或文件名後綴並將請求傳遞給特定的瀏覽器插件模塊。這個模塊能夠除去頭、解析數據(若是須要),並根據須要調用其餘函數或模塊。對於這個實現流,一個常見的例子是瀏覽器對於 Java Servlet 的支持。HTTP 請求會被轉發到 JVM 運行的服務端代碼來執行處理。
XML-RPC 是1998年做爲一個 RPC 消息傳遞協議,將請求和響應封裝解析爲人類可讀的 XML格式。XML 格式基於 HTTP 協議,緩解了傳統企業的防火牆須要爲 RPC 服務器應用程序打開額外的端口的問題。
下面是一個 XML-RPC 消息的例子:
<methodCall> <methodName> sample.sumAndDifference </methodName> <params> <param><value><int> 5 </int></value></param> <param><value><int> 3 </int></value></param> </params> </methodCall>
這個例子中,方法 sumAndDifference 有兩個整數參數 5 和 3。
XML-RPC 支持的基本數據類型是:int、string、boolean、double 和 dateTime.iso8601。此外,還有 base64 類型用於編碼任意二進制數據。array 和 struct 容許定義數組和結構。
XML-RPC 不限制語任何特定的語言,也不是一套完整的軟件來處理遠程過程,諸如存根生成、對象管理和服務查找都不在協議內。如今有不少庫針能夠針對不一樣的語言,好比 Apache XML-RPC 能夠用於 Java、Python 和 Perl。
XML-RPC 是一個簡單的規範(約7頁),沒有雄心勃勃的目標——它只關注消息,而並不處理諸如垃圾收集、遠程對象、遠程過程的名稱服務和其餘方面的問題。然而,即便沒有普遍的產業支持,簡單的協議卻能普遍採用。
SOAP(Simple Object Access Protocol,簡單對象訪問協議),是以 XML-RPC 規範做爲建立 SOAP 的依據,成立於1998年,得到微軟和 IBM 的大力支持。該協議在建立初期只做爲一種對象訪問協議,但因爲 SOAP 的發展,其協議已經不單只是用於簡單的訪問對象,因此這種 SOAP 縮寫已經在標準的1.2版後被廢止了。1.2版在2003年6月24日成爲 W3C 的推薦版本。SOAP 指定 XML 做爲無狀態的消息交換格式,包括了 RPC 式的過程調用。
有關 SOAP 的標準能夠參閱 https://www.w3.org/TR/soap/。
SOAP 只是一種消息格式,並未定義垃圾回收、對象引用、存根生成和傳輸協議。
下面是一個簡單的例子:
<?xml version='1.0' ?> <env:Envelope xmlns:env="http://www.w3.org/2003/05/soap-envelope"> <env:Header> <m:reservation xmlns:m="http://travelcompany.example.org/reservation" env:role="http://www.w3.org/2003/05/soap-envelope/role/next" env:mustUnderstand="true"> <m:reference>uuid:093a2da1-q345-739r-ba5d-pqff98fe8j7d</m:reference> <m:dateAndTime>2001-11-29T13:20:00.000-05:00</m:dateAndTime> </m:reservation> <n:passenger xmlns:n="http://mycompany.example.com/employees" env:role="http://www.w3.org/2003/05/soap-envelope/role/next" env:mustUnderstand="true"> <n:name>Åke Jógvan Øyvind</n:name> </n:passenger> </env:Header> <env:Body> <p:itinerary xmlns:p="http://travelcompany.example.org/reservation/travel"> <p:departure> <p:departing>New York</p:departing> <p:arriving>Los Angeles</p:arriving> <p:departureDate>2001-12-14</p:departureDate> <p:departureTime>late afternoon</p:departureTime> <p:seatPreference>aisle</p:seatPreference> </p:departure> <p:return> <p:departing>Los Angeles</p:departing> <p:arriving>New York</p:arriving> <p:departureDate>2001-12-20</p:departureDate> <p:departureTime>mid-morning</p:departureTime> <p:seatPreference/> </p:return> </p:itinerary>