構(gòu)建排隊WCF響應(yīng)服務(wù).doc_第1頁
構(gòu)建排隊WCF響應(yīng)服務(wù).doc_第2頁
構(gòu)建排隊WCF響應(yīng)服務(wù).doc_第3頁
構(gòu)建排隊WCF響應(yīng)服務(wù).doc_第4頁
構(gòu)建排隊WCF響應(yīng)服務(wù).doc_第5頁
已閱讀5頁,還剩9頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

構(gòu)建排隊 WCF 響應(yīng)服務(wù)Windows Communication Foundation (WCF) 使客戶端與服務(wù)之間能夠以非連接方式進(jìn)行通信??蛻舳藢⑾l(fā)布給隊列,服務(wù)稍后再對這些消息進(jìn)行處理。這種交互方式造就了一種不同于默認(rèn)的請求/響應(yīng)模式的編程模型,從而有望更好地平衡負(fù)載、提高可用性、進(jìn)行補(bǔ)償工作,為用戶帶來諸多好處。本專欄首先簡要介紹 Windows Communication Foundation 的排隊調(diào)用功能,然后提出“如何從排隊的調(diào)用獲取結(jié)果”這樣一個有趣的問題,接著通過一些超酷的 Windows Communication Foundation 編程技術(shù)以及我為此所編寫的助手類來找到解決辦法。排隊調(diào)用Windows Communication Foundation 使用 NetMsmqBinding 來支持排隊調(diào)用。Windows Communication Foundation 在傳輸消息時不是通過 TCP 或 HTTP,而是通過 Microsoft 消息隊列 (MSMQ)。客戶端也不是將 Windows Communication Foundation 消息發(fā)送到某個在線服務(wù),而是發(fā)送到 MSMQ 隊列。所有客戶端所面向和交互的對象是隊列,而非服務(wù)端點(diǎn)。因此,調(diào)用在本質(zhì)上是異步的、是不連接的。直到服務(wù)在將來某一時刻處理消息時,這些調(diào)用才得以執(zhí)行。請注意,Windows Communication Foundation 消息并不直接映射到 MSMQ 消息。一個 MSMQ 消息可以包含一個或多個 Windows Communication Foundation 消息,具體個數(shù)視合約會話模式而定。對于必需會話模式,多個 Windows Communication Foundation 調(diào)用可共存于一個 MSMQ 消息中;而對于允許或不允許會話模式(由單調(diào)用和單例式服務(wù)使用),每個 Windows Communication Foundation 調(diào)用將位于單獨(dú)的 MSMQ 消息中。如同各 Windows Communication Foundation 服務(wù)一樣,客戶端會與代理進(jìn)行交互,如圖1 所示。由于已將代理配置為使用 MSMQ 綁定,因而該代理不會向任何特定服務(wù)發(fā)送 Windows Communication Foundation 消息,而是將調(diào)用轉(zhuǎn)換為 MSMQ 消息,然后將這些消息發(fā)布到端點(diǎn)地址所指定的隊列中。圖 1WCF 排隊調(diào)用體系結(jié)構(gòu) (單擊該圖像獲得較小視圖) 圖 1WCF 排隊調(diào)用體系結(jié)構(gòu) (單擊該圖像獲得較大視圖) 在服務(wù)端,當(dāng)具有排隊端點(diǎn)的服務(wù)主機(jī)啟動后,主機(jī)會安裝隊列偵聽程序。隊列偵聽程序會檢測到隊列中的消息并使其出隊,然后創(chuàng)建主機(jī)端以調(diào)度程序為終點(diǎn)的偵聽器鏈。調(diào)度程序會照例調(diào)用服務(wù)實例。如果客戶端向隊列發(fā)布了多個消息,偵聽程序會隨著消息的出隊創(chuàng)建新的實例,最終以異步、非連接的并發(fā)調(diào)用結(jié)束。如果主機(jī)處于離線狀態(tài),消息將在隊列中保持待處理狀態(tài)。待下次主機(jī)上線時,消息會被轉(zhuǎn)發(fā)給服務(wù)。面向隊列進(jìn)行的、可能處于非連接狀態(tài)的調(diào)用不可能返回任何值,因為在將消息調(diào)度到隊列時并未調(diào)用任何服務(wù)邏輯。此外,調(diào)用可能會在客戶端應(yīng)用程序停止運(yùn)行后被調(diào)度給服務(wù)進(jìn)行處理,而這時客戶端根本無法處理返回的值。同樣,調(diào)用也無法將任何服務(wù)端異常返回給客戶端,而且也沒有客戶端用來捕獲和處理異常。由于客戶端不會因為調(diào)用操作而被封鎖,更確切地說,客戶端只有在將消息送去排隊的片刻才才被封鎖,因而從客戶端的角度來看,排隊調(diào)用在本質(zhì)上屬于異步調(diào)用。這些是單向調(diào)用的典型特征。因此,由使用 NetMsmqBinding 的端點(diǎn)所提供的任何合約都只能具有單向操作。Windows Communication Foundation 會在加載服務(wù)和代理時對此進(jìn)行驗證: /只能對排隊合約執(zhí)行單向調(diào)用ServiceContractinterface IMyContract OperationContract(IsOneWay = true) void MyMethod();由于與 MSMQ 的交互封裝在綁定中,因而在服務(wù)調(diào)用代碼或客戶端調(diào)用代碼中沒有任何與調(diào)用排隊相關(guān)的內(nèi)容。服務(wù)代碼和客戶端代碼看起來與任何其他 Windows Communication Foundation 客戶端代碼和服務(wù)代碼都是一樣的,如圖2 所示。針對排隊服務(wù)定義端點(diǎn)時,端點(diǎn)地址中必須包含隊列名稱和隊列類型(公有或私有): 最后,MSMQ 是 Windows Communication Foundation 的事務(wù)性資源管理器。如果隊列是事務(wù)性的,則當(dāng)客戶端的事務(wù)中止時,客戶端所發(fā)布的消息將會回滾。在服務(wù)端,從隊列中讀取消息時會啟動新的事務(wù)。如果服務(wù)參與并中止該事務(wù)(可能因異常而導(dǎo)致),消息會回滾到隊列中等待下一次重試。Windows Communication Foundation 提供了完善的故障檢測和病毒消息處理支持功能,本專欄對此不作介紹。響應(yīng)服務(wù)到目前為止,我們所介紹的排隊調(diào)用的編程模型是單側(cè)的:客戶端向隊列發(fā)布單向消息,再由服務(wù)處理該消息。如果排隊的操作真的就是單向調(diào)用,那么這種模型足以滿足要求。然而,排隊服務(wù)有時候需要反過來向其客戶端報告調(diào)用的結(jié)果、返回的結(jié)果,甚至是錯誤。但在默認(rèn)情況下,這是無法實現(xiàn)的。Windows Communication Foundation 將排隊調(diào)用與單向調(diào)用等同起來,而單向調(diào)用在本質(zhì)上是禁止任何此類響應(yīng)的。此外,排隊服務(wù)(及其客戶端)可能未處于連接狀態(tài)。如果客戶端發(fā)布對未連接服務(wù)的排隊調(diào)用,則當(dāng)服務(wù)最終獲得并處理這些消息時,可能不會有客戶端來接收值,因為客戶端可能早已離線了。這一問題的解決方案是讓服務(wù)將報告返回給客戶端所提供的排隊服務(wù)。我將此類服務(wù)稱作響應(yīng)服務(wù)。圖3 顯示了此類解決方案的體系結(jié)構(gòu)。圖 3響應(yīng)服務(wù) (單擊該圖像獲得較小視圖) 圖 3響應(yīng)服務(wù) (單擊該圖像獲得較大視圖) 響應(yīng)服務(wù)就是系統(tǒng)中的另一個排隊服務(wù)。它同樣可能與客戶端斷開連接并由單獨(dú)的進(jìn)程或單獨(dú)的計算機(jī)進(jìn)行托管,或者它也可能共享客戶端的進(jìn)程。如果響應(yīng)服務(wù)共享客戶端的進(jìn)程,則當(dāng)客戶端啟動時,響應(yīng)服務(wù)即開始處理排隊的響應(yīng)。將響應(yīng)服務(wù)由獨(dú)立于客戶端的進(jìn)程(甚至是計算機(jī))托管有助于進(jìn)一步將響應(yīng)服務(wù)的生存期與使用該響應(yīng)服務(wù)的客戶端相分離。設(shè)計響應(yīng)服務(wù)合約就如使用任何 Windows Communication Foundation 服務(wù)一樣,客戶端和服務(wù)需要預(yù)先商定響應(yīng)合約及其適用對象(例如返回的值和錯誤信息,或僅僅是返回的值)。請注意,您也可將響應(yīng)服務(wù)拆分為兩個服務(wù),一個用于響應(yīng)結(jié)果,另一個用于響應(yīng)錯誤。例如,假定有如下由排隊 MyCalculator 服務(wù)所實現(xiàn)的 ICalculator 合約: ServiceContract interface ICalculator OperationContract(IsOneWay = true) void Add(int number1,int number2); . /更多操作 ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall) class MyCalculator : ICalculator .要求 MyCalculator 服務(wù)以計算結(jié)果來響應(yīng)客戶端并報告所有錯誤。計算結(jié)果為整數(shù)形式,錯誤以 Windows Communication Foundation ExceptionDetail 數(shù)據(jù)合約形式表示。對于響應(yīng)服務(wù),可按下列方式定義 ICalculatorResponse 合約: ServiceContractinterface ICalculatorResponse OperationContract(IsOneWay = true) void OnAddCompleted(int result,ExceptionDetail error);支持 ICalculatorResponse 的響應(yīng)服務(wù)需要檢查返回的錯誤信息,在方法結(jié)束時通知客戶端應(yīng)用程序、用戶或應(yīng)用程序管理員,并將結(jié)果提供給相關(guān)方。下面是一個支持 IcalculatorResponse 的簡單響應(yīng)服務(wù): ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall)class MyCalculatorResponse : ICalculatorResponse OperationBehavior(TransactionScopeRequired = true) public void OnAddCompleted(int result,ExceptionDetail error) MessageBox.Show(結(jié)果 = + result,MyCalculatorResponse); if(error != null) /處理錯誤 實現(xiàn) MyCalculator 和 MyCalculatorResponse 會直接引出兩個問題。第一個問題是同一響應(yīng)服務(wù)可能會被用于處理多個排隊服務(wù)上多個調(diào)用的響應(yīng)(或完成),而 MyCalculatorResponse(更重要的是其所服務(wù)的客戶端)無法區(qū)分這些響應(yīng)。這一問題的解決方案是讓發(fā)出原始排隊調(diào)用的客戶端向該調(diào)用分配某個唯一的 ID 作為標(biāo)記。排隊服務(wù) MyCalculator 需要將該 ID 傳遞給 MyCalculatorResponse,使其能夠應(yīng)用與該 ID 相關(guān)的某種自定義邏輯。第二個問題是排隊服務(wù)如何發(fā)現(xiàn)響應(yīng)服務(wù)的地址。與雙向回調(diào)不同的是,Windows Communication Foundation 內(nèi)部并不支持將響應(yīng)服務(wù)引用傳遞給服務(wù)。而將該地址放入服務(wù)主機(jī)配置文件中(客戶端一節(jié)中)并不是明智之舉,因為同一排隊服務(wù)可能會被多個客戶端調(diào)用,而每個客戶端都有其自身專用的響應(yīng)服務(wù)和地址。一種可能的解決方案是將客戶端所管理的 ID 和所需的響應(yīng)服務(wù)地址作為參數(shù)基于排隊服務(wù)合約明確傳遞給每個操作: ServiceContractinterface ICalculator OperationContract(IsOneWay = true) void Add(int number1,int number2, string responseAddress,string methodID);同樣,排隊服務(wù)也可以將響應(yīng)服務(wù)的方法 ID 作為參數(shù)基于排隊響應(yīng)合約明確傳遞給每個操作: ServiceContractinterface ICalculatorResponse OperationContract(IsOneWay = true) void OnAddCompleted(int result,ExceptionDetail error, string methodID);使用消息頭盡管將地址和 ID 作為顯式參數(shù)進(jìn)行傳遞能夠解決以上問題,但這種做法會改變原始的合約,并且在業(yè)務(wù)級參數(shù)的基礎(chǔ)上又在同一操作中引入了管道級參數(shù)。因而更好的解決方案是讓客戶端將響應(yīng)地址和操作 ID 存儲在調(diào)用的傳出消息頭中。這種方式是在將帶外信息(只有通過此方式才會出現(xiàn)在服務(wù)合約中的信息)傳遞給服務(wù)時通常所采用的技術(shù)。操作上下文會提供傳入和傳出標(biāo)頭的集合,這些集合可以通過 IncomingMessageHeaders 和 OutgoingMessageHeaders 屬性來獲?。?public sealed class OperationContext : . public MessageHeaders IncomingMessageHeaders get; public MessageHeaders OutgoingMessageHeaders get; . /更多成員各集合均為 MessageHeaders 類型,代表 MessageHeader 對象的集合: public sealed class MessageHeaders : IEnumerable, . public void Add(MessageHeader header); public T GetHeader(int index); public T GetHeader(string name,string ns); . /更多成員不能使用 MessageHeader 類來與應(yīng)用程序開發(fā)人員直接進(jìn)行交互,而應(yīng)使用可提供類型安全性并輕松將公共語言運(yùn)行時 (CLR) 類型轉(zhuǎn)換為消息頭的 MessageHeader 類: public abstract class MessageHeader : .public class MessageHeader public MessageHeader(); public MessageHeader(T content); public T Content get;set; public MessageHeader GetUntypedHeader(string name,string ns); . /更多成員對于 MessageHeader 的類型參數(shù),可以使用任何可序列化類型或數(shù)據(jù)合約類型??梢試@ CLR 類型構(gòu)造 MessageHeader,然后使用 GetUntypedHeader 方法將其轉(zhuǎn)換為 MessageHeader 并存儲在傳出消息頭中。GetUntypedHeader 要求您提供通用類型參數(shù)名稱和命名空間。名稱和命名空間將用于從標(biāo)頭集合中查找標(biāo)頭。查找操作可通過 MessageHeaders 的 GetHeader 方法來執(zhí)行。調(diào)用 GetHeader 可獲取所用 MessageHeader 類型參數(shù)的值。ResponseContext 類由于客戶端需要在消息頭中傳遞地址和方法 ID,因而僅僅一個基元類型參數(shù)并不能滿足要求,而應(yīng)使用 ResponseContext 類(如圖4 所示,其中省略了一些錯誤處理代碼)。ResponseContext 提供了一個位置來存儲響應(yīng)地址和 ID。此外,如果客戶端要使用單獨(dú)的錯誤響應(yīng)服務(wù),ResponseContext 還提供了一個字段用來存儲錯誤響應(yīng)服務(wù)地址。(圖4 并未體現(xiàn)出這一點(diǎn),完整代碼請參閱本期的下載內(nèi)容。)客戶端負(fù)責(zé)構(gòu)造具有唯一 ID 的 ResponseContext 實例。盡管客戶端可以將此 ID 作為構(gòu)造參數(shù)來提供,但它也可以使用 ResponseContext 的構(gòu)造函數(shù)(僅接受響應(yīng)地址參數(shù)),并讓該構(gòu)造函數(shù)為此 ID 生成 GUID。ResponseContext 的重要屬性是靜態(tài) Current 屬性。它完全封裝了與消息頭的交互。通過訪問 Current 屬性,您便獲得了一個理想的編程模型:get 訪問器從傳入消息頭中讀取 ResponseContext 實例,而 set 訪問器將 ResponseContext 實例存儲在傳出標(biāo)頭中??蛻舳司幊炭蛻舳丝赏ㄟ^對每次調(diào)用使用不同的 ResponseContext 實例來為每個方法調(diào)用提供一個 ID??蛻舳诵枰獙?ResponseContext 實例存儲在傳出消息頭中。此外,客戶端還必須在新的操作上下文中執(zhí)行該操作,而不能使用其現(xiàn)有操作上下文。這一要求對于服務(wù)和非服務(wù)客戶端都同樣適用。Windows Communication Foundation 使客戶端能夠通過如下定義的 OperationContextScope 類來對當(dāng)前線程采用新的操作上下文: public sealed class OperationContextScope : IDisposable public OperationContextScope(IContextChannel channel); public OperationContextScope(OperationContext context); public void Dispose();OperationContextScope 是在現(xiàn)有上下文不適宜的情況下轉(zhuǎn)換新上下文的常規(guī)技術(shù)。OperationContextScope 的構(gòu)造函數(shù)用新上下文替換當(dāng)前線程的操作上下文。在 OperationContextScope 實例上調(diào)用 Dispose 即可恢復(fù)原來的上下文(即使它為空)。如果不調(diào)用 Dispose,可能會破壞同一線程中需要使用先前上下文的其他對象。因此,OperationContextScope 專用于 using 語句中,僅僅為某一代碼作用域提供新的操作上下文(即使遇到異常)。當(dāng)構(gòu)造新的 OperationContextScope 實例時,應(yīng)向其構(gòu)造函數(shù)提供調(diào)用所用代理的內(nèi)部通道??蛻舳诵枰獎?chuàng)建新的 OperationContextScope,并在作用域內(nèi)分配給 ResponseContext.Current。具體步驟如圖5 所示。為使客戶端的工作簡單化、自動化,需要使用封裝圖5 所示響應(yīng)服務(wù)設(shè)置步驟的代理基類。與雙向回調(diào)不同的是,Windows Communication Foundation 并不提供這樣的代理類,因此必須手動創(chuàng)建一個代理類。為簡化該任務(wù),我編寫了如圖6 所定義的 ResponseClientBase。要使用 ResponseClientBase,應(yīng)從其中派生一個具體類,并為類型參數(shù)提供排隊合約類型。與普通代理的處理方式不同的是,不要再從合約派生子類,而應(yīng)提供一組類似的方法,這些方法都將返回方法 ID 的字符串,這些字符串不能為 void。(這就是您不能從合約派生子類的原因,因為基于合約的操作不會返回任何內(nèi)容,所有操作都是單向的)。例如,圖7 顯示了使用此排隊服務(wù)合約的、可識別響應(yīng)服務(wù)的對應(yīng)代理過程: ServiceContractinterface ICalculator OperationContract(IsOneWay = true) void Add(int number1,int number2); . /更多操作使用 ResponseClientBase 后,圖5 所示的代碼將簡化為: string responseAddress = net.msmq:/localhost/private/MyCalculatorResponseQueue;CalculatorClient proxy = new CalculatorClient(responseAddress);string methodId = proxy.Add(2,3);proxy.Close(); ResponseClientBase 的虛擬方法 GenerateMethodId 使用方法 ID 的 GUID。ResponseClientBase 的子類可將其覆蓋,并提供任何其他唯一的字符串,如遞增的整數(shù)。ResponseClientBase 的構(gòu)造函數(shù)接受響應(yīng)地址和常規(guī)代理參數(shù),如端點(diǎn)名稱、地址和綁定。構(gòu)造函數(shù)將響應(yīng)地址存儲在只讀的公共字段中。ResponseClientBase 從常規(guī) ClientBase 中派生而來,因此所有構(gòu)造函數(shù)將委托給其各自的基本構(gòu)造函數(shù)。ResponseClientBase 的核心是 Enqueue 方法。Enqueue 將接受要調(diào)用(實際上排列消息)的操作名稱和操作參數(shù),創(chuàng)建新的操作上下文作用域,生成新的方法 ID,并將該 ID 和響應(yīng)地址存儲在 ResponseContext 中。它通過設(shè)置 ResponseContext.Current 將 ResponseContext 分配給傳出消息頭,然后使用反射來調(diào)用所提供的操作名稱。由于使用了反射和后期綁定,因而 ResponseClientBase 不支持合約層次結(jié)構(gòu)或重載操作。對于這些情況,需要進(jìn)行手動編碼,如圖5 所示。服務(wù)端編程排隊服務(wù)通過 ResponseContext.Current 訪問其傳入消息頭,并從中讀取響應(yīng)地址和方法 ID。服務(wù)需要使用該地址來構(gòu)造響應(yīng)服務(wù)的代理,需要將該 ID 提供給響應(yīng)服務(wù)。服務(wù)通常并不會直接使用 ID。服務(wù)可以使用與客戶端相同的技術(shù)來將方法 ID 傳遞給響應(yīng)服務(wù):使用傳出消息頭將 ID 向外傳遞給響應(yīng)服務(wù),而不使用顯式參數(shù)。與客戶端一樣,服務(wù)也必須通過 OperationContextScope 來采用新的操作上下文,以便能夠修改傳出標(biāo)頭集合。服務(wù)可以編程方式構(gòu)造響應(yīng)服務(wù)的代理,并將響應(yīng)地址和 NetMsmqBinding 實例提供給該代理。服務(wù)甚至還可以從配置文件中讀取綁定設(shè)置。具體步驟如圖8 所示。服務(wù)會捕獲由業(yè)務(wù)邏輯操作所引發(fā)的所有異常,并使用 ExceptionDetail 對象將各個異常進(jìn)行包裝。服務(wù)不會再次引發(fā)異常,因為異常會中止用于將響應(yīng)消息排入響應(yīng)服務(wù)隊列中的事務(wù),所以再次引發(fā)異常會取消響應(yīng)。在 finally 語句中,無論是否出現(xiàn)異常,服務(wù)都會作出響應(yīng)。它使用來自響應(yīng)上下文的地址和 NetMsmqBinding 的新實例來構(gòu)造響應(yīng)服務(wù)的代理。服務(wù)使用代理的內(nèi)部通道來播種新的 OperationContextScope。在新的作用域中,服務(wù)會將通過設(shè)置 Response.Current 而收到的相同響應(yīng)上下文添加到新環(huán)境的傳出標(biāo)頭中,然后調(diào)用響應(yīng)服務(wù)代理,實際上就是使響應(yīng)排入隊列中。之后,服務(wù)會釋放上下文作用域并關(guān)閉代理。為使服務(wù)從標(biāo)頭中提取響應(yīng)參數(shù)時的工作簡單化、自動化,我創(chuàng)建了 ResponseScope 類: public class ResponseScope : IDisposable where T : class public readonly T Response; public ResponseScope(); public ResponseScope(string bindingConfiguration); public ResponseScope(NetMsmqBinding binding); public void Dispose();ResponseScope 是可釋放的對象,它會安裝新的操作上下文,當(dāng)它被釋放后,作用域?qū)⒒謴?fù)為原操作上下文。為實現(xiàn)自動化(即使遇到異常),應(yīng)在 using 語句中使用 ResponseScope。ResponseScope 接受代表響應(yīng)合約的類型參數(shù),并為 Response 提供相同類型的只讀公共字段。Response 是響應(yīng)服務(wù)的代理,ResponseScope 的客戶端使用 Response 來調(diào)用響應(yīng)服務(wù)上的

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論