使用微軟訊息佇列實現C#處理序間通訊
顧名思義,微軟訊息佇列(MSMQ)是一種給隊列發送訊息以便稍後進行處理的方法。訊息由一個“Producer”(生產者)應用程式發送出去,再由一個“Consumer”(消費者)應用程式返回。
這兩個應用程式可以在同一台機器上,在整個網路中,或甚至是位於並不總是串連在一起的不同機器上。MSMQ具有故障保險特性,因為如果第一次傳送失敗,它會重新發送訊息。這樣可保證你的應用程式訊息到達它們的目的地。
訪問MSMQ
通過.NET訪問隊列由System.Messaging.MessageQueue對象完成。列表A說明了如何在一台名為“SRV-MESSAGING”的電腦上訪問TechRepublic隊列。
列表A
MessageQueue queue =
new MessageQueue("SRV-MESSAGINGTechRepublic");
現在我們有了一個MessageQueue對象,這個對象為你提供與隊列互動需要的所有功能。
如果隊列不存在,你可以調用MessageQueue對象的靜態Create方法編程建立隊列。列表B中的代碼說明如何檢查隊列是否存在,建立隊列或給隊列添加一個參考。
列表B
MessageQueue queue = null;
string queueName = "SRV-MESSAGINGTechRepublic";
if (MessageQueue.Exists(queueName))
queue = newMessageQueue(queueName);
else
queue = MessageQueue.Create(queueName, false);
改寫隊列
改寫隊列時,用到MessageQueue.Send方法。列表C舉例說明如何向TechRepublic隊列發送一條訊息。
列表C
queue.Send("My message body", "Message Label");
在這個例子中,我們給TechRepublic隊列發送一條本文為“My message body”的訊息,並對這個訊息應用了一個“Message Label”標籤。訊息標籤允許你不需閱讀訊息本文就可以分割訊息。如果從電腦管理主控台中查看隊列,還可在“隊列訊息”部分看到這些標籤。
讀取隊列
可以使用幾種方法從隊列中讀取訊息。最常見的情況是從隊列中取出所有訊息,然後一次性處理它們。這時要調用MessageQueue.GetAllMessages方法。列表D舉例說明如何應用這個方法。
列表D
System.Messaging.Message[] messages = queue.GetAllMessages();
foreach (System.Messaging.Message message in messages)
{
//Do something with the message.
}
你也可以用GetMessageEnumerator2方法代替上面的MessageQueue.GetAllMessages方法。雖然這兩個方 法的用法類似,但GetMessageEnumerator2隻能向前(forward-only)。對於非常龐大的隊列,則應用使用這個方法,而不是 MessageQueue.GetAllMessages方法。
這是因為GetAllMessages方法領取所有訊息,把它們儲存在當地記憶體中;而GetMessageEnumerator2方法只領取當前訊息在本地儲存,在調用MoveNext時才領取下一條訊息。列表E舉例說明了GetMessageEnumerator2方法的用法。這段代碼檢查隊列中的每一條訊息,再刪除它。
列表E
MessageEnumerator enumerator = queue.GetMessageEnumerator2();
while (enumerator.MoveNext())
enumerator.RemoveCurrent();
在使用GetMessageEnumerator2方法時,還要考慮另外一個問題,即你要訪問隊列中增加的任何新訊息,即使它們是在你調用GetMessageEnumerator2後再增加的。這假定新訊息被添加到隊列末尾。
如果你只希望返回隊列中的第一條訊息,你應該使用MessageQueue.Receive方法。這個方法會領取隊列中的第一條訊息,在這個過程中將它從隊列中刪除。由於訊息在讀取的時候被刪除,你可以確保你的進程是唯一收到訊息的進程。Receive方法的應用執行個體如列表F所示。
列表F
System.Messaging.Message message = queue.Receive();
可以用Peek方法代替Receive方法。Peek方法像Receive方法一樣領取隊列中的第一條訊息;但是,它在隊列中保留訊息備份。這允許你從隊列中刪除訊息之前檢查訊息內容。Peek的文法與Receive類似。
列表G
System.Messaging.Message message = queue.Peek();
發送/接收序列化對象
雖然給隊列發送文本的功能非常有用,但隊列還允許你發送可序列化對象。這意味著你可以建立一個自訂的.NET類,執行個體化它的一個執行個體,將其發送給 隊列以便其它應用程式使用。要完成這個過程,首先得使用XML Serializer序列化被發送的對象,然後對序列化對象放到訊息的本文中。
例如,假設我們希望給TechRepublic訊息佇列發送以下對象(列表H):
列表H
[Serializable()]
publicclassMessageContent
{
privateDateTime _creationDate = DateTime.Now;
privatestring _messageText;
public MessageContent()
{
}
public MessageContent(string messageText)
{
_messageText = messageText;
}
publicstring MessageText
{
get { return _messageText; }
set { _messageText = value; }
}
publicDateTime CreationDate
{
get { return _creationDate; }
set { _creationDate = value; }
}
}
給隊列發送這個對象的一個執行個體只需簡單調用MessageQueue.Send方法,並把一個對象執行個體作為參數提交給這個方法。列表I說明了這種情況。
列表I
MessageContent message = newMessageContent("Hello world!");
queue.Send(message, "Sample Message");
如你所見,上面的代碼類似於我們前面發送本文為一個字串的訊息時使用的代碼。接收一個包含序列化對象的訊息更加困難一些。我們需要告訴訊息它包含哪種對象。
為向訊息指出它包含哪種對象,我們必須建立訊息的格式化器(formatter)。給訊息的Formatter屬性指定一個 System.Messaging.XmlMessageFormatter對象即可建立格式化器。由於我們的訊息包含一個MessageContent 對象,我們希望為它配置XmlMessageFormatter。
列表J
message.Formatter =
new System.Messaging.XmlMessageFormatter(
newType[1] { typeof(MessageContent) }
);
既然我們已經給訊息指定了一個格式化器,我們可以從訊息中提取MessageContent對象。但在這之前,我們需要把message.Body屬性的傳回值分配給一個MessageContent對象。
列表K
MessageContent content = (MessageContent)message.Body;
在這個例子中,“content”變數是我們向隊列發送的原始MessageContent對象的序列化版本,我們可以訪問原始對象的所有屬性和值。
設定訊息優先順序別
在正常情況下,隊列中的訊息以先進先出的形式被訪問。這表示如何你先發送訊息A,再發送訊息B,那麼隊列將首先返回訊息A,然後才是訊息B。在多數 情況下,這樣處理沒有問題。但是,有時,由於一條訊息比其它訊息更加重要,你希望將它提到隊列前面。要實現這種功能,你就需要設定訊息優先順序別。
一條訊息的優先順序別由它的Message.Priority屬性值決定。下面是這個屬性的所有有效值(全部來自MessagePriority的列舉類型):
- 最高(Highest)
- 非常高(VeryHigh)
- 高(High)
- 高於正常層級(AboveNormal)
- 正常(Normal)
- 低(Low)
- 非常低(VeryLow)
- 最低(Lowest)
訊息在隊列中的位置由它的優先順序別決定——例如,假如隊列中有四條訊息,兩條訊息的優先順序別為“正常”(Normal),另兩條為“高”(High)。則隊列中訊息排列如下:
- High Priority A——這是發送給隊列的第一條“高”優先順序訊息。
- High Priority B——這是發送給隊列的第二條“高”優先順序訊息。
- Normal Priority A——這是發送隊列的第一條“正常”優先順序訊息。
- Normal Priority B——這是發送隊列的第二條“正常”優先順序訊息。
根據這個順序,如果我們給隊列發送另一條“最高”優先順序的訊息,它將位於隊列的頂部。
如果需要使用訊息優先順序功能,你必須修改發送訊息的代碼。因為Message對象的構造器沒有指定訊息優先順序別的功能,你必須執行個體化一個Message對象,並在將它發送給隊列之前給它設定相應的屬性。列表L中的代碼說明如何設定優先順序別,並給隊列發送一條“最高”優先順序別的訊息。
列表L
//Instantiate the queue
MessageQueue queue = newMessageQueue(queueName);
//Create a XmlSerializer for the object type we're sending.
XmlSerializer serializer = new
XmlSerializer(typeof(MessageContent));
//Instantiate a new message.
System.Messaging.Message queueMessage =
new System.Messaging.Message();
//Set the priority to Highest.
queueMessage.Priority = MessagePriority.Highest;
//Create our MessageContent object.
MessageContent messageContent =
newMessageContent("Hello world - IMPORTANT!");
//Serialize the MessageContent object into the queueMessage.
serializer.Serialize(queueMessage.BodyStream, messageContent);
//Send the message.
queue.Send(queueMessage, "HIGH PRIORITY");
這段代碼和上面代碼的最明顯區別在於它使用了XmlFormatter。它實際是可選的,列表L中的代碼也可用列表M中的代碼代替。
列表M
//Instantiate a new message.
System.Messaging.Message queueMessage =
new System.Messaging.Message();
//Set the priority to Highest.
queueMessage.Priority = MessagePriority.Highest;
//Create our MessageContent object.
MessageContent messageContent =
newMessageContent("Hello world - IMPORTANT!");
//Set the body as the messageContent object.
queueMessage.Body = messageContent;
//Send the message.
queue.Send(queueMessage, "HIGH PRIORITY");
這段代碼執行和列表L中的代碼相同的任務,但代碼更少。
應用
輸入消費者請求是MSMQ功能的一個簡單一實例。消費者提出一個請求,由一個面向消費者的應用程式將它送交給訊息佇列。向隊列發送請求後,它會向消費者送出一個確認(acknowledgement)。
然後,一個獨立的進程從隊列中提取訊息,並運行任何所需的商務邏輯(business logic)。完成商務邏輯後,處理系統將向另一個隊列提交一個響應。接下來,面向消費者的應用程式從隊列中提取這個響應,並給消費者返回一個響應。
這種類型的配置能夠加快面向消費者的應用程式的速度,使其迅速做出反應,同時在一個內部系統中完成大量處理工作。這樣還可以將請求處理分散到多台內部機器上,提供可擴充性。