# 跟我一起學WCF(1)——MSMQ消息隊列
## 一、引言
Windows Communication Foundation(WCF)是Microsoft為構建面向服務的應用程序而提供的統一編程模型,該服務模型提供了支持松散耦合和版本管理的序列化功能,并提供了與消息隊列(MSMQ)、COM+、Asp.net Web服務、.NET Remoting等微軟現有的分布式系統技術。利用WCF平臺,開發人員可以很方便地構建面向服務的應用程序(SOA)。可以認為,WCF是對之前現有的分布式技術(指的是MSMQ、.NET Remoting和Web 服務等技術)的集成和擴展,既然這樣,我們就有必要首先了解下之前分布式技術,只有這樣才能更深刻地明白WCF所帶來的好處。今天就分享下MSMQ這種分布式技術。
## 二、MSMQ的介紹
MSMQ全稱是Microsoft Message Queue——微軟消息隊列。它是一種異步傳輸模式,可以在不同的應用之間實現相互通信,相互通信的應用可以分布在同一臺機器上,也可以分布于相連的網絡空間中的任一位置。
## 2.1 MSMQ 工作原理
MSMQ的實現原理是:消息的發送者把自己想要發送的信息放入一個容器,然后把它保存到一個系統公用空間的消息隊列中,本地或異地的消息接收程序再從該隊列中取出發給它的消息進行處理。
消息隊列是一個公用存儲空間,它可以存在于內存中或物理文件中,因此,消息以兩種方式發送,即快遞方式和可恢復模式。它們的區別是消息存儲位置的不同,快遞方式,為了消息的快速傳遞,所以把消息放置在內存中,而不放在物理磁盤上,以獲得較高的處理能力;而可恢復模式在傳送過程的每一步驟中,都把消息寫入物理磁盤上,這樣當保存消息隊列的機器發生故障而重新啟動后,可以把發送的消息恢復到故障發送之前的狀態,以獲得更好的消息恢復能力。消息隊列可以放在發送方、接收方所在的機器上,也可以單獨放置在另外一臺機器上。另外,采用消息隊列機制,發送方不必要擔心接收方是否啟動,是否發生故障等因素,只要消息成功發送出去,就可以認為處理完成,而實際上對方可能甚至未開機,或者實際消息傳遞到對方可能在第二天。MSMQ機制類似QQ消息傳遞機制。下圖演示了MSMQ的實現原理。

MSMQ中主要有兩個概念。
* 一個是消息Message:Message是通信雙方需要傳遞的消息,它可以是文本、圖片、視頻等。消息包含發送和接收者的標識,只有指定的用戶才能取得消息。
* 一個是隊列Queue:用來保存消息的存儲空間,MSMQ中主要包括以下幾種隊列類型:
* 公共隊列:在整個消息隊列網絡中復制,有可能由網絡連接的所有站點訪問。路徑格式為:機器名稱\隊列名稱
* 專用隊列(或叫私有隊列):不在整個網絡中發布,它們僅在所駐留的本地計算機上可用,專用隊列只能由知道隊列的完整路徑名稱或標簽的應用程序訪問。路徑格式為:機器名稱\Private$\隊列名稱
* 日志隊列:包含確認在給定“消息隊列中發送的消息回執消息”。路徑格式為:機器名稱\隊列名稱\Journal$
* 機器日志隊列對應的格式為:機器名稱\Journal$;
* 機器死信隊列對應的格式為:機器名稱\Deadletter$;
* 機器信道死信隊列對應的格式為:機器名稱\XactDeadletter$。
## 2.2 隊列引用說明
當創建了一個MessageQueue實例之后,就應指明和哪個隊列進行通信,在.NET中有3種訪問指定消息隊列的方法:
* 使用路徑,消息隊列的路徑被機器名和隊列名唯一確定,所以可以用消息隊列路徑來指明使用的消息隊列。
* 使用格式名(format name),它是由MSMQ在消息隊列創建時生成的唯一標識,個使命不由用戶指定,而是由隊列管理者自動生成的GUID。
* 使用標識名(label),它是消息隊列創建時由消息管理者指定的帶有意義的名字。
## 三、消息隊列的優缺點
采用消息隊列的好處是:由于是異步通信,無論是發送方還是接收方都不同等待對方返回成功消息,就可以執行余下的代碼,大大提高了處理的能力;在信息傳遞過程中,具有故障恢復能力;MSMQ的消息傳遞機制使得通信的雙方具有不同的物理平臺成為可能。
消息隊列缺點是:不適合Client需要Server端實時交互情況,大量請求時候,響應可能延遲。對于客戶端,必須是Windows系統。可以通過連接器跟其他的非微軟技術集成。
## 四、利用MSMQ開發分布式應用
## 4.1 環境準備
要想在.NET平臺進行MSMQ的開發,需要安裝消息隊列,你需要打開控制面板->程序->打開或關閉Windows功能,勾選消息隊列服務所有選項,具體操作如下圖所示:

勾選完之后點擊確定之后,可以在我的電腦->管理->服務和應用程序->消息隊列 看到下面的圖:

看到上面這個圖代表你已經成功配置了MSMQ的開發環境,下面就可以使用Visual Studio 進行開發。注意,對特定類型隊列的操作代碼,一定要成功安裝對應的隊列類型。
## 4.2 使用MSMQ開發分布式應用
首先,實現服務器端。創建一個控制臺項目,添加System.Messaging引用,因為消息隊列的類全部封裝在System.Messaging.dll程序集里。具體服務端的代碼如下:
```
1 using System;
2 using System.Messaging;
3
4 namespace MSMQServer
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 // 創建一個公共隊列,公共隊列只能創建在域環境里
11 //if (!MessageQueue.Exists(@".\LearningHardMSMQ")) // 判斷此路徑下是否已經有該隊列
12 //{
13 // using (MessageQueue mq = MessageQueue.Create(@".\LearningHardMSMQ"))
14 // {
15 // mq.Label = "LearningHardQueue"; // 設置隊列標簽
16 // Console.WriteLine("已經創建了一個公共隊列");
17 // Console.WriteLine("路徑為:{0}", mq.Path);
18 // Console.WriteLine("隊列名字為:{0}", mq.QueueName);
19 // mq.Send("MSMQ Message", "Leaning Hard"); // 發送消息
20 // }
21 //}
22
23 //if (MessageQueue.Exists(@".\Private$\LearningHardMSMQ"))
24 //{
25 // 刪除消息隊列
26 // MessageQueue.Delete(@".\Private$\LearningHardMSMQ");
27 //}
28 // 創建一個私有消息隊列
29 if (!MessageQueue.Exists(@".\Private$\LearningHardMSMQ"))
30 {
31 using (MessageQueue mq = MessageQueue.Create(@".\Private$\LearningHardMSMQ"))
32 {
33 mq.Label = "LearningHardPrivateQueue";
34 Console.WriteLine("已經創建了一個私有隊列");
35 Console.WriteLine("路徑為:{0}", mq.Path);
36 Console.WriteLine("私有隊列名字為:{0}", mq.QueueName);
37 mq.Send("MSMQ Private Message", "Leaning Hard"); // 發送消息
38 }
39 }
40
41 // 遍歷所有的公共消息隊列
42 //foreach (MessageQueue mq in MessageQueue.GetPublicQueues())
43 //{
44 // mq.Send("Sending MSMQ public message" + DateTime.Now.ToLongDateString(), "Learning Hard");
45 // Console.WriteLine("Public Message is sent to {0}", mq.Path);
46 //}
47
48 if (MessageQueue.Exists(@".\Private$\LearningHardMSMQ"))
49 {
50 // 獲得私有消息隊列
51 MessageQueue mq = new MessageQueue(@".\Private$\LearningHardMSMQ");
52 mq.Send("Sending MSMQ private message" + DateTime.Now.ToLongDateString(), "Leaning Hard");
53 Console.WriteLine("Private Message is sent to {0}", mq.Path);
54 }
55
56 Console.Read();
57 }
58 }
59 }
```
服務器端代碼需要注意的是,公共隊列只能在域環境中創建,由于我的個人電腦沒有加入域環境,所以不能創建公共隊列,從開始的消息隊列的截圖也可以看出,在圖中并沒有安裝公共隊列。
實現完服務器端之后,自然就是完成客戶端。MSMQ程序的原理主要是:服務器端把消息發送到共享的消息隊列中,然后,客戶端從這個共享的消息隊列中取出消息進行處理。具體客戶端的實現代碼如下所示:
```
1 using System;
2 using System.Messaging; // 需要添加System.Messaging引用
3
4 namespace MSMQClient
5 {
6 class Program
7 {
8 static void Main(string[] args)
9 {
10 if (MessageQueue.Exists(@".\Private$\LearningHardMSMQ"))
11 {
12 // 創建消息隊列對象
13 using (MessageQueue mq = new MessageQueue(@".\Private$\LearningHardMSMQ"))
14 {
15 // 設置消息隊列的格式化器
16 mq.Formatter = new XmlMessageFormatter(new string[] { "System.String" });
17 foreach (Message msg in mq.GetAllMessages())
18 {
19 Console.WriteLine("Received Private Message is: {0}", msg.Body);
20 }
21
22 Message firstmsg = mq.Receive(); // 獲得消息隊列中第一條消息
23 Console.WriteLine("Received The first Private Message is: {0}", firstmsg.Body);
24 }
25 }
26 Console.Read();
27 }
28 }
29 }
```
## 4.3 運行演示
經過上面步驟,我們已經完成了MSMQ分布式程序的實現了,下面看看如何運行該程序來查看效果。
首先,自然要啟動服務器,右鍵MSMQServer項目->調試->啟動新實例來啟動服務器,具體步驟如下圖所示:

運行成功之后,你將到服務器發送消息成功的控制臺界面,效果圖如下所示:

接下來運行客戶端來從消息隊列中取得消息并顯示在控制臺中,采用和服務器相同的方式來啟動客戶端,右鍵MSMQClient->調試->啟動新實例,看到客戶端的效果如下圖所示:

從上圖可以看出,客戶端確實成功地取得了消息隊列中的消息。
以上MSMQ程序需要特別注意是:[MessageQueue.Receive()](http://msdn.microsoft.com/en-us/library/system.messaging.messagequeue.receive(v=vs.110).aspx)是取出消息隊列中隊列中的第一條消息,并從消息隊列中移除它(MSDN中文翻譯上是錯誤,MSDN寫的是不移除,而英文原文是移除),而實際結果也是移除的,如果你再運行一次客戶端時,你會發現消息隊列中只有一條消息,具體運行效果如下圖所示:

## 五、總結
到這里,MSMQ的內容就分享結束, 其MSMQ的實現原理也非常簡單,一句話慨括就是服務器把消息放在一個公共的地方,這個地方叫做消息隊列,而其他客戶端可以從這個地方取出消息進行處理。下一章將分享.NET 平臺上另外一種分布式技術——.NET Remoting。
本文的示例代碼文件:[MSMQSample](http://files.cnblogs.com/zhili/MSMQSample.zip)。
- C# 基礎知識系列
- C# 基礎知識系列 專題一:深入解析委托——C#中為什么要引入委托
- C# 基礎知識系列 專題二:委托的本質論
- C# 基礎知識系列 專題三:如何用委托包裝多個方法——委托鏈
- C# 基礎知識系列 專題四:事件揭秘
- C# 基礎知識系列 專題五:當點擊按鈕時觸發Click事件背后發生的事情
- C# 基礎知識系列 專題六:泛型基礎篇——為什么引入泛型
- C# 基礎知識系列 專題七: 泛型深入理解(一)
- C# 基礎知識系列 專題八: 深入理解泛型(二)
- C# 基礎知識系列 專題九: 深入理解泛型可變性
- C#基礎知識系列 專題十:全面解析可空類型
- C# 基礎知識系列 專題十一:匿名方法解析
- C#基礎知識系列 專題十二:迭代器
- C#基礎知識 專題十三:全面解析對象集合初始化器、匿名類型和隱式類型
- C# 基礎知識系列 專題十四:深入理解Lambda表達式
- C# 基礎知識系列 專題十五:全面解析擴展方法
- C# 基礎知識系列 專題十六:Linq介紹
- C#基礎知識系列 專題十七:深入理解動態類型
- 你必須知道的異步編程 C# 5.0 新特性——Async和Await使異步編程更簡單
- 全面解析C#中參數傳遞
- C#基礎知識系列 全面解析C#中靜態與非靜態
- C# 基礎知識系列 C#中易混淆的知識點
- C#進階系列
- C#進階系列 專題一:深入解析深拷貝和淺拷貝
- C#進階系列 專題二:你知道Dictionary查找速度為什么快嗎?
- C# 開發技巧系列
- C# 開發技巧系列 使用C#操作Word和Excel程序
- C# 開發技巧系列 使用C#操作幻燈片
- C# 開發技巧系列 如何動態設置屏幕分辨率
- C# 開發技巧系列 C#如何實現圖片查看器
- C# 開發技巧 如何防止程序多次運行
- C# 開發技巧 實現屬于自己的截圖工具
- C# 開發技巧 如何使不符合要求的元素等于離它最近的一個元素
- C# 線程處理系列
- C# 線程處理系列 專題一:線程基礎
- C# 線程處理系列 專題二:線程池中的工作者線程
- C# 線程處理系列 專題三:線程池中的I/O線程
- C# 線程處理系列 專題四:線程同步
- C# 線程處理系列 專題五:線程同步——事件構造
- C# 線程處理系列 專題六:線程同步——信號量和互斥體
- C# 多線程處理系列專題七——對多線程的補充
- C#網絡編程系列
- C# 網絡編程系列 專題一:網絡協議簡介
- C# 網絡編程系列 專題二:HTTP協議詳解
- C# 網絡編程系列 專題三:自定義Web服務器
- C# 網絡編程系列 專題四:自定義Web瀏覽器
- C# 網絡編程系列 專題五:TCP編程
- C# 網絡編程系列 專題六:UDP編程
- C# 網絡編程系列 專題七:UDP編程補充——UDP廣播程序的實現
- C# 網絡編程系列 專題八:P2P編程
- C# 網絡編程系列 專題九:實現類似QQ的即時通信程序
- C# 網絡編程系列 專題十:實現簡單的郵件收發器
- C# 網絡編程系列 專題十一:實現一個基于FTP協議的程序——文件上傳下載器
- C# 網絡編程系列 專題十二:實現一個簡單的FTP服務器
- C# 互操作性入門系列
- C# 互操作性入門系列(一):C#中互操作性介紹
- C# 互操作性入門系列(二):使用平臺調用調用Win32 函數
- C# 互操作性入門系列(三):平臺調用中的數據封送處理
- C# 互操作性入門系列(四):在C# 中調用COM組件
- CLR
- 談談: String 和StringBuilder區別和選擇
- 談談:程序集加載和反射
- 利用反射獲得委托和事件以及創建委托實例和添加事件處理程序
- 談談:.Net中的序列化和反序列化
- C#設計模式
- UML類圖符號 各種關系說明以及舉例
- C#設計模式(1)——單例模式
- C#設計模式(2)——簡單工廠模式
- C#設計模式(3)——工廠方法模式
- C#設計模式(4)——抽象工廠模式
- C#設計模式(5)——建造者模式(Builder Pattern)
- C#設計模式(6)——原型模式(Prototype Pattern)
- C#設計模式(7)——適配器模式(Adapter Pattern)
- C#設計模式(8)——橋接模式(Bridge Pattern)
- C#設計模式(9)——裝飾者模式(Decorator Pattern)
- C#設計模式(10)——組合模式(Composite Pattern)
- C#設計模式(11)——外觀模式(Facade Pattern)
- C#設計模式(12)——享元模式(Flyweight Pattern)
- C#設計模式(13)——代理模式(Proxy Pattern)
- C#設計模式(14)——模板方法模式(Template Method)
- C#設計模式(15)——命令模式(Command Pattern)
- C#設計模式(16)——迭代器模式(Iterator Pattern)
- C#設計模式(17)——觀察者模式(Observer Pattern)
- C#設計模式(18)——中介者模式(Mediator Pattern)
- C#設計模式(19)——狀態者模式(State Pattern)
- C#設計模式(20)——策略者模式(Stragety Pattern)
- C#設計模式(21)——責任鏈模式
- C#設計模式(22)——訪問者模式(Vistor Pattern)
- C#設計模式(23)——備忘錄模式(Memento Pattern)
- C#設計模式總結
- WPF快速入門系列
- WPF快速入門系列(1)——WPF布局概覽
- WPF快速入門系列(2)——深入解析依賴屬性
- WPF快速入門系列(3)——深入解析WPF事件機制
- WPF快速入門系列(4)——深入解析WPF綁定
- WPF快速入門系列(5)——深入解析WPF命令
- WPF快速入門系列(6)——WPF資源和樣式
- WPF快速入門系列(7)——深入解析WPF模板
- WPF快速入門系列(8)——MVVM快速入門
- WPF快速入門系列(9)——WPF任務管理工具實現
- ASP.NET 開發
- ASP.NET 開發必備知識點(1):如何讓Asp.net網站運行在自定義的Web服務器上
- ASP.NET 開發必備知識點(2):那些年追過的ASP.NET權限管理
- ASP.NET中實現回調
- 跟我一起學WCF
- 跟我一起學WCF(1)——MSMQ消息隊列
- 跟我一起學WCF(2)——利用.NET Remoting技術開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(3)——利用Web Services開發分布式應用
- 跟我一起學WCF(4)——第一個WCF程序
- 跟我一起學WCF(5)——深入解析服務契約 上篇
- 跟我一起學WCF(6)——深入解析服務契約 下篇
- 跟我一起學WCF(7)——WCF數據契約與序列化詳解
- 跟我一起學WCF(8)——WCF中Session、實例管理詳解
- 跟我一起學WCF(9)——WCF回調操作的實現
- 跟我一起學WCF(10)——WCF中事務處理
- 跟我一起學WCF(11)——WCF中隊列服務詳解
- 跟我一起學WCF(12)——WCF中Rest服務入門
- 跟我一起學WCF(13)——WCF系列總結
- .NET領域驅動設計實戰系列
- .NET領域驅動設計實戰系列 專題一:前期準備之EF CodeFirst
- .NET領域驅動設計實戰系列 專題二:結合領域驅動設計的面向服務架構來搭建網上書店
- .NET領域驅動設計實戰系列 專題三:前期準備之規約模式(Specification Pattern)
- .NET領域驅動設計實戰系列 專題四:前期準備之工作單元模式(Unit Of Work)
- .NET領域驅動設計實戰系列 專題五:網上書店規約模式、工作單元模式的引入以及購物車的實現
- .NET領域驅動設計實戰系列 專題六:DDD實踐案例:網上書店訂單功能的實現
- .NET領域驅動設計實戰系列 專題七:DDD實踐案例:引入事件驅動與中間件機制來實現后臺管理功能
- .NET領域驅動設計實戰系列 專題八:DDD案例:網上書店分布式消息隊列和分布式緩存的實現
- .NET領域驅動設計實戰系列 專題九:DDD案例:網上書店AOP和站點地圖的實現
- .NET領域驅動設計實戰系列 專題十:DDD擴展內容:全面剖析CQRS模式實現
- .NET領域驅動設計實戰系列 專題十一:.NET 領域驅動設計實戰系列總結