.NET 使用消息隊列,包括遠程訪問
之前的項目用到了隊列,現在總結一下,下面有非常詳細的DEMO,希望能對有需要的人提供幫助。
我的需求很簡單,就是多個客戶端連接到我的一個小型的數據轉發服務器上,開始使用的是Socket通信實現這個功能,一旦數據服務器接收到來自不同客戶端發來的消息,就對這些消息進行處理(我這裏是將數據接收到後再轉發到另一個服務器上),但考慮到客戶端是每隔一個很短的時間周期向服務器發送信息,並且連接客服端數量比較多的時候,擔心會產生並發訪問的問題,也希望避免 數據轉發服務器 頻繁地從多個不同線程獲取信息而出現其他未知問題,所以在處理客戶端向數據轉發服務器發送信息的時候采取隊列的方式。
一般情況下,使用MSMQ,要先安裝消息服務,這個直接百度就行;
在VS裏添加 Messaging引用,就可以使用MessageQueue這個類了;接下來就要思考清楚你的數據(消息)的流向問題,之前因為自己對隊列的錯誤認識,對到底在哪創建隊列,隊列的消息又由誰去發送和接收沒有弄清除,還有參考的一些寫得不是太清晰地博文,繞了好大一圈,所以今天在這裏以我自己的項目需求為例子,說明 1、如何創建隊列 2、如何向隊列發送消息 3、 如何獲取隊列中的消息
首先、創建隊列:根據我的需求,我要通過Socket通信將信息發送至數據轉發服務器,因此為了避免並發訪問問題的產生,消息隊列應當建立在數據轉發服務器上;
System.Messaging.MessageQueue myQuere = null; /// <summary> /// 這樣就在數據轉發服務器端創建了一個名為queuedemo的消息隊列; /// 從客戶端要發送的消息就保存在這個隊列裏, /// 你可以通過計算機管理->服務和應用下的消息隊列中看到你創建的queuedemo隊列, /// private$關鍵字是說明隊列為專用隊列, /// 如果沒有這個關鍵字還要配置域服務器,還是挺麻煩,這個還是借助百度吧, /// 前面的“.”代表創建的隊列目錄是本機 /// </summary> /// <param name="sender"></param>/// <param name="e"></param> private void btnQueueInit_Click(object sender, EventArgs e) { // 註意這裏是$符號 string queuePath = @".\private$\quereDemo"; // 判斷消息隊列示例是否存在 if (!System.Messaging.MessageQueue.Exists(queuePath)) { // 不存在則創建一個消息隊列 myQuere = System.Messaging.MessageQueue.Create(queuePath); } myQuere = new System.Messaging.MessageQueue(queuePath); }
這樣就在數據轉發服務器端創建了一個名為queuedemo的消息隊列;從客戶端要發送的消息就保存在這個隊列裏,你可以通過計算機管理->服務和應用下的消息隊列中看到你創建的queuedemo隊列,private$關鍵字是說明隊列為專用隊列,如果沒有這個關鍵字還要配置域服務器,還是挺麻煩,這個還是借助百度吧,前面的“.”代表創建的隊列目錄是本機,這個隊列一旦創建成功,就是系統的事了,接下來要做的就是你怎麽去把消息寫進這個隊列,或者讀取隊列的值
這裏要特別註意,不要將queuepath路徑字符串寫成
string queuePath = @"FormatName:Direct=TCP:192.168.1.153\private$\quereDemo";
這樣寫的話是用於遠程計算機對這個隊列進行訪問的,因為MessageQueue的Create()和Exisit()方法是沒辦法去識別上述FormatName格式的,還有要確保Create()函數要被執行了之後再用MessageQueue實例去引用;這樣服務器端隊列的創建就完成了;
在客戶端中,向隊列發送信息;
/// <summary> /// 寫入數據到消息隊列 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnWriteMessage_Click(object sender, EventArgs e) { // 要往隊列裏寫入的消息 // 要求發送的對象要以序列化的方式寫進去,所以要設置formatter,這裏用的是XmlMessageFormatter 還有BinaryMessageFormatter等等 string s = "客戶端往隊列裏發送的消息"; // 實例化一個消息隊列Object System.Messaging.Message message = new System.Messaging.Message(); message.Body = s; // body 為object類型 message.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) }); // 選擇xml的方式進行傳送消息 // 創建輸送消息的隊列對象(在客戶端和消息隊列服務器分離的時候需要創建實例) // System.Messaging.MessageQueue myQuere = new System.Messaging.MessageQueue(@"FormatName:Direct=TCP:192.168.1.153\private$\queuedemo"); myQuere.Send(message); }
在客戶端中,用一個MessageQueue實例指向服務器本機上創建的隊列路徑,這時,MessageQueue實例的構造函數裏的路徑就一定要用FormatName格式,指明是TCP通信還是HTTP還是Machine如我上面代碼所示,然後調用Send()方法,將消息寫進隊列,這個要求發送的對象要以序列化的方式寫進去,所以要設置formatter,這裏用的是XmlMessageFormatter 還有BinaryMessageFormatter等等 註意保存你消息的 消息體Body是Object類型的 因此可以將你寫的任何一個類的對象發送至消息隊列
在服務器中接收消息隊列
/// <summary> /// 開始讀取消息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnReadMessage_Click(object sender, EventArgs e) { // 實例化消息隊列 System.Messaging.MessageQueue msgQuere = new System.Messaging.MessageQueue(@".\private$\quereDemo"); // 指定寫入客戶端的序列化方式 msgQuere.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) }); // 開啟線程讀取 // 此處也可以寫成 Thread thread = new Thread(()=> { }); System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(() => { // 此處使用無限讀取,讀取完了馬上while,也可以用一個時間來間隔 while (true) { //接收 System.Messaging.MessageQueue 引用的隊列中可用的第一條消息。此調用是同步的,在有可用消息前,它將一直阻止當前線程的執行。 System.Messaging.Message msg = msgQuere.Receive(); if (null != msg) { MessageBox.Show(msg.Body.ToString()); } } })); // 啟動線程 thread.IsBackground = true; // 設置為後臺線程 thread.Start(); }
在本機上可以新創建一個隊列實例指向本機的隊列,然後按照之前約定的序列化格式反序列化消息體所以將新的隊列實例的foarmatter屬性賦值為發送時的formatter屬性如代碼所示,這個時候就直接用Receive()得到消息體,然後對消息體裏的信息做處理,我這裏是開啟一個線程顯示隊列的消息,只要有新的消息寫入,我就在消息框中輸出
這個時候可能客戶端無法向遠程服務器成功發送消息,原因基本權限問題 服務器的消息隊列的權限沒有對未驗證的客戶端開放 你要在服務器隊列裏分配對應權限 如果你想讀取隊列的內容 還需要加系統變量
問題解決辦法
1. 服務器端(dos:compmgmt.msc)
- 服務器上消息隊列權限設置:給ANONYMOUS LOGON賦予所有權限;
- 修改服務器的註冊表,允許非驗證客戶端訪問
- 註冊表新增HKLM\Software\Microsoft\MSMQ\Parameters\security\AllowNonauthenticatedRpc項,設置其DWORD值為1
- 註冊表新增HKLM\Software\Microsoft\MSMQ\Parameters\security\NewRemoteReadServerDenyWorkgroupClient項,設置其DWORD值為1
MSMQ的安全訪問控制說明參見:http://msdn.microsoft.com/en-us/library/4108f68e-80f5-40e1-b3df-b713cc4dff79(prot.20).aspx
這樣客戶端就可以讀取服務器裏的隊列信息了 當然一般業務邏輯上不這麽做 因為他只負責發送消息 ,綜上,就是使用消息隊列 跨服務器讀寫的 最基本的用法
.NET 使用消息隊列,包括遠程訪問