接口 Interfaces
Slice的中心焦點是定義接口
這里定義了一個稱為Clock的接口類型。該接口支持兩個操作:getTime和setTime。客戶機通過在對象的代理上調用操作來訪問支持時鐘接口的對象:要讀取當前時間,客戶端調用getTime操作;要設置當前時間,客戶端調用setTime操作,傳遞類型TimeOfDay的參數。
在代理上調用操作會指示Ice運行期間向目標對象發送消息,目標對象可以在另一個地址空間中,或者可以與調用者并列(在同一進程中) - 目標對象的位置對客戶端是透明的。 如果目標對象位于另一個(可能是遠程的)地址空間中,則Ice運行時間將通過遠程過程調用調用該操作; 如果目標與客戶端并置,則Ice運行時間會繞過網絡堆棧,從而更有效地傳遞請求。
操作 Operations
參數和返回值
操作定義必須包含返回類型和零個或多個參數定義。 例如,在Clock接口中,getTime方法的返回類型為TimeOfDay,setTime方法的返回類型為void。 您必須使用void來表示操作不返回值 - Slice操作沒有默認返回類型。
操作可以具有一個或多個輸入參數。 例如,setTime接受一個名為Time的TimeOfDay類型的單個輸入參數。 當然,您可以使用多個輸入參數:
請注意,參數名稱(如Java)是必需的。 您不能省略參數名稱,因此下面這樣是錯誤的:
默認情況下,參數從客戶端發送到服務器,也就是它們是輸入參數。 要將值從服務器傳遞給客戶端,可以使用由out關鍵字指示的輸出參數。 例如,在Clock接口中定義getTime操作的另一種方法是:
這實現了相同的事情,但使用輸出參數而不是返回值。 與輸入參數一樣,您可以使用多個輸出參數:
如果您有一個操作既有輸入又有輸出參數,輸出參數必須在輸入參數之后:
Slice不支持一個參數即是輸入又是輸出參數(通過參考調用)。 原因在于,對于遠程調用,引用參數不會導致在編程語言中引用的相同的節省。 (數據仍然需要在兩個方向復制,并且編組效率中的任何增益都可以忽略不計)。另外,參考(或輸入 - 輸出)參數會導致更復雜的語言映射,隨之增加代碼大小。
可選參數和返回值
從Ice 3.5開始,一個方操作的返回值和參數可以被聲明為可選的,以指示程序可以使其值保持不變。 未聲明為可選參數的參數稱為必需參數; 一個程序必須提供所有必需參數的合法值。 在下面的討論中,我們使用參數來引用輸入參數,輸出參數和返回值。
必須為每個可選參數分配唯一的非負整數標記:
標簽的范圍僅限于其操作,對其他操作沒有影響。
操作的簽名可以包括任何需要的和可選參數的組合,但是輸出參數仍然必須在輸入參數之后:
操作風格定義
如您所料,語言映射遵循Slice中使用的操作定義的樣式:Slice返回類型映射到編程語言返回類型,而Slice參數映射到編程語言參數。
對于僅返回單個值的方法,通常從操作中返回值,而不是使用out-parameter。這種風格自然地映射成所有的編程語言。請注意,如果您使用外部參數,則在客戶端上強制使用不同的API樣式:大多數編程語言允許忽略函數的返回值,而通常無法忽略輸出參數。
對于返回多個值的操作,通常將所有值作為out-parameters返回,并使用void的返回類型。但是,由于具有多個輸出值的方法可以具有被認為比其余值更“重要”的一個特定值,因此規則并非全部清楚。一個常見的例子是一個迭代器操作,一個一個地從一個集合中返回項目:
next操作返回兩個值:檢索的記錄和一個bool值,表示收集結束條件。 (如果返回值為false,則已經到達了集合的結尾,并且參數r具有未定義的值。)這種定義方式可能很有用,因為它自然符合程序員編寫控制結構的方式。 例如:
操作重載
Slice不支持任何形式的操作重載。例如:
同一接口中的操作必須具有不同的名稱,無論它們具有什么類型和數量的參數。 存在此限制,因為重載函數無法在沒有內置支持的情況下映射到語言
冪等操作
一些操作,如Clock接口中的getTime,不會修改它們所操作對象的狀態。setTime確實修改了對象的狀態,但是也是冪等的。 您可以在Slice中注明如下:
這將getTime和setTime方法標記為冪等。如果操作的兩個連續調用與單個調用具有相同的效果,則操作是等冪的。例如,x = 1;是一個冪等的操作,因為它是否執行一次或兩次 - 無論是哪種方式,x都以值1結束。另一方面,x + = 1;不是一個冪等的操作,因為執行兩次會產生一個與執行一次不同的x值。顯然,只讀操作是冪等的。
該冪等關鍵字是有用的,因為它允許Ice運行時間在執行自動重試以從錯誤中恢復時更加積極。具體來說,Ice保證操作調用最多一次語義:
對于正常(不是冪等)操作,Ice運行時間必須對如何處理錯誤保守。例如,如果客戶端向服務器發送操作調用,然后失去連接性,則客戶端運行時間無法確定其發送的請求是否實際將其發送到服務器。這意味著運行時間不能通過重新建立連接并再次發送請求來嘗試從錯誤中恢復,因為這可能會導致第二次調用操作并違反最多一次語義;運行時間沒有選擇,而是將錯誤報告給應用程序。
對于冪等操作,另一方面,客戶端運行時間可以嘗試重新建立到服務器的連接,并且第二次安全地發送失敗的請求。如果第二次嘗試可以訪問服務器,一切都很好,應用程序從不會注意到(暫時的)故障。只有第二次嘗試失敗才需要運行時報告錯誤回應用程序。 (可以使用Ice配置參數來增加重試次數。)
用戶異常 User Exceptions
用戶異常語法和語義
看看Clock接口中的setTime操作,我們發現一個潛在的問題:由于TimeOfDay結構使用short作為每個字段的類型,如果客戶端調用setTime操作并傳遞一個具有無意義字段值的TimeOfDay值,會發生什么, 例如-199為分鐘字段,或42為小時? 顯然,向呼叫者提供一些沒有意義的指示是很好的。 Slice允許您定義用戶異常以指示客戶端的錯誤狀況。 例如:
用戶異常非常像一個結構,它包含一些數據成員。 但是,與結構不同,異常可以具有零數據成員,也就是空。 像類一樣,用戶異常支持繼承,可能包括可選的數據成員。
用戶異常成員的默認值
你可以為具有以下類型的異常數據成員指定一個默認值:
An integral type (byte, short, int, long)?
A floating point type (float or double)?
string?
bool?
enum
例如:
在操作中定義用戶異常
例外情況允許您在執行操作時出現錯誤情況,向客戶端返回任意數量的錯誤信息。 操作使用異常規范來指示可能返回給客戶端的異常:
此定義表示setTime操作可能會引發RangeError或Error用戶異常(而不會導致其他類型的異常)。如果客戶端收到RangeError異常,則該異常包含傳遞給setTime的TimeOfDay值,并導致錯誤(在errorTime成員中)以及可使用的最小和最大時間值(在minTime和maxTime成員中)。如果由于非法參數值引起的錯誤,setTime失敗,則會引發錯誤。顯然,因為Error沒有數據成員,客戶端將不知道是什么錯誤 - 它只是知道操作不起作用。
操作只能拋出其異常規范中列出的那些用戶異常。如果在運行時,執行一個操作會拋出一個未列在其異常規范中的異常,則客戶端會收到運行時異常),以指示該操作執行某些非法操作。為了表明操作不會引發任何用戶異常,只需省略異常規范。 (Slice中沒有空的異常規范。)
限制用戶異常
異常不是一級的數據類型,一級的數據類型也不是異常:
你不能將異常作為參數值傳遞。
你不能使用異常作為數據成員的類型。
你不能將異常用作序列的元素類型。
你不能將異常用作字典的鍵或值類型。
你不能拋出非異常類型的值(例如int或string類型的值)。
這些限制的原因是某些實現語言對異常使用特定和單獨的類型(與Slice相同)。 對于這樣的語言,如果可以將其用作普通的數據類型,則很難映射異常。 (通過允許使用任意類型作為異常,C ++在編程語言中有些不尋常。)
用戶異常繼承
異常支持繼承,例如:
這些定義設置了一個簡單的異常層次結構:
ErrorBase位于樹的根目錄,并包含一個解釋錯誤原因的字符串。
派生自ErrorBase的是RuntimeError和LogicError。 每個這些異常都包含一個枚舉值,進一步對錯誤進行分類。
最后,RangeError來自LogicError,并報告具體錯誤的詳細信息。
設置這樣的異常層次結構不僅有助于創建更可讀的規范,因為錯誤被分類,而且還可以在語言層面上使用,以獲得良好的優勢。 例如,Slice C ++映射保留異常層次結構,因此您可以將異常通常作為基礎異常捕獲,或者設置異常處理程序來處理特定的異常。
查看異常層次結構,不清楚在運行時,應用程序是否只會拋出大多數派生異常,例如RangeError,或者是否還會拋出基本異常,例如LogicError,RuntimeError和ErrorBase。 如果要表示基類異常,接口或類是抽象的(不會被實例化),則可以為該效果添加注釋。
請注意,如果操作的異常指定表示特定的異常類型,則在運行時,該操作的實現也可能會導致更多的派生異常。 例如:
在這個例子中,op可能會拋出一個Base或Derived異常,也就是說,與異常規范中列出的異常類型兼容的任何異常都可以在運行時拋出。
隨著系統的發展,將新的導出異常添加到現有的層次結構中是很常見的。 假設我們最初使用以下定義構建客戶端和服務器:
還假設在現場部署了大量客戶端,即升級系統時,無法輕松升級所有客戶端。 隨著應用程序的發展,系統將添加一個新的異常,服務器將重新部署到新的定義中:
這引發了如果服務器從doSomething中引發FatalApplicationError會發生什么的問題。 答案取決于客戶端是使用舊的還是更新的定義構建的:
如果客戶端使用與服務器相同的定義構建,那么它只會收到一個FatalApplicationError。
如果客戶端是用原始定義構建的,該客戶端就不知道FatalApplicationError是否存在。 在這種情況下,Ice運行時間會自動將異常切片到接收者理解的最為傳統的類型(在這種情況下為Error),并丟棄特定于異常派生部分的信息。 (這完全類似于通過值捕獲C ++異常 - 異常被切分為在catch子句中使用的類型。)
異常僅支持單繼承。 (多重繼承將難以映射成許多編程語言。)
運行時異常 Run-Time Exceptions
除了操作異常規范中列出的任何用戶異常外,操作還可能會導致Ice運行時異常。 運行時異常是指定與平臺相關的運行時錯誤的預定義異常。 例如,如果網絡錯誤中斷客戶端和服務器之間的通信,則會通過運行時異常(例如ConnectTimeoutException或SocketException)通知客戶端。
異常繼承的結構
所有的Ice運行時和用戶異常都安排在繼承層次結構中,如下所示:
Ice運行時異常的完整層次結構:
許多運行時異常都有自解釋的名稱,如MemoryLimitException。 其他指示Ice運行時的問題,如EncapsulationException。 還有一些只能通過應用程序編程錯誤出現,例如TwowayOnlyException。 實際上,您可能永遠不會看到這些例外情況。 但是,您將遇到一些運行時異常以及您應該知道的含義。
本地和遠程異常 Local Versus Remote Exceptions
普通異常? Common Exceptions
在客戶端檢測到大多數錯誤條件。 例如,如果嘗試聯系服務器失敗,則客戶端運行時會引發ConnectTimeoutException。 但是,服務器檢測到三個具體的錯誤條件(在Ice運行時異常層次結構圖中顯示為陰影),并通過Ice協議明確地顯示給客戶端運行時:ObjectNotExistException,FacetNotExistException和OperationNotExistException。
ObjectNotExistException
此異常指示請求已發送到服務器,但服務器無法找到具有嵌入在代理中的身份的servant。 換句話說,服務器找不到要發送請求的對象。
ObjectNotExistException是一個死亡證書:它表示服務器中的目標對象不存在。
Ice run time只有在沒有匹配的身份的方面存在時才會引發ObjectNotExistException; 否則,它會引發FacetNotExistException。
最可能的情況是這種情況,因為該對象在過去存在一段時間,并且已被破壞,但是如果客戶端使用具有從未創建的對象的身份的代理,那么也會引發相同的異常。 如果您收到此異常,則需要清理可能已分配的與您收到此異常的特定對象相關的資源。
FacetNotExistException
客戶端嘗試聯系對象的不存在的facets,即,服務器至少有一個具有給定身份的servant,但沒有具有匹配facet名稱的仆人。
OperationNotExistException
如果服務器能夠找到具有正確標識的對象,但在試圖發送客戶機的操作調用時,服務器發現目標對象沒有這樣的操作,則會觸發此異常。
你只會在兩種情況下看到此異常:
? 1你對不正確類型的代理使用了未經檢查的down-cast。
? 2客戶端使用指定操作存在的對象的接口定義構建客戶端,但服務器是使用不同版本的接口定義,其中不存在這個操作
未知異常 Unknown Exceptions
服務器端上的任何錯誤條件,在前面三個異常中沒有描述的情況,都被客戶稱為三個泛型異常之一(在Ice運行時異常層次圖中顯示的是陰影):UnknownUserException,UnknownLocalException, UnknownException.
UnknownUserException
這個異常指示操作實現拋出了一個Slice異常,該異常在操作的異常規范中沒有聲明(并且不是從操作的異常規范中的一個異常派生出來的)。
UnknownLocalException
如果一個操作的實現提出了一個運行時異常除了ObjectNotExistException,FacetNotExistException,或OperationNotExistException(如NotRegisteredException),客戶端收到一個UnknownLocalException。換句話說,Ice協議不傳輸服務器中遇到的確切異常,但是在答復中簡單地向客戶機返回一點,表明服務器遇到運行時異常。
UnknownException
一個操作已經拋出一個非Ice異常。例如,如果服務器中的操作拋出一個c++異常,例如char *或Java異常,例如ClassCastException,客戶端將接收到UnknownException。