遠端過程呼叫
對於遠端過程呼叫(Remote Procedure Call,RPC),Birrell和Nelson在1984年發表於ACM Transactions on Computer Systems的論文Implementing remote procedure calls做了經典的詮釋。RPC是指,計算機A上的程序,呼叫另外一臺計算機B上的程序,其中A上的呼叫程序被掛起,而B上的被呼叫程序開始執行,當值返回給A時,A程序繼續執行。
呼叫方可以透過使用引數將訊息傳送給被呼叫方,而後可以透過傳回的結果得到訊息。這一過程對於開發人員來說是透明的。RPC採用客戶端/伺服器(C/S)模式。請求程式就是一個客戶端,而服務提供程式就是一臺伺服器。與常規或本地過程呼叫一樣,RPC是同步操作,在遠端過程結果返回之前,需要暫時中止請求程式。使用相同地址空間的低權程序或低權執行緒允許同時執行多個RPC。
圖3-2描述了併發環境下RPC的實現過程。
圖3-2 併發環境下RPC的實現過程
遠端過程呼叫原理
RPC背後的思想是儘量使遠端過程呼叫具有與本地呼叫相同的形式。假設程式需要從某個檔案中讀取資料,程式設計師在程式碼中執行read呼叫來讀取資料。在傳統的系統中,read過程由連結器從庫中提取出來,然後連結器將它插入目標程式中。read過程是一個短過程,一般透過執行一個等效的read系統呼叫來實現,即read過程是一個位於使用者程式碼與本地作業系統之間的介面。
雖然read中執行了系統呼叫,但它本身依然是透過將引數壓入堆疊的常規方式實現呼叫的。如圖3-1(b)所示,程式設計師並不知道read幹了什麼。
RPC透過類似的途徑來獲得透明性。當read實際上是一個遠端過程時(比如在檔案伺服器所在的機器上執行的過程),庫中就放入read的另外一個版本,稱為客戶端存根(Client Stub)。這種版本的read過程同樣遵循圖3-1(b)的呼叫次序,這點與原來的read過程相同。另一個相同點是其中也執行了本地作業系統呼叫。唯一不同點是它不要求作業系統提供資料,而是將引數打包成訊息,而後請求將此訊息傳送到伺服器,如圖3-3所示。在呼叫send後,客戶端存根呼叫receive過程,隨即阻塞自己,直到收到響應訊息。
圖3-3 客戶端與伺服器之間的RPC原理
當訊息到達伺服器時,伺服器上的作業系統將它傳遞給伺服器存根(Server Stub)。伺服器存根是客戶端存根在伺服器端的等價物,也是一段程式碼,用來將透過網路輸入的請求轉換為本地過程呼叫。伺服器存根一般先呼叫receive,然後被阻塞,等待訊息輸入。收到訊息後,伺服器將引數從訊息中提取出來,然後以常規方式呼叫伺服器上的相應過程(見圖3-3)。從伺服器角度看,過程好像是由客戶直接呼叫的:引數和返回地址都位於堆疊中,一切都很正常。伺服器執行所要求的操作,隨後將得到的結果以常規的方式返回給呼叫方。以read為例,伺服器將用資料填充read中第二個引數指向的快取區,該快取區是屬於伺服器存根內部的。
呼叫完後,伺服器存根要將控制權交回給客戶端發出呼叫的過程,它將結果(快取區)打包成訊息,隨後呼叫send將結果返回給客戶端。
事後,伺服器存根一般會再次呼叫receive,等待下一個輸入的請求。
客戶端接收到訊息後,客戶端作業系統發現該訊息屬於某個客戶端程序(實際上該程序是客戶端存根,只是作業系統無法區分二者)。作業系統將訊息複製到相應的快取區中,隨後解除對客戶端程序的阻塞。客戶端存根檢查該訊息,將結果提取出來並複製給呼叫者,而後以常規的方式返回。當呼叫者在read呼叫進行完畢後重新獲得控制權時,它所知道的唯一事情就是已經得到了所需的資料,但它不知道操作是在本地作業系統進行的,還是遠端完成的。
整個方法中,客戶端可以簡單地忽略不關心的內容。客戶端所涉及的操作只是執行普通的(本地)過程呼叫來訪問遠端服務,它並不需要直接呼叫send和receive。訊息傳遞的所有細節都隱藏在雙方的庫過程中,就像傳統庫隱藏了執行實際系統呼叫的細節一樣。概括來說,遠端過程呼叫包含以下步驟。
(1)客戶端過程以正常的方式呼叫客戶端存根。
(2)客戶端存根生成一個訊息,然後呼叫本地作業系統。
(3)客戶端作業系統將訊息傳送給遠端作業系統。
(4)遠端作業系統將訊息交給伺服器存根。
(5)伺服器存根將引數提取出來,而後呼叫伺服器。
(6)伺服器執行要求的操作,完成後將結果返回給伺服器存根。
(7)伺服器存根將結果打包成一個訊息,而後呼叫本地作業系統。
(8)伺服器作業系統將含有結果的訊息傳送給客戶端作業系統。
(9)客戶端作業系統將訊息交給客戶端存根。
(10)伺服器的客戶端存根將結果從訊息中提取出來,返回給呼叫它的客戶端的客戶存根。
以上步驟就是客戶端過程將客戶端存根發出的本地呼叫轉換成對伺服器過程的本地呼叫,而客戶端和伺服器都不會意識到中間步驟的存在。
RPC有兩個主要優點。首先,程式設計師可以使用過程呼叫語義來呼叫遠端方法並獲取響應。其次,簡化了編寫分散式系統應用程式的難度,因為RPC隱藏了所有的網路程式碼存根方法。應用程式不必擔心一些細節,比如Socket、埠號以及資料的轉換和解析。在OSI參考模型中,RPC跨越了會話層和表示層。
如何實現遠端過程呼叫
要實現遠端過程呼叫,需考慮以下8個問題。
1.如何傳遞引數
引數有兩種,一種是值引數,另一種是引用引數。
傳遞值引數比較簡單,圖3-4所示是一個簡單RPC進行遠端計算的步驟。其中,遠端過程add(i,j)有兩個引數i和j,其結果是返回i和j的算術和。
圖3-4 透過RPC進行遠端計算的步驟
透過RPC進行遠端計算的步驟如下。
(1)將引數放入訊息中,並在訊息中新增要呼叫的過程的名稱或者編碼。
(2)訊息到達伺服器後,伺服器存根對該訊息進行分析,以判斷明確需要呼叫哪個過程,隨後執行相應的呼叫。
(3)伺服器執行完畢,伺服器存根將伺服器得到的結果打包成訊息送回客戶存根,客戶端存根將結果從訊息中提取出來,把結果值返回給客戶端。
當然,這裡只是做了簡單的演示,在實際分散式系統中,還需要考慮其他情況,因為不同的機器對於數字、字元和其他型別的資料項的表示方式常有差異。比如整數型,就有Big Endian和Little Endian之分。
傳遞引用引數相對來說比較困難。單純傳遞引數的引用(也包含指標)是完全沒有意義的,因為引用地址傳遞給遠端計算機,其指向的記憶體位置可能與遠端系統上完全不同。如果你想支援傳遞引用引數,就必須傳送引數的副本,將它們放置在遠端系統記憶體中,向它們傳遞一個指向伺服器函式的指標,然後將物件傳送回客戶端,複製它的引用。如果遠端過程呼叫必須支援引用複雜的結構,比如樹和連結串列,它們需要將結構複製到一個無指標的資料表裡面(比如,一個扁平的樹),並傳輸到遠端端來重建資料結構。
2.如何表示資料
在本地系統上不存在資料不相容的問題,因為資料格式總是相同的。而在分散式系統中,不同遠端計算機上可能有不同的位元組順序、不同大小的整數,以及不同的浮點表示。對於RPC,如果想與異構系統通訊,我們就需要想出一個“標準”來對所有資料型別進行編碼,並可以作為引數傳遞。例如,ONC RPC使用XDR(eXternal Data Representation)格式。這些資料表示格式可以使用隱式或顯式型別。隱式型別是指只傳遞值,而不傳遞變數的名稱或型別。常見的例子是ONC RPC的XDR和DCE RPC的NDR。顯式型別指需要傳遞每個欄位的型別和值。常見的例子是ISO標準ASN.1(Abstract Syntax Notation)、JSON(JavaScriptObject Notation)、Google Protocol Buffers,以及各種基於XML的資料表示格式。
3.如何選用傳輸協議
有些實現只允許使用一個協議(例如TCP)。大多數RPC實現支援幾個,例如TCP、HTTP等,並允許使用者選擇。
4.出錯時會發生什麼
相比於本地過程呼叫,遠端過程調用出錯的機率更大。由於本地過程呼叫沒有過程呼叫失敗的概念,專案使用遠端過程呼叫必須準備測試遠端過程呼叫失敗或捕獲異常的情況。
5.遠端呼叫的語義是什麼
呼叫一個普通的過程語義很簡單:當我們呼叫時,過程被執行。遠端過程完全一次性呼叫成功是非常難以實現的。執行遠端過程可能有如下結果。
·如果伺服器崩潰或程序在執行伺服器程式碼之前就中斷執行了,那麼遠端過程會被執行0次。
·如果一切工作正常,遠端過程會被執行1次。
·如果伺服器返回伺服器存根後在傳送響應前就崩潰了,遠端過程會被執行1次或者多次。客戶端接收不到返回的響應,可以決定再試一次,因此出現多次執行函式的現象。如果沒有再試一次,函式執行一次。
·如果客戶機超時和重新傳輸,那麼遠端過程會被執行多次。也有可能是原始請求延遲了,兩者都可能執行或不執行。
RPC系統通常會提供至少一次或最多一次的語義,或者在兩者之間選擇。如果需要了解應用程式的性質和遠端過程的功能是否安全,可以透過多次呼叫同一個函式來驗證。如果一個函式可以執行任意次數而不影響結果,這是冪等(Idempotent)函式,如每天的時間、數學函式、讀取靜態資料等。否則,它是一個非冪等(Nonidempotent)函式,如新增或修改一個檔案。
6.遠端呼叫的效能怎麼樣
毫無疑問,一個遠端過程呼叫將比常規的本地過程呼叫慢得多,因為產生了額外的步驟以及網路傳輸本身存在延遲。然而,這並不應該阻止我們使用遠端過程呼叫。
7.遠端呼叫安全嗎
使用RPC,我們必須關注各種安全問題。
·客戶端傳送訊息到遠端過程,這個過程是可信的嗎?
·客戶端傳送訊息到遠端計算機,這個遠端計算機是可信的嗎?·伺服器如何驗證接收的訊息來自合法的客戶端?伺服器如何識別客戶端?
·訊息在網路中傳播時如何防止被其他程序嗅探?
·如何防止訊息在客戶端和伺服器的網路傳播中被其他程序攔截和修改?
·協議能防止重播攻擊嗎?
·如何防止訊息在網路傳播中被意外損壞或截斷?
8.遠端過程呼叫的優點
遠端過程呼叫有諸多優點。
·不必擔心傳輸地址問題。伺服器可以繫結到任何可用的埠,然後用RPC名稱服務來註冊埠。客戶端將透過該名稱服務來找到對應的埠號所需要的程式。而這一切對於程式設計師來說是透明的。
·系統可以獨立於傳輸提供者。自動生成伺服器存根使其在系統上的任何一個傳輸提供者上可用,包括TCP和UDP,而這些,客戶端是可以動態選擇的。當代碼傳送以後,接收訊息是自動生成的,不需要額外的程式設計程式碼。
·應用程式在客戶端只需要知道一個傳輸地址——名稱服務,負責告訴應用程式去哪裡連線伺服器函式集。
·使用函式呼叫模型來代替Socket的傳送/接收(讀/寫)介面。客戶不需要處理引數的解析。
遠端過程呼叫API
任何RPC實現都需要提供一組支援庫。
·名稱服務操作:註冊和查詢繫結資訊(埠、機器)。允許一個應用程式使用動態埠(作業系統分配的)。·繫結操作:使用適當的協議建立客戶機/伺服器通訊(建立通訊端點)。
·終端操作:註冊端點資訊(協議、埠號、機器名)到名稱服務並監聽過程呼叫請求。這些函式通常被自動生成的主程式——伺服器存根(骨架)所呼叫。
·安全操作:系統應該提供機制保證客戶端和伺服器之間能夠相互驗證,兩者之間提供一個安全的通訊通道。
·國際化操作(可能):目前,有一小部分RPC包支援轉換包括時間格式、貨幣格式和特定語言的字串的功能。
·封送處理/資料轉換操作:函式將資料序列化為一個普通的位元組陣列,透過網路進行傳遞,並能夠重建。
·存根記憶體管理和垃圾收集:存根可能需要分配記憶體來儲存引數,特別是模擬引用傳遞語義。RPC包需要分配和清理任何這樣的記憶體。它們也可能需要為建立網路快取區而分配記憶體。RPC包支援物件,RPC系統需要跟蹤遠端客戶端是否仍有引用物件或一個物件是否可以刪除。
·程式標識操作:允許應用程式訪問(或處理)RPC介面集的識別符號,這樣的伺服器提供的介面集可以被用來交流和使用。
·物件和函式的標識操作:允許將遠端函式或遠端物件的引用傳遞給其他程序,並不是所有的RPC系統都支援。
所以,判斷一種通訊方式是不是RPC,就看它是否提供上述的API。
遠端過程呼叫發展歷程
1.第一代RPC
Sun公司是第一個提供商業化RPC庫和RPC編譯器的公司。在20世紀80年代中期Sun計算機提供RPC,並在Sun Network FileSystem(NFS)上得到支援。該協議主要由以Sun和AT&T為首的開放網路計算(Open Network Computing)作為一個標準來推動。這是一個非常輕量級的RPC系統,可在大多數POSIX和類POSIX作業系統中使用,包括Linux、SunOS、OS X和各種釋出版本的BSD。這樣的系統被稱為Sun RPC或ONC RPC。該階段的其他代表產品還有DCE RPC。
2.第二代RPC支援物件
面向物件的語言開始在20世紀80年代末興起,很明顯,當時的SunONC和DCE RPC系統都沒有提供任何支援,諸如從遠端類例項化遠端物件、跟蹤物件的例項或提供支援多型性。現有的RPC機制雖然可以運作,但它們仍然不支援自動、透明的方式的面向物件程式設計技術。該階段的主要產品有微軟DCOM(COM+)、CORBA、Java RMI。
3.第三代RPC以及Web Services
傳統RPC解決方案可以工作在網際網路上,但問題是,它們通常嚴重依賴於動態埠分配,往往要進行額外的防火牆配置。Web Services成為一組協議,允許服務被髮布、發現,並用於技術無關的形式,即服務不應該依賴於客戶的語言、作業系統或機器架構。該階段的代表產品有XML-RPC、SOAP、Microsoft.NET Remoting、JAX-WS等。