RMI 通信協(xié)議
10.1 概述
RMI 協(xié)議使用另兩個協(xié)議作為其在通信格式:Java 對象序列化和 HTTP。對象序
列化協(xié)議用于編組調(diào)用和返回數(shù)據(jù)。HTTP 協(xié)議用于“投寄”遠程方法調(diào)用,并在
情況允許時獲得返回數(shù)據(jù)。每種協(xié)議都有專門的語法文檔。產(chǎn)品規(guī)則中的非終結(jié)
符號可能會引用其它協(xié)議(對象序列化或 HTTP)所管理的規(guī)則。在跨協(xié)議邊界時
,后續(xù)產(chǎn)品將使用該嵌入的協(xié)議。
關(guān)于語法符號的說明
我們使用與 Java 語言規(guī)范(參見 JLS 的第 2.3 節(jié))中類似的符號。
流中的控制代碼由十六進制形式的文字值表示。
語法中的有些非終結(jié)符號表示調(diào)用中提供的與應(yīng)用程序有關(guān)的值。這種非終結(jié)符
號的定義由其 Java 類型組成。語法后面是將這些非終結(jié)字符映射到相應(yīng)類型的
表。
10.2 RMI 傳輸協(xié)議
RMI 的通信格式由 Stream 表示。這里所采用的術(shù)語是從客戶機的角度來講的。
out 指輸出消息,而 in 指輸入消息。傳輸標題的內(nèi)容并未用對象序列化進行格
式化。
Stream: Out In
RMI 所用的輸入和輸出流是成對出現(xiàn)的。每個 out 流都有相應(yīng)的 in 流。在語法
中,out 流映射到套接字的輸出流(從客戶機角度)。in 流(在語法中)將與相
應(yīng)套接字的輸入流配對。由于輸出與輸入流是成對的,所以輸入流中唯一需要的
標題信息就是一個表示是否理解協(xié)議的確認;其他標題信息(例如魔數(shù)和版本號
)都將由流對的上下文所隱含。
10.2.1 輸出流格式
RMI 中的輸出流由傳輸標題信息后跟一個消息序列組成。此外,輸出流也可包含
嵌入在 HTTP 協(xié)議中的調(diào)用。
Out: Header Messages HttpMessage
Header: 0x4a 0x52 0x4d 0x49 Version Protocol
Version: 0x00 0x01
Protocol: StreamProtocol SingleOpProtocol MultiplexProtocol
StreamProtocol: 0x4b
SingleOpProtocol: 0x4c
MultiplexProtocol: 0x4d
Messages: Message Messages Message
Messages 將包裝在 Protocol 指定的特定協(xié)議內(nèi)。對于 SingleOpProtocol,He
ader 的后面可能只有一個 Message,且該 Message 沒有包裝其它數(shù)據(jù)。Single
OpProtocol 用于 HTTP 請求中所嵌入的調(diào)用,其中請求和響應(yīng)都只能為一個。
對于 StreamProtocol 和 MultiplexProtocol,服務(wù)器必須用字節(jié) 0x4e(表示支
持該協(xié)議)和 EndpointIdentifier(包含主機名和端口號,服務(wù)器可以看到它們
在被客戶機使用)進行響應(yīng)。如果由于安全原因而無法執(zhí)行該操作,客戶機即可
使用該信息來確定其主機名。隨后,客戶機必須用另一個包含接受連接的缺省端
點的 EndpointIdentifier 進行響應(yīng)。對于MultiplexProtocol,服務(wù)器可以用它
來標識客戶機。
對于 StreamProtocol,本次端點協(xié)商后,將在輸出流上發(fā)送 Messages,而不對
數(shù)據(jù)進行進一步打包。對于 MultiplexProtocol,套接字將連接用作多路復(fù)用連
接的具體連接,如第 10.6 節(jié)“RMI 的多路復(fù)用協(xié)議”中所述。在該多路復(fù)用連
接上初始化的虛擬連接由一系列 Messages 組成,如下所述。
輸出消息共有三種:Call、Ping 和 DgcAck。Call 將對方法調(diào)用進行編碼。Pin
g 是一個傳輸級消息,用于測試遠程虛擬機的活動性。DGCAck 是一個對服務(wù)器的
分布式垃圾收集器的確認,指示客戶機已經(jīng)接收到服務(wù)器返回值中的遠程對象。
Message: Call Ping DgcAck
Call: 0x50 CallData
Ping: 0x52
DgcAck: 0x54 UniqueIdentifier
10.2.2 輸入流格式
當前輸入信息共有三種:ReturnData、HttpReturn 和 PingAck。ReturnData 是
“正常”RMI 調(diào)用的結(jié)果。HttpReturn 是 HTTP 協(xié)議中嵌入調(diào)用的返回結(jié)果。P
ingAck 是對 Ping 消息的確認。
In: ProtocolAck Returns ProtocolNotSupported HttpReturn
ProtocolAck: 0x4e
ProtocolNotSupported: 0x4f
Returns: Return Returns Return
Return: ReturnData PingAck
ReturnData: 0x51 ReturnValue
PingAck: 0x53
10.3 RMI 對對象序列化協(xié)議的使用
RMI 調(diào)用中的調(diào)用和返回數(shù)據(jù)將使用 Java 對象序列化協(xié)議進行格式化。每個方
法調(diào)用的 CallData 表示為 ObjectIdentifier(調(diào)用的目標)、Operation(代
表要調(diào)用方法的數(shù)字)、Hash(檢驗客戶機 stub 與遠程對象 skeleton 是否使
用同一 stub 協(xié)議的數(shù)字),后跟該調(diào)用的零個或多個參數(shù)列表。
在 JDK1.1 stub 協(xié)議中,Operation 代表方法號(由 rmic 分配),而 Hash 是
stub/skeleton 散列,它是該 stub 的接口散列。在 JDK1.2 stub 協(xié)議(利用
帶 -v1.2 選項的 rmic 生成 JDK1.2 stub)中,Operation 的值為 -1 且 Hash
代表了所要調(diào)用方法的散列。散列將在“ RemoteRef 接口”一節(jié)中介紹。
CallData: ObjectIdentifier Operation Hash Argumentsopt
ObjectIdentifier: ObjectNumber UniqueIdentifier
UniqueIdentifier: Number Time Count
Arguments: Value Arguments Value
Value: Object Primitive
RMI 調(diào)用的 ReturnValue 由指示正常或異常返回的返回代碼、標記返回值的 Un
iqueIdentifier(用于在必要時發(fā)送 DGCAck)后跟以下返回結(jié)果組成:返回的值
或拋出的異常。
ReturnValue: 0x01 UniqueIdentifier Valueopt 0x02 UniqueIdentifier Exc
eption
----------------------------------------------------------------------
----------
注意 - ObjectIdentifier、UniqueIdentifier 和 EndpointIdentifier 并不是
用缺省序列化編寫的,而是各自使用自己的 write 方法(但不是對象序列化所用
的 writeObject 方法);每種標識符的 write 方法都將其組件數(shù)據(jù)連續(xù)添加到
輸出流中。
----------------------------------------------------------------------
----------
10.3.1 類注解和類加載
RMI 分別覆蓋了 ObjectOutputStream 和 ObjectInputStream 的 annotateClas
s 和 resolveClass 方法。每個類都用 codebase URL(加載該類的位置)進行注
解。annotateClass 方法中將查詢加載該類的類加載器以得到其 codebase URL。
如果類加載器非空且其 codebase 也為非空,則將使用 ObjectOutputStream.wr
iteObject 方法將該 codebase 寫入流中;否則將使用 writeObject 方法將空值
寫入流中。注意:最好不要注解“java”包中的類,因為它們對于接收者來說總
是可用的。
類注解是在序列化恢復(fù)期間用 ObjectInputStream.resolveClass 方法解析的。
resolveClass 方法首先用 ObjectInputStream.readObject 方法讀取注解。如果
注解(codebase URL)非空,它就獲得該 URL 的類加載器并試圖加載該類。利用
java.net.URLConnection 獲取類字節(jié),即可對該類進行加載(與 web 瀏覽器的
applet 類加載器所用的機制相同)。
10.4 RMI 對 HTTP POST 協(xié)議的使用
為了通過防火墻調(diào)用遠程方法,有些 RMI 調(diào)用使用了 HTTP 協(xié)議,尤其是 HTTP
POST。在傳遞標題中指定的 URL 可以為下列內(nèi)容之一:
第一個 URL 用于與特定 host 和 port 上的 RMI 服務(wù)器直接通信。第二種形式
的 URL 用于調(diào)用服務(wù)器上的“cgi”腳本,后者將把調(diào)用轉(zhuǎn)發(fā)給指定 port 上的
服務(wù)器。
HttpPostHeader 是 POST 請求的標準 HTTP 標題。HttpResponseHeader 是對傳
遞過程的標準 HTTP 響應(yīng)。如果響應(yīng)狀態(tài)代碼不是 200,則認為沒有返回值。注
意一個 HTTP POST 請求中只能嵌入一個 RMI 調(diào)用。
HttpMessage: HttpPostHeader Header Message
HttpReturn: HttpResponseHeader Return
----------------------------------------------------------------------
----------
注意 - 只有 SingleOpProtocol 出現(xiàn)在 HttpMessage 的標題中。HttpReturn 不
包含用于確認協(xié)議的字節(jié)。
----------------------------------------------------------------------
----------
10.5 RMI 的與應(yīng)用程序有關(guān)的值
本表列表出 RMI 所用的代表與應(yīng)用程序有關(guān)的值的非終結(jié)符號。該表將每個符號
映射為相應(yīng)的類型。每個符號都分別使用它所嵌入在其中的協(xié)議進行格式化。
Count short
Exception java.lang.Exception
Hash long
Hostname UTF
Number int
Object java.lang.Object
ObjectNumber long
Operation int
PortNumber int
Primitive byte, int, short, long...
Time long
10.6 RMI 的多路復(fù)用協(xié)議
多路復(fù)用的目的是提供一種模型,其中兩個端點都可打開多個到另一端點的全雙
工連接,而在相同環(huán)境下,使用其他工具(例如 TCP 連接)時,只有一個端點能
打開這樣的雙向連接。利用這種簡單的多路復(fù)用協(xié)議,RMI 即可允許客戶在某些
其他協(xié)議無能為力的情況下,連接到 RMI 的服務(wù)器對象上。例如,有些 applet
環(huán)境的安全管理器不允許創(chuàng)建服務(wù)器套接字監(jiān)聽到來的連接,以防止這種 appl
et 從直接套接字連接上導(dǎo)出 RMI 對象及提供遠程調(diào)用服務(wù)。但是,如果該 app
let 可以打開到其 codebase 主機的正常套接字連接,它就可以在該連接上使用
多路復(fù)用協(xié)議,從而允許 codebase 主機調(diào)用該 applet 所導(dǎo)出的 RMI 對象的方
法。本節(jié)介紹了多路復(fù)用協(xié)議的格式和規(guī)則。
10.6.1 定義
本節(jié)定義一些將在協(xié)議其余部分使用的術(shù)語。
端點是用多路復(fù)用協(xié)議連接的兩個用戶之一。
多路復(fù)用協(xié)議必須位于已有的雙向可靠字節(jié)流之上,假設(shè)由一個端點向另一個端
點進行初始化。在當前的 RMI 用法中,它通常是 TCP 連接,由 java.net.Sock
et 對象建立。該連接稱為具體連接。
多路復(fù)用協(xié)議有助于虛擬連接的使用。虛擬連接本身就是雙向的可靠字節(jié)流,代
表兩個端點之間的特定會話。一個連接上兩個端點之間的虛擬連接集組成一個多
路復(fù)用連接。使用多路復(fù)用協(xié)議,虛擬連接可以由任一端點打開和關(guān)閉。虛擬連
接相對給定端點的狀態(tài)由在具體連接上發(fā)送和接收的多路復(fù)用協(xié)議元素定義。該
狀態(tài)涉及連接是打開還是關(guān)閉、傳送的實際數(shù)據(jù)及相關(guān)的流控制機制。如果沒有
特別說明,本節(jié)中其余部分將使用術(shù)語連接表示虛擬連接。
給定多路復(fù)用連接內(nèi)的虛擬連接由一個 16 位整數(shù)標識,稱為連接標識符。因而
,一個多路復(fù)用連接中可能存在 65,536 個虛擬連接。實現(xiàn)可能會限制能同時使
用的虛擬連接數(shù)。
10.6.2 連接狀態(tài)和流控制
連接由用多路復(fù)用協(xié)議定義的各種操作來控制。下面是協(xié)議所定義的操作名:OP
EN、CLOSE、CLOSEACK、REQUEST 和 TRANSMIT。所有操作的準確格式和規(guī)則將在
第 10.6.3 節(jié) “協(xié)議格式”中詳細介紹。
OPEN、CLOSE 和 CLOSEACK 操作控制連接的打開和關(guān)閉,而 REQUEST 和 TRANSM
IT 操作用于在流控制機制的限制內(nèi)在打開的連接上傳輸數(shù)據(jù)。
連接狀態(tài)
如果端點發(fā)送連接的 OPEN 操作或接收到連接的 OPEN 操作(且隨后沒有關(guān)閉它
),則該虛擬連接相對于該端點即為打開的。下面介紹不同的協(xié)議操作。
如果端點發(fā)送連接的 CLOSE 操作,但隨后沒有接收到該連接的 CLOSE 或 CLOSE
ACK 操作,則該虛擬連接相對于該端點是等待關(guān)閉的。
如果端點從來沒有打開過連接或接收到連接的 CLOSE 或 CLOSEACK 操作(且隨后
沒有打開),則該虛擬連接相對于該端點是關(guān)閉的。
流控制
多路復(fù)用協(xié)議使用簡單的包流控制機制允許多個虛擬連接并存于同一具體連接上
。流控制機制的高級要求是所有虛擬連接的狀態(tài)都是獨立的;一個連接的狀態(tài)不
會影響其他連接。例如,如果處理來自某個連接的數(shù)據(jù)的數(shù)據(jù)緩沖區(qū)已滿,應(yīng)不
會防礙其他連接的數(shù)據(jù)傳輸和處理。如果連接的繼續(xù)依賴于另一個連接的結(jié)束(
例如遞歸 RMI 調(diào)用時),則這一點將至關(guān)重要。因而,它的實際意義是實現(xiàn)必須
總能消耗和處理在具體連接上(假定它遵循該規(guī)范)準備輸入的所有多路復(fù)用協(xié)
議數(shù)據(jù)。
每個端點具有兩個與各連接相關(guān)聯(lián)的狀態(tài)值:該端點已經(jīng)請求但尚未接收到的數(shù)
據(jù)字節(jié)數(shù)(輸入請求數(shù))和另一端點請求但該端點尚未提供的數(shù)據(jù)字節(jié)數(shù)(輸出
請求數(shù))。
端點的輸出請求數(shù)在從其他端點接收到 REQUEST 操作時將增大,而在它發(fā)送 TR
ANSMIT 操作時將減小。端點的輸入請求數(shù)在它發(fā)送 REQUEST 操作時將增大,而
在它接收到 TRANSMIT 操作時將減小。這些值如果為負就將違反協(xié)議。
如果端點發(fā)送 REQUEST 操作而導(dǎo)致其輸入請求數(shù)增大并超過其當前可以無阻塞處
理的字節(jié)數(shù),則違反協(xié)議。但如果連接的用戶在等待讀取數(shù)據(jù),則應(yīng)確保其輸入
請求數(shù)大于零。
如果端點發(fā)送的 TRANSMIT 操作包含有比其輸出請求數(shù)更多的字節(jié),則違反協(xié)議
。它可以緩沖外流的數(shù)據(jù),直到連接用戶請求顯式刷新寫入到連接中的數(shù)據(jù)。但
如果因為顯式的刷新或?qū)崿F(xiàn)的輸入緩沖區(qū)滿而必須在連接上發(fā)送數(shù)據(jù),則連接用
戶可能被阻塞,直到有足夠 TRANSMIT 操作。
在滿足上述規(guī)則的前提下,實現(xiàn)可以相對自由地發(fā)送 REQUEST 和 TRANSMIT 操作
。例如,如果其輸入緩沖區(qū)不空,則端點可以請求連接的更多數(shù)據(jù)。
10.6.3 協(xié)議格式
多路復(fù)用協(xié)議的字節(jié)流格式由連續(xù)的可變長度記錄序列組成。記錄的第一個字節(jié)
是一個操作碼,它可以識別記錄的操作并可確定其內(nèi)容其余部分的格式。我們定
義了下列合法的操作碼:
值 名稱
0xE1 OPEN
0xE2 CLOSE
0xE3 CLOSEACK
0xE4 REQUEST
0xE5 TRANSMIT
如果記錄的第一個字節(jié)不是所定義的操作碼,則違反協(xié)議。下面各節(jié)介紹了每種
操作碼的記錄格式。
OPEN 操作
下面是 OPEN 操作的記錄格式:
大小(字節(jié)) 名字 描述
1 opcode 操作碼 (OPEN)
2 ID 連接標識符
端點將發(fā)送 OPEN 操作以打開指定的連接。如果ID 指向?qū)Πl(fā)送端點當前已打開或
即將關(guān)閉的連接,則違反協(xié)議。打開連接后,連接的輸入和請求數(shù)狀態(tài)在兩個端
點上都為零。
接收到 OPEN 操作表示另一端點正在打開指定的連接。打開連接后,連接的輸出
和請求數(shù)狀態(tài)在兩個端點處都為零。
為防止兩端點間的標識符沖突,有效連接標識符空間將根據(jù)最高位的值分為兩半
。每個端點僅允許打開高位為某一特定值的連接。啟動具體連接的端點必須只打
開高位為標識符中的連接,另一端點必須只打開高位為零的連接。例如,如果不
能創(chuàng)建服務(wù)器套接字的 RMI applet 啟動了與其 codebase 主機的多路復(fù)用連接
,則該 applet 可以打開標識符范圍為 0x8000-7FFF 的虛擬連接,而服務(wù)器可以
打開標識符范圍為 0-0x7FFF 的虛擬連接。
CLOSE 操作
以下是 CLOSE 操作的記錄格式:
大小(字節(jié)) 名字 描述
1 opcode 操作代碼 (OPEN)
2 ID 連接標識符
端點發(fā)送 CLOSE 操作以關(guān)閉指定的連接。如果 ID 指向?qū)Πl(fā)送端點當前已關(guān)閉或
即將關(guān)閉的連接(如果它已發(fā)送過此連接的 CLOSE 操作,也可能是對接收端點即
將關(guān)閉的連接),則違反協(xié)議。 發(fā)送 CLOSE 后,連接就成為對發(fā)送端點即將關(guān)
閉的連接。因此,該端點將不能重新打開該連接,直到它從另一端點接收到 CLO
SE 或 CLOSEACK 為止。
接收到 CLOSE 操作表示另一端點已關(guān)閉指定的連接,因此該連接已在接收端點上
被關(guān)閉。雖然接收端點可能不再為此連接發(fā)送其它操作(直到被再次打開),但
它仍應(yīng)為此連接的讀者提供實現(xiàn)的輸入緩沖區(qū)中的數(shù)據(jù)。如果連接已經(jīng)被打開(
而不是即將關(guān)閉),則接收端點必須用 CLOSEACK 操作作為響應(yīng)。
CLOSEACK 操作
以下是 CLOSEACK 操作的記錄格式:
大?。ㄗ止?jié)) 名字 描述
1 opcode 操作代碼 (OPEN)
2 ID 連接標識符
端點發(fā)送 CLOSEACK 操作以表明已收到來自接收端點的 CLOSE 操作。如果收到操
作時 ID 指向的連接不是對接收端點將要關(guān)閉的連接,則違反協(xié)議。
接收到 CLOSEACK 操作可將指定連接的狀態(tài)從即將關(guān)閉改為已關(guān)閉,因此以后還
可重新打開連接。
REQUEST 操作
以下是 REQUEST 操作的記錄格式:
大?。ㄗ止?jié)) 名字 描述
1 opcode 操作代碼 (OPEN)
2 ID 連接標識符
4 count 請求的額外字節(jié)數(shù)
端點發(fā)送 REQUEST 操作以增大指定連接的輸入請求數(shù)。如果 ID 未指向發(fā)送端點
打開的連接,則違反協(xié)議。端點的輸入請求數(shù)按值 count 遞增。count 的值是
32 位有符號整數(shù)。如果為負數(shù)或零,則違反協(xié)議。
接收到 REQUEST 操作將使指定連接的輸出請求數(shù)按 count 增加。如果接收端點
即將關(guān)閉連接,則將忽略 REQUEST 操作。
TRANSMIT 操作
以下是 TRANSMIT 操作的記錄格式。
大?。ㄗ止?jié)) 名字 描述
1 opcode 操作碼 (OPEN)
2 ID 連接標識符
4 count 傳輸?shù)淖止?jié)數(shù)
count data 傳輸數(shù)據(jù)
端點發(fā)送 TRANSMIT 操作后,才真正在指定連接上傳輸數(shù)據(jù)。如果 ID 未指向?qū)?
發(fā)送端點打開的連接,則違反協(xié)議。端點的輸出請求按值 count 遞減。count 的
值是 32 位有符號整數(shù)。如果為負數(shù)或零,則違反協(xié)議。如果 TRANSMIT 操作導(dǎo)
致輸出請求數(shù)成為負數(shù),則也違反協(xié)議。
接收到 TRANSMIT 操作時從連接中可讀到的字節(jié)隊列將增加 count 字節(jié)的數(shù)據(jù)。
接收端點的輸入請求數(shù)按值 count 遞減。如果這會使輸入請求數(shù)成為零,而該連
接的用戶卻試圖讀取更多數(shù)據(jù),則該端點應(yīng)用另一個 REQUEST 操作作為響應(yīng)。如
果接收端點即將關(guān)閉連接,則將忽略 TRANSMIT 操作。
違反協(xié)議
如果出現(xiàn)上述違反協(xié)議的現(xiàn)象,或者在具體連接中檢測到通訊錯誤,則多路復(fù)用
連接即被關(guān)閉。實際連接將被終止,而所有虛擬連接也被立即關(guān)閉。連接的用戶
可以讀取虛擬連接中已經(jīng)可以讀取的數(shù)據(jù)。