很長時間以來都沒有怎么好好搞清楚RPC(即Remote Procedure Call,遠程過程調用)和HTTP調用的區別,不都是寫一個服務然后在客戶端調用么?這里請允許我迷之一笑~Naive!本文簡單地介紹一下兩種形式的C/S架構,先說一下他們最本質的區別,就是RPC主要是基於TCP/IP協議的,而HTTP服務主要是基於HTTP協議的,我們都知道HTTP協議是在傳輸層協議TCP之上的,所以效率來看的話,RPC當然是要更勝一籌啦!下面來具體說一說RPC服務和HTTP服務。
為什么RPC呢?就是無法在一個進程內,甚至一個計算機內通過本地調用的方式完成的需求,比如比如不同的系統間的通訊,甚至不同的組織間的通訊。由於計算能力需要橫向擴展,需要在多台機器組成的集群上部署應用。
RPC的協議有很多,比如最早的CORBA,Java RMI,Web Service的RPC風格,Hessian,Thrift,甚至Rest API。
關於RPC
RPC框架,首先了解什么叫RPC,為什么要RPC,RPC是指遠程過程調用,也就是說兩台服務器A,B,一個應用部署在A服務器上,想要調用B服務器上應用提供的函數/方法,由於不在一個內存空間,不能直接調用,需要通過網絡來表達調用的語義和傳達調用的數據。
比如說,一個方法可能是這樣定義的:
Employee getEmployeeByName(String fullName)
那么:
- 首先,要解決通訊的問題,主要是通過在客戶端和服務器之間建立TCP連接,遠程過程調用的所有交換的數據都在這個連接里傳輸。連接可以是按需連接,調用結束后就斷掉,也可以是長連接,多個遠程過程調用共享同一個連接。
- 第二,要解決尋址的問題,也就是說,A服務器上的應用怎么告訴底層的RPC框架,如何連接到B服務器(如主機或IP地址)以及特定的端口,方法的名稱名稱是什么,這樣才能完成調用。比如基於Web服務協議棧的RPC,就要提供一個endpoint URI,或者是從UDDI服務上查找。如果是RMI調用的話,還需要一個RMI Registry來注冊服務的地址。
- 第三,當A服務器上的應用發起遠程過程調用時,方法的參數需要通過底層的網絡協議如TCP傳遞到B服務器,由於網絡協議是基於二進制的,內存中的參數的值要序列化成二進制的形式,也就是序列化(Serialize)或編組(marshal),通過尋址和傳輸將序列化的二進制發送給B服務器。
- 第四,B服務器收到請求后,需要對參數進行反序列化(序列化的逆操作),恢復為內存中的表達方式,然后找到對應的方法(尋址的一部分)進行本地調用,然后得到返回值。
- 第五,返回值還要發送回服務器A上的應用,也要經過序列化的方式發送,服務器A接到后,再反序列化,恢復為內存中的表達方式,交給A服務器上的應用
OSI網絡七層模型
在說RPC和HTTP的區別之前,我覺的有必要了解一下OSI的七層網絡結構模型(雖然實際應用中基本上都是五層),它可以分為以下幾層: (從上到下)
- 第一層:應用層。定義了用於在網絡中進行通信和傳輸數據的接口;
- 第二層:表示層。定義不同的系統中數據的傳輸格式,編碼和解碼規范等;
- 第三層:會話層。管理用戶的會話,控制用戶間邏輯連接的建立和中斷;
- 第四層:傳輸層。管理着網絡中的端到端的數據傳輸;
- 第五層:網絡層。定義網絡設備間如何傳輸數據;
- 第六層:鏈路層。將上面的網絡層的數據包封裝成數據幀,便於物理層傳輸;
- 第七層:物理層。這一層主要就是傳輸這些二進制數據。
實際應用過程中,五層協議結構里面是沒有表示層和會話層的。應該說它們和應用層合並了。我們應該將重點放在應用層和傳輸層這兩個層面。因為HTTP是應用層協議,而TCP是傳輸層協議。好,知道了網絡的分層模型以后我們可以更好地理解為什么RPC服務相比HTTP服務要Nice一些!
RPC服務
從三個角度來介紹RPC服務:分別是RPC架構,同步異步調用以及流行的RPC框架。
RPC架構
先說說RPC服務的基本架構吧。允許我可恥地盜一幅圖哈~我們可以很清楚地看到,一個完整的RPC架構里面包含了四個核心的組件,分別是Client ,Server,Client Stub以及Server Stub,這個Stub大家可以理解為存根。分別說說這幾個組件:
- 客戶端(Client),服務的調用方。
- 服務端(Server),真正的服務提供者。
- 客戶端存根,存放服務端的地址消息,再將客戶端的請求參數打包成網絡消息,然后通過網絡遠程發送給服務方。
- 服務端存根,接收客戶端發送過來的消息,將消息解包,並調用本地的方法。
RPC主要是用在大型企業里面,因為大型企業里面系統繁多,業務線復雜,而且效率優勢非常重要的一塊,這個時候RPC的優勢就比較明顯了。實際的開發當中是這么做的,項目一般使用maven來管理。比如我們有一個處理訂單的系統服務,先聲明它的所有的接口(這里就是具體指Java中的interface
),然后將整個項目打包為一個jar
包,服務端這邊引入這個二方庫,然后實現相應的功能,客戶端這邊也只需要引入這個二方庫即可調用了。為什么這么做?主要是為了減少客戶端這邊的jar
包大小,因為每一次打包發布的時候,jar
包太多總是會影響效率。另外也是將客戶端和服務端解耦,提高代碼的可移植性。
同步調用與異步調用
什么是同步調用?什么是異步調用?同步調用
就是客戶端等待調用執行完成並返回結果。異步調用
就是客戶端不等待調用執行完成返回結果,不過依然可以通過回調函數等接收到返回結果的通知。如果客戶端並不關心結果,則可以變成一個單向的調用。這個過程有點類似於Java中的callable
和runnable
接口,我們進行異步執行的時候,如果需要知道執行的結果,就可以使用callable
接口,並且可以通過Future
類獲取到異步執行的結果信息。如果不關心執行的結果,直接使用runnable
接口就可以了,因為它不返回結果,當然啦,callable
也是可以的,我們不去獲取Future
就可以了。
流行的RPC框架
目前流行的開源RPC框架還是比較多的。下面重點介紹三種:
- gRPC是Google最近公布的開源軟件,基於最新的HTTP2.0協議,並支持常見的眾多編程語言。 我們知道HTTP2.0是基於二進制的HTTP協議升級版本,目前各大瀏覽器都在快馬加鞭的加以支持。 這個RPC框架是基於HTTP協議實現的,底層使用到了Netty框架的支持。
- Thrift是Facebook的一個開源項目,主要是一個跨語言的服務開發框架。它有一個代碼生成器來對它所定義的IDL定義文件自動生成服務代碼框架。用戶只要在其之前進行二次開發就行,對於底層的RPC通訊等都是透明的。不過這個對於用戶來說的話需要學習特定領域語言這個特性,還是有一定成本的。
- Dubbo是阿里集團開源的一個極為出名的RPC框架,在很多互聯網公司和企業應用中廣泛使用。協議和序列化框架都可以插拔是及其鮮明的特色。同樣 的遠程接口是基於Java Interface,並且依托於spring框架方便開發。可以方便的打包成單一文件,獨立進程運行,和現在的微服務概念一致。
偷偷告訴你
集團內部已經不怎么使用dubbo啦,現在用的比較多的叫HSF,又名“好舒服”。后面有可能會開源,大家拭目以待。
HTTP服務
其實在很久以前,我對於企業開發的模式一直定性為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
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)模式。請求程序就是一個客戶機,而服務提供程序就是一台服務器。和常規或本地過程調用一樣,遠程過程調用是同步操作,在遠程過程結果返回之前,需要暫時中止請求程序。使用相同地址空間的低權進程或低權線程允許同時運行多個遠程過程調用。
RPC 的基本操作
讓我們看看本地過程調用是如何實現的。考慮下面的 C 語言的調用:
count = read(fd, buf, nbytes);
- 1
其中,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 實現支持幾個,並允許用戶選擇。
出錯時,會發生什么
相比於本地過程調用,遠程過程調用出錯的機會將會更多。由於本地過程調用沒有過程調用失敗的概念,項目使用遠程過程調用必須准備測試遠程過程調用的失敗或捕獲異常。
遠程調用的語義是什么
調用一個普通的過程語義很簡單:當我們調用時,過程被執行。遠程過程完全一次性調用成功是非常難以實現。執行遠程過程可以有如下結果:
- 如果服務器崩潰或進程在運行服務器代碼之前就死了,那么遠程過程會被執行0次;
- 如果一切工作正常,遠程過程會被執行1次;
- 如果服務器返回服務器存根后在發送響應前就奔潰了,遠程過程會被執行1次或者多次。客戶端接收不到返回的響應,可以決定再試一次,因此出現多次執行函數。如果沒有再試一次,函數執行一次;
- 如果客戶機超時和重新傳輸,那么遠程過程會被執行多次。也有可能是原始請求延遲了。兩者都可能會執行或不執行。
RPC 系統通常會提供至少一次或最多一次的語義,或者在兩者之間選擇。如果需要了解應用程序的性質和遠程過程的功能是否安全,可以通過多次調用同一個函數來驗證。如果一個函數可以運行任何次數而不影響結果,這是冪等(idempotent)函數的,如每天的時間、數學函數、讀取靜態數據等。否則,它是一個非冪等(nonidempotent)函數,如添加或修改一個文件)。
遠程調用的性能怎么樣
毫無疑問,一個遠程過程調用將會比常規的本地過程調用慢得多,因為產生了額外的步驟以及網絡傳輸本身存在延遲。然而,這並不應該阻止我們使用遠程過程調用。
遠程調用安全嗎?
使用 RPC,我們必須關注各種安全問題:
- 客戶端發送消息到遠程過程,那個過程是可信的嗎?
- 客戶端發送消息到遠程計算機,那個遠程機器是可信的嗎?
- 服務器如何驗證接收的消息是來自合法的客戶端嗎?服務器如何識別客戶端?
- 消息在網絡中傳播如何防止時被其他進程嗅探?
- 可以由其他進程消息被攔截和修改時遍歷網絡從客戶端到服務器或服務器端?
- 協議能防止重播攻擊嗎?
- 如何防止消息在網絡傳播中被意外損壞或截斷?
遠程過程調用的優點
遠程過程調用有諸多的優點:
- 你不必擔心傳輸地址問題。服務器可以綁定到任何可用的端口,然后用 RPC 名稱服務來注冊端口。客戶端將通過該名稱服務來找到對應的端口號所需要的程序。而這一切對於程序員來說是透明的。
- 系統可以獨立於傳輸提供者。自動生成服務器存根使其可以在系統上的任何一個傳輸提供者上可用,包括 TCP 和 UDP,而這些,客戶端可以動態選擇的。當代碼發送以后,接收消息是自動生成的,而不需要額外的編程代碼。
- 應用程序在客戶端只需要知道一個傳輸地址——名稱服務,負責告訴應用程序去哪里連接服務器函數集。
- 使用函數調用模型來代替 socket 的發送/接收(讀/寫)接口。用戶不需要處理參數的解析。
RPC API
任何 RPC 實現都需要提供一組支持庫。這些包括:
- 名稱服務操作:
注冊和查找綁定信息(端口、機器)。允許一個應用程序使用動態端口(操作系統分配的); - 綁定操作:使用適當的協議建立客戶機/服務器通信(建立通信端點);
- 終端操作:注冊端點信息(協議、端口號、機器名)到名稱服務並監聽過程調用請求。這些函數通常被自動生成的主程序——服務器存根(骨架)所調用;
- 安全操作:系統應該提供機制保證客戶端和服務器之間能夠相互驗證,兩者之間提供一個安全的通信通道;
- 國際化操作(可能):這是很少的一部分 RPC 包可能包括了轉換時間格式、貨幣格式和特定於語言的在字符串表的字符串的功能;
- 封送處理/數據轉換操作:函數將數據序列化為一個普通的的字節數組,通過網絡進行傳遞,並能夠重建;
- 存根內存管理和垃圾收集:存根可能需要分配內存來存儲參數,特別是模擬引用傳遞語義。RPC 包需要分配和清理任何這樣的分配。他們也可能需要為創建網絡緩沖區而分配內存。RPC 包支持對象,RPC 系統需要一種跟蹤遠程客戶端是否仍有引用對象或一個對象是否可以刪除。
- 程序標識操作:允許應用程序訪問(或處理) RPC 接口集的標識符,這樣的服務器提供的接口集可以被用來交流和使用。
- 對象和函數的標識操作:
允許將遠程函數或遠程對象的引用傳遞給其他進程。並不是所有的 RPC 系統都支持。
所以,判斷一種通信方式是否是 RPC,就看它是否提供上述的 API。
第一代 RPC
ONC RPC(以前稱為 Sun RPC)
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.h:包含項目的定義、版本和聲明的函數。客戶端和服務器端功能應該包括這個文件。
- date_svc.c :C 語言代碼來實現服務器存根。
- date_clnt.c :C 語言代碼來實現客戶端存根。
- date_xdr.c :包含 XDR 例程來將數據轉化為 XDR 格式。如果這個文件生成,它應該編譯並用來鏈接在客戶端和服務器的函數。
創建客戶端和服務器可執行文件的第一步是定義在文件 date.x 里的編譯數據。之后,客戶端和服務器端函數可能被編譯,並鏈接各自 rpcgen 生成的存根函數。
在舊版本里,傳輸協議只能將字符串“tcp”或字符串“udp”來指定各自的 IP 服務 RPC,且僅限於 Linux 實現的 RPC。為了使接口更加靈活,UNIX 系統從版本 4 (SunOS 從版本 5)開始網絡選擇程序允許一個更普通的規范。他們搜索文件(/etc/netconfig),來查找第一個滿足您需求的提供者。最后一個參數可以是:
- “netpath”:搜索 NETPATH 環境變量用於首選傳輸提供者的序列;
- “circuit_n”:找到第一個在 NETPATH 列表中的虛擬電路提供者;
- “datagram_n”:找到第一個 NETPATH 列表中的數據報提供者;
- “visible”:找到第一個在 /etc/netconfig 的可見傳輸提供者;
- “circuit_v”:找到第一個在 /etc/netconfig 的可見虛擬電路傳輸提供者;
- “datagram_v”:找到第一個在 /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)並將返回值返回給客戶端存根。客戶端存根而后返回到客戶端發出調用的代碼。
分布式計算環境中的 RPC(DCE RPC)
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 中的函數查找
第二代 RPC:支持對象
面向對象的語言開始在1980年代末興起,很明顯,當時的 Sun ONC 和 DCE RPC 系統都沒有提供任何支持諸如從遠程類實例化遠程對象、跟蹤對象的實例或提供支持多態性。現有的 RPC 機制雖然可以運作,但他們仍然不支持自動、透明的方式的面向對象編程技術。
微軟 DCOM(COM+)
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 通過。
CORBA
雖然 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 體系的主要內容包括以下幾部分:
- 對象請求代理 (Object Request Broker,ORB):負責對象在分布環境中透明地收發請求和響應,它是構建分布對象應用、在異構或同構環境下實現應用間互操作的基礎。
- 對象服務(Object Services):為使用和實現對象而提供的基本對象集合,這些服務應獨立於應用領域。主要的 CORBA 服務有:名錄服務(Naming Service)、事件服務(Event Service)、生命周期服務(Life Cycle Service)、關系服務(Relationship Service)以及事務服務(Transaction Service)等。這些服務幾乎包括分布系統和面向對象系統的各個方面,每個組成部分都非常復雜。
- 公共設施(Common Facilitites):向終端用戶提供一組共享服務接口,例如系統管理、組合文檔和電子郵件等。
- 應用接口(Application Interfaces)。由銷售商提供的可控制其接口的產品,相應於傳統的應用層表示,處於參考模型的最高層。
- 領域接口(Domain Interfaces):為應用領域服務而提供的接口,如OMG 組織為 PDM 系統制定的規范。
當客戶端發出請求時,ORB 做了如下事情:
- 在客戶端編組參數;
- 定位服務器對象。如果有必要的話,它會在服務器創建一個過程來處理請求;
- 如果服務器是遠程是,就使用 RPC 或 socket 來傳送請求;
- 在服務器上將參數解析成為服務器格式;
- 在服務器上組裝返回值;
- 如果服務器是遠程的,就將返回值傳回;
- 在客戶端對返回結果進行解析;
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); }; };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
IDL數據類型包括:
- 基本類型:long, short, string, float, …
- 構造類型:struct、union、枚舉、序列
- 對象引用
- any 類型:一個動態類型的值
編程中最常見的實現方式是通過對象引用來實現請求。下面是一個使用 IDL 的例子:
Student st = ... // get object reference try { StudentInfo sinfo = st.getinfo("Fred Grampp"); } catch (Throwable e) { ... // error }
- 1
- 2
- 3
- 4
- 5
- 6
在 CORBA 規范中,沒有明確說明不同廠商的中間件產品要實現所有的服務功能,並且允許廠商開發自己的服務類型。因此, 不同廠商的 ORB 產品對 CORBA 服務的支持能力不同,使我們在針對待開發系統的功能進行中間件產品選擇時,有更多的選擇余地。
CORBA 的不足有:
- 盡管有多家供應商提供CORBA產品,但是仍找不到能夠單獨為異種網絡中的所有環境提供實現的供應商。不同的 CORBA 實現之間會出現缺乏互操作性的現象,從而造成一些問題;而且,由於供應商常常會自行定義擴展,而 CORBA 又缺乏針對多線程環境的規范,對於像 C 或 C++ 這樣的語言,源碼兼容性並未完全實現。
- CORBA 過於復雜,要熟悉 CORBA,並進行相應的設計和編程,需要許多個月來掌握,而要達到專家水平,則需要好幾年。
更多有關 CORBA 的優缺點,可以參閱 Michi Henning 的《The rise and fall of CORBA》。
Java RMI
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 的設計目標應該是:
- 能夠適應語言、集成到語言、易於使用;
- 支持無縫的遠程調用對象;
- 支持服務器到 applet 的回調;
- 保障 Java 對象的安全環境;
- 支持分布式垃圾回收;
- 支持多種傳輸。
分布式對象模型與本地 Java 對象模型相似點在於:
- 引用一個對象可以作為參數傳遞或作為返回的結果;
- 遠程對象可以投到任何使用 Java 語法實現的遠程接口的集合上;
- 內置 Java instanceof 操作符可以用來測試遠程對象是否支持遠程接口。
不同點在於:
- 遠程對象的類是與遠程接口進行交互,而不是與這些接口的實現類交互;
- Non-remote 參數對於遠程方法調用來說是通過復制,而不是通過引用;
- 遠程對象是通過引用來傳遞,而不是復制實際的遠程實現;
- 客戶端必須處理額外的異常。
接口和類
所有的遠程接口都繼承自 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; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
注意,每個方法必須在 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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
圖7 Java RMI 工作流程
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 程序獲得返回值。
要完成以上步驟需要有以下幾個步驟:
- 生成一個遠程接口;
- 實現遠程對象(服務器端程序);
- 生成 Stub 和 Skeleton(服務器端程序);
- 編寫服務器程序 ;
- 編寫客戶程序 ;
- 注冊遠程對象;
- 啟動遠程對象
圖8 Java RMI 架構
RMI 分布式垃圾回收
根據 Java 虛擬機的垃圾回收機制原理,在分布式環境下,服務器進程需要知道哪些對象不再由客戶端引用,從而可以被刪除(垃圾回收)。在 JVM中,Java 使用引用計數。當引用計數歸零時,對象將會垃圾回收。在RMI,Java 支持兩種操作:dirty 和 clean。本地 JVM 定期發送一個 dirty 到服務器來說明該對象仍在使用。定期重發 dirty 的周期是由服務器租賃時間來決定的。當客戶端沒有需要更多的本地引用遠程對象時,它發送一個 clean 調用給服務器。不像 DCOM,服務器不需要計算每個客戶機使用的對象,只是簡單的做下通知。如果它租賃時間到期之前沒有接收到任何 dirty 或者 clean 的消息,則可以安排將對象刪除。
第三代 RPC 以及 Web Services
由於互聯網的興起,Web 瀏覽器成為占主導地位的用於訪問信息的模型。現在的應用設計的首要任務大多數是提供用戶通過瀏覽器來訪問,而不是編程訪問或操作數據。
網頁設計關注的是內容。解析展現方面往往是繁瑣的。傳統 RPC 解決方案可以工作在互聯網上,但問題是,他們通常嚴重依賴於動態端口分配,往往要進行額外的防火牆配置。
Web Services 成為一組協議,允許服務被發布、發現,並用於技術無關的形式。即服務不應該依賴於客戶的語言、操作系統或機器架構。
Web Services 的實現一般是使用 Web 服務器作為服務請求的管道。客戶端訪問該服務,首先是通過一個 HTTP 協議發送請求到服務器上的 Web 服務器。Web 服務器配置識別 URL 的一部分路徑名或文件名后綴並將請求傳遞給特定的瀏覽器插件模塊。這個模塊可以除去頭、解析數據(如果需要),並根據需要調用其他函數或模塊。對於這個實現流,一個常見的例子是瀏覽器對於 Java Servlet 的支持。HTTP 請求會被轉發到 JVM 運行的服務端代碼來執行處理。
XML-RPC
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>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
這個例子中,方法 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
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> <q:lodging xmlns:q="http://travelcompany.example.org/reservation/hotels"> <q:preference>none</q:preference> </q:lodging> </env:Body> </env:Envelope>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
其中<soap:Envelope>
是 SOAP 消息中的根節點,是 SOAP 消息中必須的部分。<soap:Header>
是 SOAP 消息中可選部分,是指消息頭。<soap:Body>
是 SOAP 中必須部分,是指消息體。
上面例子的 SOAP 消息結構如下:
圖9 SOAP 消息結構
SOAP 它只是提供了一個標准化的消息結構,為了實現它往往需要用 WSDL 來描述 Web Services 的方法。WSDL (Web Services Description Language) 是基於 XML 的一種用於描述 Web Services 以及如何訪問 Web Services 的語言。
WSDL 文檔包括以下幾個部分:
- 類型(Types):定義了 Web Services 使用的數據類型;
- 消息(n/a):描述使用消息的數據元素或參數;
- 接口(Interface):描述服務提供的操作。這包括操作以及每個操作所使用的輸入和輸出消息;
- 綁定(Binding):為每個端口定義消息格式和協議細節。例如,它可以定義 RPC 式的消息;
- 服務(Service):系統功能相關的集合,包括其關聯的接口、操作、消息等;
- 終點(Endpoint):定義了地址或者 Web Services 的連接點;
- 操作(Operation):定義了 SOAP 的動作,以及消息編碼的方式。
下面是一個 WSDL 2.0 版本的例子:
<?xml version="1.0" encoding="UTF-8"?> <description xmlns="http://www.w3.org/ns/wsdl" xmlns:tns="http://www.tmsws.com/wsdl20sample" xmlns:whttp="http://schemas.xmlsoap.org/wsdl/http/" xmlns:wsoap="http://schemas.xmlsoap.org/wsdl/soap/" targetNamespace="http://www.tmsws.com/wsdl20sample"> <documentation> This is a sample WSDL 2.0 document. </documentation> <!-- Abstract type --> <types> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://www.tmsws.com/wsdl20sample" targetNamespace="http://www.example.com/wsdl20sample"> <xs:element name="request"> ... </xs:element> <xs:element name="response"> ... </xs:element> </xs:schema> </types> <!-- Abstract interfaces --> <interface name="Interface1"> <fault name="Error1" element="tns:response"/> <operation name="Get" pattern="http://www.w3.org/ns/wsdl/in-out"> <input messageLabel="In" element="tns:request"/> <output messageLabel="Out" element="tns:response"/> </operation> </interface> <!-- Concrete Binding Over HTTP --> <binding name="HttpBinding" interface="tns:Interface1" type="http://www.w3.org/ns/wsdl/http"> <operation ref="tns:Get" whttp:method="GET"/> </binding> <!-- Concrete Binding with SOAP--> <binding name="SoapBinding" interface="tns:Interface1" type="http://www.w3.org/ns/wsdl/soap" wsoap:protocol="http://www.w3.org/2003/05/soap/bindings/HTTP/" wsoap:mepDefault="http://www.w3.org/2003/05/soap/mep/request-response"> <operation ref="tns:Get" /> </binding> <!-- Web Service offering endpoints for both bindings--> <service name="Service1" interface="tns:Interface1"> <endpoint name="HttpEndpoint" binding="tns:HttpBinding" address="http://www.example.com/rest/"/> <endpoint name="SoapEndpoint" binding="tns:SoapBinding" address="http://www.example.com/soap/"/> </service> </description>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
Microsoft .NET Remoting
從微軟的產品角度來看,可以說 .NET Remoting 就是 DCOM 的一種升級,它改善了很多功能,並極好的融合到 .NET 平台下。Microsoft .NET Remoting 提供了一種允許對象通過應用程序域與另一對象進行交互的框架。
.NET Remoting 提供了一種允許對象通過應用程序域與另一對象進行交互的框架。這種框架提供了多種服務,包括激活和生存期支持,以及負責與遠程應用程序進行消息傳輸的通訊通道。格式化程序用於在消息通過通道傳輸之前,對其進行編碼和解碼。應用程序可以在注重性能的場合使用二進制編碼,在需要與其他遠程處理框架進行交互的場合使用 XML 編碼。在從一個應用程序域向另一個應用程序域傳輸消息時,所有的 XML 編碼都使用 SOAP 協議。出於安全性方面的考慮,遠程處理提供了大量掛鈎,使得在消息流通過通道進行傳輸之前,安全接收器能夠訪問消息和序列化流
.NET Remoting 對象
有三類對象可以配置為 .NET Remoting 對象。您可以根據應用程序的需要來選擇對象類型:
- Single Call(單一調用對象):
Single Call 能且只能為一個請求提供服務。在需要對象完成的工作量有限的情況下 Single Call 非常有用。Single Call 對象通常不要求存儲狀態信息,並且不能在方法調用之間保留狀態信息。但是,Single Call對象可以配置為負載平衡模式。 - Singleton Objects(單一元素對象):
Singleton Objects 可以為多個客戶端提供服務,因此可以通過保存客戶端調用的狀態信息來實現數據共享。當客戶端之間需要明確地共享數據,並且不能忽略創建和維護對象的開銷時,這類對象非常有用。 - Client-Activated Objects (CAO,客戶端激活的對象):
CAO 是服務器端的對象,將根據來自客戶端的請求激活這些對象。這種激活服務器對象的方法與傳統的 COM coclass 激活方法非常相似。當客戶端使用“new”運算符提交對服務器對象的請求時,會向遠程應用程序發送一個激活請求消息。隨后,服務器創建被請求類的實例,並向調用它的客戶端應用程序返回 ObjRef。然后,使用此 ObjRef 在客戶端上創建代理。客戶端的方法調用將在代理上執行。客戶端激活的對象可以為特定的客戶端(不能跨越不同的客戶端對象)保存方法調用之間的狀態信息。每個“new”調用都會向服務器類型的獨立實例返回代理。
在 .NET Remoting 中,可以通過以下方式在應用程序之間傳遞對象:
- 作為方法調用中的參數,例如:
public int myRemoteMethod (MyRemoteObject myObj)
- 方法調用的返回值,例如:
public MyRemoteObject myRemoteMethod(String myString)
- 訪問 .NET 組件的屬性或字段得到的值,例如:
myObj.myNestedObject
對於 Marshal By Value (MBV,按值封送)的對象,當它在應用程序之間傳遞時,將創建一個完整的副本。
對於 Marshal By Reference (MBR,按引用封送)的對象,當它在應用程序之間傳遞時,將創建該對象的引用。當對象引用 (ObjRef) 到達遠程應用程序后,將轉變成“代理”返回給原始對象。
下面是一個簡單 .NET Remoting 服務器對象的代碼示例:
using System; using System.Runtime.Remoting; namespace myRemoteService { // Well Known Web Service object public class myRemoteObject : MarshalByRefObject { // Method myRemoteMethod public String myRemoteMethod(String s) { return "Hello World"; } } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
下面是客戶端代碼來訪問這個對象:
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Runtime.Remoting.Channels.Http; using myRemoteService; public class Client { public static int Main(string[] args) { ChannelServices.RegisterChannel(new HttpChannel()); // Create an instance of a myRemoteObject class myRemoteObject myObj = ( myRemoteObject)Activator.GetObject(typeof(myRemoteObject), "http://myHost:7021/host/myRemoteObject.soap"); myObj. myRemoteMethod ("Hello World"); return 0; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
租用生存期
對於那些具有在應用程序之外傳送的對象引用的對象,將創建一個租用。租用具有一個租用時間。如果租用時間為 0,則租用過期,對象就斷開與 .NET Romoting 框架的連接。一旦 AppDomain 中的所有對象引用都被釋放,則下次垃圾回收時,該對象將被回收。租用控制了對象的生存期。
對象有默認的租用階段。當客戶端要在同一服務器對象中維護狀態時,可以通過許多方法擴展租用階段,使對象繼續生存:
- 服務器對象可以將其租用時間設置為無限,這樣 Remoting 在垃圾回收時就不會回收此對象。
- 客戶端可以調用
RemotingServices.GetLifetimeService
方法,從 AppDomain 的租用管理器獲取服務器對象的租用。然后,客戶端可以通過 Lease 對象調用 Lease.Renew 方法以延長租用。 - 客戶端可用 AppDomain 的租用管理器為特定的租用注冊負責人。當 Remoting 對象的租用過期時,租用管理器將通知負責人提出續租的申請。
- 如果設置了
ILease::RenewOnCallTime
屬性,則每次調用 Remoting 對象時,都會用 RenewOnCallTime 屬性指定的總時間更新租用。
集成 .NET Remoting 對象
.NET Remoting 對象可以集成在:
- 托管可執行項:
.NET Remoting 對象可以集成在任何常規的 .NET EXE 或托管服務中。 - .NET 組件服務:
.NET Remoting 對象可以集成在 .NET 組件服務基礎結構中,以便利用各種 COM+ 服務,例如:事務、JIT 和對象池等。 - IIS : .NET Remoting 對象可以集成在 Internet Information Server (IIS) 中。默認情況下,集成在 IIS 中的 Remoting 對象通過 HTTP 通道接收消息。要在 IIS 中集成 Remoting 對象,必須創建一個虛擬的根,並將 remoting.config 文件復制到其中。包含 Remoting 對象的可執行文件或 DLL 應放在 IIS 根指向的目錄下的 bin 目錄中。需要注意的是,IIS 根名稱應該與配置文件中指定的應用程序名稱相同。當第一個消息到達應用程序時,Remoting 配置文件會自動加載。使用這種方法,可以提供 .NET Remoting 對象作為 Web 服務。
下面是一個 Remoting.cfg 文件的例子:
<configuration> <system.runtime.remoting> <application name="RemotingHello"> <service> <wellknown mode="SingleCall" type="Hello.HelloService, Hello" objectUri="HelloService.soap" /> </service> <channels> <channel port="8000" type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" /> </channels> </application> </system.runtime.remoting> </configuration>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
通道服務 (System.Runtime.Remoting.Channels)
.NET 應用程序和 AppDomain 之間使用消息進行通信。.NET 通道服務為這一通信過程提供了基礎傳輸機制。
.NET 框架提供了 HTTP 和 TCP 通道,但是第三方也可以編寫並使用自己的通道。默認情況下,HTTP 通道使用 SOAP 進行通信,TCP 通道使用二進制有效負載。
通過使用可以編寫到集成混合應用程序中的自定義通道,可以插入通道服務(使用 IChannel)。
下面是一個加載通道服務的例子:
public class myRemotingObj { HttpChannel httpChannel; TcpChannel tcpChannel; public void myRemotingMethod() { httpChannel = new HttpChannel(); tcpChannel = new TcpChannel(); ChannelServices.RegisterChannel(httpChannel); // Register the HTTP Channel ChannelServices.RegisterChannel(tcpChannel); // Register the TCP Channel } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
序列化格式化程序 (System.Runtime.Serialization.Formatters)
.NET 序列化格式化程序對 .NET 應用程序和 AppDomain 之間的消息進行編碼和解碼。在 .NET 運行時中有兩個本地格式化程序,分別為二進制 (System.Runtime.Serialization.Formatters.Binary
) 和 SOAP (System.Runtime.Serialization.Formatters.Soap
)。
通過實現 IRemotingFormatter 接口,並將其插入到上文介紹的通道中,可以插入序列化格式化程序。這樣,您可以靈活地選擇通道和格式化程序的組合方式,采用最適合應用程序的方案。
例如:您可以采用 HTTP 通道和二進制格式化程序(串行化二進制數據),也可以采用 TCP 通道和 SOAP 格式。
Remoting 上下文
上下文是一個包含共享公共運行時屬性的對象的范圍。某些上下文屬性的例子是與同步和線程緊密相關的。當 .NET 對象被激活時,運行時將檢查當前上下文是否一致,如果不一致,將創建新的上下文。多個對象可以同時在一個上下文中運行,並且一個 AppDomain 中可以有多個上下文。
一個上下文中的對象調用另一個上下文中的對象時,調用將通過上下文代理來執行,並且會受組合的上下文屬性的強制策略影響。新對象的上下文通常是基於類的元數據屬性選擇的。
可以與上下文綁定的類稱作上下文綁定類。上下文綁定類可以具有稱為上下文屬性的專用自定義屬性。上下文屬性是完全可擴展的,您可以創建這些屬性並將它們附加到自己的類中。與上下文綁定的對象是從 System.ContextBoundObject
導出的。
.NET Remoting 元數據和配置文件
元數據
.NET 框架使用元數據和程序集來存儲有關組件的信息,使得跨語言編程成為可能。.NET Remoting 使用元數據來動態創建代理對象。在客戶端創建的代理對象具有與原始類相同的成員。但是,代理對象的實現僅僅將所有請求通過 .NET Remoting 運行時轉發給原始對象。序列化格式化程序使用元數據在方法調用和有效負載數據流之間來回轉換。
客戶端可以通過以下方法獲取訪問 Remoting 對象所需的元數據信息:
- 服務器對象的 .NET 程序集 : 服務器對象可以創建元數據程序集,並將其分發給客戶端。在編譯客戶端對象時,客戶端對象可以引用這些程序集。在客戶端和服務器都是托管組件的封閉環境中,這種方法非常有用。
- Remoting 對象可以提供 WSDL文件,用於說明對象及其方法。所有可以根據 WSDL 文件讀取和生成 SOAP 請求的客戶端都可以調用此對象,或使用 SOAP 與之通信。使用與 .NET SDK 一同分發的 SOAPSUDS.EXE 工具,.NET Remoting 服務器對象可以生成具有元數據功能的 WSDL 文件。當某個組織希望提供所有客戶都能訪問和使用的公共服務時,這種方法非常有用。
- .NET 客戶可以使用 SOAPSUDS 工具從服務器上下載 XML 架構(在服務器上生成),生成僅包含元數據(沒有代碼)的源文件或程序集。您可以根據需要將源文件編譯到客戶端應用程序中。如果多層應用程序中某一層的對象需要訪問其他層的 Remoting 對象,則經常使用此方法。
配置文件
配置文件(.config 文件)用於指定給定對象的各種 Remoting 特有信息。通常情況下,每個 AppDomain 都有自己的配置文件。使用配置文件有助於實現位置的透明性。配置文件中指定的詳細信息也可以通過編程來完成。使用配置文件的主要好處在於,它將與客戶端代碼無關的配置信息分離出來,這樣在日后更改時僅需要修改配置文件,而不用編輯和重新編譯源文件。.NET Remoting 的客戶端和服務器對象都使用配置文件。
典型的配置文件包含以下信息及其他信息:
- 集成應用程序信息
- 對象名稱
- 對象的 URI
- 注冊的通道(可以同時注冊多個通道)
- 服務器對象的租用時間信息
下面是一個配置文件示例:
<configuration> <system.runtime.remoting> <application name="HelloNew"> <lifetime leaseTime="20ms" sponsorshipTimeout="20ms" renewOnCallTime="20ms" /> <client url="http://localhost:8000/RemotingHello"> <wellknown type="Hello.HelloService, MyHello" url="http://localhost:8000/RemotingHello/HelloService.soap" /> <activated type="Hello.AddService, MyHello"/> </client> <channels> <channel port="8001" type="System.Runtime.Remoting.Channels.Http.HttpChannel, System.Runtime.Remoting" /> </channels> </application> </system.runtime.remoting> </configuration>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
.NET Remoting 方案
了解 .NET Remoting 如何工作之后,讓我們來看一下各種方案,分析如何在不同的方案中充分發揮 .NET Remoting 的優勢。下表列出了可能的客戶端/服務器組合,以及默認情況下采用的底層協議和有效負載。請注意,.NET Remoting 框架是可擴展的,您可以編寫自己的通信通道和序列化格式化程序。
客戶端 | 服務器 | 有效負載 | 協議 |
---|---|---|---|
.NET 組件 | .NET 組件 | SOAP/XML | http |
.NET 組件 | .NET 組件 | 二進制 | TCP |
托管/非托管 | .NET Web 服務 | SOAP/XML | http |
.NET 組件 | 非托管的傳統 COM 組件 | NDR(網絡數據表示形式) | DCOM |
非托管的傳統 COM 組件 | .NET 組件 | NDR | DCOM |
使用 HTTP-SOAP
Web 服務是可以通過 URL 尋址的資源,並通過編程向需要使用這些資源的客戶端返回信息。客戶端使用 Web Services 時不必考慮其實現細節。Web Services 使用稱為“合約”的嚴格定義的接口,此接口采用 Web 服務說明語言 (WSDL) 文件描述。
.NET Remoting 對象可以集成在 IIS 中,作為 Web 服務提供。任何可以使用 WSDL 文件的客戶端都可以按照 WSDL 文件中指定的合約,對 Remoting 對象執行 SOAP 調用。IIS 使用 ISAPI 擴展將這些請求路由到相應的對象。這樣,Remoting 對象就可以作為 Web 服務對象來使用,從而充分發揮 .NET 框架基礎結構的作用。如果您希望不同平台/環境的程序均能夠訪問對象,可以采用這種配置。這種配置便於客戶端通過防火牆訪問您的 .NET 對象。
圖10 .NET 使用 HTTP-SOAP
使用 SOAP-HTTP 通道
默認情況下,HTTP 通道使用 SOAP 格式化程序。因此,如果客戶端需要通過 Internet 訪問對象,可以使用 HTTP 通道。因為這種方法使用 HTTP,所以此配置允許通過防火牆遠程訪問 .NET 對象。只需要按前一節中介紹的方法將這些對象集成在 IIS 中,即可將它配置為 Web 服務對象。隨后,客戶端就可以讀取這些對象的 WSDL 文件,使用 SOAP 與 Remoting 對象通信。
使用 TCP 通道
默認情況下,TCP 通道使用二進制格式化程序。此格式化程序以二進制格式對數據進行序列化,並使用原始 socket 在網絡中傳送數據。如果對象部署在受防火牆保護的封閉環境中,此方法是理想的選擇。這種方法使用 socket 在對象之間傳遞二進制數據,因此性能極佳。由於它使用 TCP 通道來提供對象,因此在封閉環境中具有低開銷的優點。由於防火牆和配置的問題,此方法不宜在 Internet 上使用。
圖11 .NET 使用 TCP 通道
使用非托管的 COM 組件
可以通過 COM Interop Service 調用非托管的傳統 COM 組件。當 .NET Remoting 客戶端對象創建 COM 對象的實例時,該對象通過運行時可調用包裝程序 (RCW) 來提供。其中,RCW 擔當真正的非托管對象的代理。對於 .NET 客戶,這些包裝程序看起來和 .NET 客戶端的任何其他托管類一樣。但實際上,它們僅僅是托管 (.NET) 和非托管 (COM) 代碼之間的封送調用。
同樣地,您可以將 .NET Remoting 服務器對象提供給傳統 COM 客戶端。當 COM 客戶端創建 .NET 對象的實例時,該對象通過 COM 可調用包裝程序 (CCW) 來提供。其中,CCW 擔當真正的托管對象的代理。
這兩種方案都使用 DCOM 通信。如果環境中既有傳統的 COM 組件,又有 .NET 組件,那么這種互操作性將為您提供便利。
總結
Microsoft .NET 框架提供了強大、可擴展、獨立於語言的框架,適合開發可靠、可伸縮的分布式系統。.NET Romoting 框架提供了根據系統需求進行遠程交互的強大手段。.NET Remoting 實現了與 Web 服務的無縫集成,並提供了一種方法,可以提供 .NET 對象以供跨平台訪問。
Java 中的 XML Web Services
Java RMI 與遠程對象進行交互,其實現是需要基於 Java 的模型。此外,它沒有使用 Web Services 和基於 HTTP 的消息傳遞。現在,已經出現了大量的軟件來支持基於 Java 的 Web Services。JAX-WS (Java API for XML Web Services) 就是作為 Web Services 消息息和遠程過程調用的規范。它允許一個調用基於Java的web服務使用Java RMI(即。,相對透明的程序員)。JAX-WS 的一個目標是平台互操作性。其 API 使用 SOAP 和WSDL。雙方不需要 Java 環境。
創建一個 RPC 端點
在服務器端,進行下面的步驟來創建一個 RPC 端點:
- 定義一個接口(Java接口);
- 實現服務;
- 創建一個發布者,用於創建服務的實例,並發布一個服務名字。
在客戶端:
- 創建一個代理(客戶端存根)。wsimport 命令根據 WSDL 文檔,創建一個客戶機存根;
- 編寫一個客戶端,通過代理創建遠程服務的一個實例(存根),調用它的方法。
JAX-RPC 執行流程如下:
- Java 客戶機調用存根上的方法(代理);
- 存根調用適當的 Web 服務;
- Web 服務器被調用並指導 JAX-WS 框架;
- 框架調用實現;
- 實現返回結果給該框架;
- 該框架將結果返回給 Web 服務器;
- 服務器將結果發送給客戶端存根;
- 客戶端存根返回信息給調用者;
圖12 JAX-WS 調用流程
超越 SOAP
SOAP 雖然仍然是廣泛部署應用,但在許多環境中很多廠商已經拋棄了 SOAP,轉而使用其他更輕量、更容易理解、或者與 Web 交互模型更干凈的機制。例如,Google 的 API 在2006年后就不再支持 SOAP 接口,而是使用AJAX、XML-RPC 和 REST 作為替代。一個匿名的微軟員工批評 SOAP 過於復雜,因為“我們希望我們的工具來閱讀它,而不是人”。不管上述言論是否准確,有一點是可以肯定的,SOAP 顯然是一個復雜和高度冗長的格式。
AJAX
Web 瀏覽器最初的設計,是為 Web 頁面提供非動態的交互模型。Web 瀏覽器是建立在同步的請求-響應(request-response)的交互模型。發送一個請求到服務器,服務器返回整個頁面。在當時沒有更新部分頁面的好方法,而唯一可行的方法是利用幀,即將不同的頁面加載到每一幀,其實現是笨重的,也有很大的限制性。而改變了這一切的關鍵因素是:
- 文檔對象模型(Document Object Model)和 JavaScript 的出現,使得可以以編程方式來更改 Web 頁面的各個部分;
- AJAX 提供了與服務器以非阻塞方式進行交互,即允許底層 JavaScript 在等待服務器結果時,用戶仍然可以與頁面進行交互。
AJAX 全稱是 Asynchronous JavaScript And XML(異步的 JavaScript 和 XML)。讓我們看看這些三項:
- 它是異步的,因為客戶端等待服務器結果不會被阻塞;
- AJAX 集成到了 JavaScript,作為瀏覽器解釋 Web 頁面的一部分。JavaScript 使用 HTTPRequest 來調用 AJAX 請求。JavaScript 也可能修改文檔對象模型,定義了頁面的樣子;
- 數據以 XML 文檔形式發送和接收。(在后期發展中,AJAX 也支持其他的數據格式,比如 JSON)
AJAX 在推動 Web 2.0 的過程中發揮了重要的,比如產生了很多高度交互的服務,如Google Maps、Writely等。基本上,它允許 JavaScript 發出HTTP 請求,獲取和處理結果,刷新局部頁面元素而不是整個頁面。在大多數瀏覽器請求的格式如下:
new XMLHttpRequest() xmlhttp.open(“HEAD”, “index.html”, true)Tell object:
- 1
- 2
REST
SOAP 在創建自己的消息傳遞協議時是基於HTTP,但實際上 REST (REpresentational State Transfer) 的方式才是保持 Web 的原理和使用 HTTP 協議的核心部分。
原始的 HTTP 協議已經定義了四個命令,清晰地映射到各種數據(定義為“資源”)操作:
- PUT (插入)
- GET (選擇)
- POST (更新)
- DELETE (刪除)
REST 其背后的理念是使用這些 HTTP 命令來請求和操作數據。作為 HTTP協議的一部分,REST 使用 URL 來引用對象和操作。考慮這個 HTTP 操作列表的例子:
HTTP GET //www.waylau.com/parts
- 1
這個命令將返回一個 XML 文檔,其中包含部分的列表。注意,返回的不是一個網頁,只是一個包含所請求的數據 XML 數據結構。
<?xml version="1.0"?> <p:Parts xmlns:p="http://www.waylau.com" xmlns:xlink="http://www.w3.org/1999/xlink"> <Part id="00345" xlink:href="http://www.waylau.com/parts/00345"/> <Part id="00346" xlink:href="http://www.waylau.com/parts/00346"/> <Part id="00347" xlink:href="http://www.waylau.com/parts/00347"/> <Part id="00348" xlink:href="http://www.waylau.com/parts/00348"/> </p:Parts>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
要特定部分的詳細信息,發送一個HTTP get 命令:
HTTP GET //www.waylau.com/parts/00345
- 1
這將返回一個特定的信息部分:
<?xml version="1.0"?> <p:Part xmlns:p="http://www.waylau.com" xmlns:xlink="http://www.w3.org/1999/xlink"> <Part-ID>00345</Part-ID> <Name>Widget-A</Name> <Description>This part is used within the frap assembly</Description> <Specification xlink:href="http://www.waylau.com/parts/00345/specification"/> <UnitCost currency="USD">0.10</UnitCost> <Quantity>10</Quantity> </p:Part>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
注意,上面例子簡化了 partid 作為 URL 的參數。例如:
HTTP GET //www.waylau.com/parts?partid=00345
- 1
REST 不是 RPC,但也有類似的請求-響應模式。制定透明度請求、封送數據、解析響應這些不屬於 REST。REST 應用非常廣泛,如 Yahoo! Search API、Ruby on Rails、Twiter 和 Open Zing Services 等。
Google Protocol Buffers:封送處理
有些時候,不僅僅是為了 RPC 和 Web Services 的需要,程序員只是想簡化對網絡上的數據的封送編組和解封的操作。Google Protocol Buffers 就是為序列化結構化數據提供了一種有效的機制,使它容易對網絡上的數據進行編碼和解碼。Protocol Buffers 是一個緊湊的二進制格式比 XML 更簡單、體積更小、速度更快。他們是獨立於語言的,只定義數據類型。每個消息是對數據名稱、類型和值的結構化集合。消息結構定義在一個高級別的格式,類似於許多接口定義語言。然后文件可以根據你選擇的語言來編譯轉換成與該語言相應的格式。Protocol Buffers 在 Google 中廣泛使用。目前已經有超過48000種不同的消息類型定義。Protocol Buffers 可以被運用在類 RPC 消息傳遞以及持久性存儲(將數據轉換成標准的串行形式寫入到一個文件中)。下面是一個定義 Protocol Buffers 的例子:
message Person {
required string name = 1; required int32 id = 2; optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
請注意,這只定義了數據結構,而不是功能。使用這個結構的例子是:
Person person; person.set_name("John Doe"); person.set_id(1234); person.set_email("jdoe@example.com"); fstream output("myfile", ios::out | ios::binary); person.SerializeToOstream(&output);
- 1
- 2
- 3
- 4
- 5
- 6
即使與緊湊的 XML 版本相比,Protocol Buffers 在時間和空間方面,解析將更加有效。下面是兩者的對比.
這個是 XML 格式:
<person> <name>John Doe</name> <email>jdoe@example.com</email> </person>
- 1
- 2
- 3
- 4
這個沒有編譯的 Protocol Buffers 格式:
person { name: "John Doe" email: "jdoe@example.com" }
- 1
- 2
- 3
- 4
Protocol Buffers 產生的二進制消息大約是28字節長,解析耗時大概需要100-200ns。相比之下,XML 版本需要69個字節長(是 Protocol Buffers 的 2.5倍),耗時是5000-10000ns(是 Protocol Buffers 的 50倍)。
JSON
JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。它基於 ECMAScript 的一個子集。JSON 采用完全獨立於語言的文本格式,但是也使用了類似於 C 語言家族的習慣(包括C、C++、C#、Java、JavaScript、Perl、Python 等)。這些特性使 JSON 成為理想的數據交換語言。易於人閱讀和編寫,同時也易於機器解析和生成。JSON 不是一個諸如 Google Protocol Buffers 的二進制格式,因此適合使用基於 HTTP的消息傳遞。JSON 是可以作為 XML 替代品,在遠程過程調用中,很多語言都支持 JSON-RPC。記住,這只是一個消息傳遞格式,JSON 並沒有試圖提供 RPC 庫來支持服務發現、綁定、托管和垃圾收集。
參考引用
- http://www.cs.virginia.edu/~zaher/classes/CS656/birrel.pdf
- https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html
- http://queue.acm.org/detail.cfm?id=1142044
- https://en.wikipedia.org/wiki/Web_Services_Description_Language
- https://msdn.microsoft.com/en-us/library/ms973864.aspx
- https://msdn.microsoft.com/en-us/library/bb985129.aspx