遠程過程調用RPC(二)
--譯文
原文地址:https://www.cs.rutgers.edu/~pxk/417/notes/03-rpc.html
第一代遠程過程調用
ONC RPC
Sun Microsystems是首批為RPC支持庫和RPC編譯器提供商業化支持的公司之一。在20世紀80年代由Sun公司提供並支持Sun公司的網絡文件系統(NFS)。由Sun和AT&T牽頭,該協議被推動成為開放式網絡計算的標准。它是一個輕量級的RPC系統,並可以在大多數的POSIX和類POSIX的操作系統上可用,包括了Linux,SunOS,OSX以及各種版本的BSD。它通常被稱之為Sun RPC或者是ONC RPC。
ONC RPC提供了一個編譯器,它接受遠程過程接口的定義,並生成客戶機和服務器的存根函數。這個編譯器叫做rpcgen。在運行這個編譯器之前,程序員必須提供接口的定義。接口定義包含了函數聲明,它們按版本號分組,並由一個惟一的程序編號來標識。這個程序編號就使得客戶端能夠識別他們需要的接口。版本號對於那些沒有更新到最新代碼的客戶端來說是非常重要的,它確保這個客戶端仍然能夠連接到一個新的服務器,只要這個服務器依然支持舊的函數接口。
在網絡中傳輸的參數被編組成為一種被稱之為XDR(eXternal Data Representation)的隱式序列化格式。這就確保了可以將參數發送到可能使用不同字節順序、不同大小整數、或不同浮點或字符串表示的異構系統中。最后,Sun RPC提供了一個運行時庫,它實現了支持RPC的必要協議和Socket例程。
所有的程序員都必須要編寫客戶端代碼,服務器函數,以及一個RPC接口定義。具體讓我們來看看以下的C語言代碼例子。
客戶端程序:(client.c)

1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <rpc/rpc.h> 4 #include <netconfig.h> 5 #include "date.h" 6 7 main(argc, argv) 8 int argc; 9 char **argv; 10 { 11 CLIENT *cl; /* rpc handle */ 12 char *server; 13 long *lresult; /* return from bin_date_1 */ 14 char **sresult; /* return from str_date_1 */ 15 16 if (argc != 2) { 17 fprintf(stderr, "usage: %s hostname\n", argv[0]); 18 exit(1); 19 } 20 server = argv[1]; /* get the name of the server */ 21 22 /* create the client handle */ 23 if ((cl=clnt_create(server, DATE_PROG, 24 DATE_VERS, "netpath")) == NULL) { 25 /* failed! */ 26 clnt_pcreateerror(server); 27 exit(1); 28 } 29 30 /* call the procedure bin_date */ 31 if ((lresult=bin_date_1(NULL, cl))==NULL) { 32 /* failed ! */ 33 clnt_perror(cl, server); 34 exit(1); 35 } 36 printf("time on %s is %ld\n", server, *lresult); 37 38 /* have the server convert the result to a date string */ 39 if ((sresult=str_date_1(lresult, cl)) == NULL) { 40 /* failed ! */ 41 clnt_perror(cl, server); 42 exit(1); 43 } 44 45 printf("date is %s\n", *sresult); 46 clnt_destroy(cl); /* get rid of the handle */ 47 exit(0); 48 }
服務端程序:(server.c)

1 #include <rpc/rpc.h> 2 #include "date.h" 3 4 /* bin_date_1 returns the system time in binary format */ 5 6 long * 7 bin_date_1() 8 { 9 static long timeval; /* must be static!! This value is */ 10 /* used by rpc after bin_date_1 returns */ 11 long time(); /* Unix time function; returns time */ 12 13 timeval = time((long *)0); 14 return &timeval; 15 } 16 17 /* str_date_1 converts a binary time into a date string */ 18 19 char ** 20 str_date_1(bintime) 21 long *bintime; 22 { 23 static char *ptr; /* return value... MUST be static! */ 24 char *ctime(); /* Unix library function that does the work */ 25 ptr = ctime(bintime); 26 return &ptr; 27 }
RPC接口定義:(date.x)

1 /* date.x - description of remote date service */ 2 3 /* we define two procedures: */ 4 /* bin_date_1 returns the time in binary format */ 5 /* (seconds since Jan 1, 1970 00:00:00 GMT) */ 6 /* str_date_1 converts a binary time to a readable date string */ 7 8 program DATE_PROG { 9 version DATE_VERS { 10 long BIN_DATE(void) = 1; /* procedure number = 1 */ 11 string STR_DATE(long) = 2; /* procedure number = 2 */ 12 } = 1; 13 } = 0x31415926;
當這個RPC接口定義文件date.x被rpcgen編譯器編譯的時候,它會生成3個或者4個文件。
- date.h
包含程序的定義、版本和函數的聲明。客戶端和服務器函數都應該包含這個文件。
- date_svc.c
實現服務器存根的代碼。
- date_clnt.c
實現客戶端存根的代碼。
- date_xdr.c
包含將數據轉換為XDR格式的XDR例程。如果生成此文件,則應將其編譯並鏈接到客戶端和服務器函數。
創建客戶端和服務器可執行文件的第一步便是編譯接口定義文件date.x。在此之后,客戶端和服務器的函數都可以被編譯,並與rpcgen生成的各自的存根函數相關聯。
在舊的RPC版本中,我們要么使用字符串“tcp”,要么使用字符串“udp”來作為其網絡傳輸的協議。此做法依然適用,在RPC的Linux實現中,這甚至是一個必需的做法。為了使接口能夠更加靈活,UNIX System v4(SunOS v5)網絡選擇例程允許一個更為通用的規范。他們通過搜索一個/etc/netconfig的文件,來找到第一個滿足需求的提供者。最后一個參數可以是:
- “netpath”
搜索一個NETPATH環境變量,作為傳輸協議的首選
- “circuit_n”
在NETPATH列表中找到第一個虛擬電路提供程序,“datagram_n”(在NETPATH列表中找到第一個數據報提供程序)
- “visible”
在/etc/netconfig中找到第一個可見的傳輸提供程序
- “circuit_v”
在/etc/netconfig中找到第一個可見的虛擬電路傳輸提供程序
- “datagram_v”
在/etc/netconfig中找到第一個可視數據報傳輸提供程序。
遠程過程調用在設計之初僅支持單一的參數,在之后的發展過程中,逐步改進以支持多個參數的傳遞。在一些rpcgen的版本中,依然默認支持單一的參數,例如蘋果的OSX操作系統。為了能夠支持多個參數,我們需要首先定義一個結構,讓它包含所有的參數數據,初始化它並傳遞這個數據結構。
遠程過程調用返回一個指向結果的指針而並非結果本身。因此,服務端的函數也必須因此而做出改動,使其可以接受一個指向一個在RPC接口定義文件中定義的值的指針來作為入參,並返回一個指向結果的指針來作為返回結果。在服務器端,指針必須指向一個靜態的數據,否則,在過程的框架被釋放或者調用過程返回時,該指針會指向一個未定義的區域。在客戶端,這個返回指針可以讓我們區分出一個失敗的RPC結果(空指針)或者是一個null返回結果(間接的null指針)。
RPC 過程的名稱若在 RPC 定義文件中做了定義,則會轉換為小寫,並在后綴加下划線,后跟一個版本號。例如,BIN_DATE將會被轉成為引用函數 bin_date_1 。你的服務器端代碼必須實現 bin_date_1。
What happens when we run the program?
服務端
當服務器啟動的時候,服務器存根函數將會在后台進程中運行(如果當你不再需要使用這個存根函數的時候,你可以通過ps的指令來找到對應的進程,並結束此進程)。存根函數會創建一個Socket並把本地任一個可用的端口綁定到此新建的Socket上。緊接着它便會調用一個svc_register的RPC庫函數,通過port mapper來注冊程序的版本跟編號。這個port mapper是另外一個獨立的進程,在服務器啟動時便會運行。它負責追蹤端口號,本版號以及程序編號。在Unix System v4系統中,這個進程稱之為rpcbind;在Linux,OSX以及BSD系統中,它被稱之為portmap。
客戶端
當我們啟動客戶端代碼的時候,它便會利用遠程系統的名稱,程序編號,版本號以及傳輸協議來調用一個clnt_creat函數。它通過遠程系統上的port mapper,在系統上找到合適的端口。
客戶端緊接着調用RPC存根函數(例如以上的例子,調用bin_date_1)。這個存根函數便會向服務器端發送一條消息並等待返回結果。
服務端接收到來自客戶端的消息,接着調用服務端的函數(服務端的bin_date_1函數),處理完畢之后將結果返回給客戶端存根。客戶端存根再把結果返回給調用的最終客戶端代碼。
整個過程可以參見下圖圖3所示:
圖3:Function Lookup in ONC RPC
分布式環境下的RPC (DEC RPC)
分布式計算環境(DCE)是由開放軟件基金會(Open Software Fundation)設計的一組組件,用於為分布式應用程序和分布式環境提供支持。在與X/Open合並之后,這個組織成為The Open Group。DCE提供的組件包括:分布式文件服務、時間服務、目錄、服務和其他幾個組件。這里,我們感興趣的是DCE的遠程過程調用。它與Sun RPC非常相似。接口是用一個接口定義語言編寫的,稱為接口定義符號(IDN),它與函數原型定義非常的類似。
Sun RPC的一個不足之處在於用一個32位的數字來唯一標識一個服務器的標識。盡管這個空間可以表示的范圍比在Socket下的16位數字空間要大得多,但它依然無法滿足唯一性的要求。DCE RPC通過不讓程序員來“想”這個數字來解決這個問題。在編寫應用程序的時候,第一步我們要做的便是使用uuidgen程序來獲得一個唯一ID。該程序生成一個包含接口ID的IDN原型文件,並保證此ID不再被其他程序使用。它是一個128位的值,包含一個encoded的位置代碼和創建時間。然后用戶編輯原型文件,填充遠程過程聲明。
在這一步之后,使用一個類似於rpcgen的IDN編譯器---dceidl,來生成頭部,客戶存根函數以及服務器存根函數。
Sun RPC的另一個缺陷是客戶端必須知道服務器所在的機器。然后,它可以向該機器上的RPC名稱服務器請求對應於它希望訪問的程序號的端口號。DCE支持將多台機器組織成稱為cell的管理實體。每台機器都知道如何與負責維護cell服務信息的機器通信:cell directory server。
對於Sun RPC,服務器只向本地名稱服務器(portmapper)來注冊它的{程序號到端口映射}。在DCE下,服務器在本地機器上使用RPC守護進程(名稱服務器)來注冊它的端點(端口),並將它的程序名稱注冊到它的cell目錄服務器上的機器映射。當客戶端想要與RPC服務器建立通信的時候,它首先要求它的cell directory server定位服務器所在的機器。然后,客戶端與該機器上的RPC守護進程進行對話,以獲取服務器進程的端口號。DCE也支持更為復雜的跨cell搜索的問題。
DCE RPC定義了一種稱之為NDR (Network Data Representation)的數據格式用於對網絡消息進行編碼來封送信息。與用一個單一的規范來表示不同的數據類型不同,NDR 支持多規范(multi-canonical)格式。它允許客戶端來自由選擇使用哪一種格式,理想的情況是,它不需要將它從本地類型來進行轉換。如果這不同於服務器的本地數據表示,服務器仍然需要進行轉換,但多規范格式可以避免當客戶端和服務器都共享相同的本地格式的情況下轉換為其他外部格式。例如,在一個規定了使用大端字節序網絡數據格式的情況下,客戶端和服務器都只支持小端字節序,那么客戶端必須將每個數據從小端字節序轉為大端字節序,然后從客戶端發送給服務端,而當服務器接受到消息后,又將每個數據轉回小端字節序。多規范網絡數據表示,將允許客戶端向網絡中發送網絡消息包含小端字節序格式的數據。
圖4:Function Lookup in DCE RPC
下一篇將繼續,介紹第二代的RPC系統。
我要小額贊助,鼓勵作者寫出更好的文章