佇列工廠之(MSMQ)

神牛003發表於2017-03-12

最近vs2017神器正式版釋出讓人很是激動,vs2017支援了很多語言的開發,從前端-後端-底層的支援,堪稱是工具中的神器;netcore我喜愛的架構之一也得到了大力的宣傳,應群友的邀請將在佇列工廠(msmq,redis,rabbitmq)一些列文章過後,繼續增加.netcore方面的文章,只為.netcore發展更好貢獻一份微弱的力量;本章內容分享的是佇列(msmq,redis,rabbitmq)封裝的佇列工廠之MSMQ希望大家能夠喜歡,也希望各位多多"掃碼支援"和"推薦"謝謝!

 

» 建立佇列工廠QueueReposity<T>

  . 佇列公共操作介面IQueue

  . 配置檔案操作類ConfClass<T>

  . 非安全單例建立佇列例項

» Win7和Server2008安裝MSMQ支援

» MSMQ測試用例(服務端+客戶端)

 

下面一步一個腳印的來分享:

» 建立佇列工廠QueueReposity<T>

首先,因為這裡需要統一封裝幾個常用的佇列方式的用法,因此採用了簡單工廠模式,所以有了QueueReposity<T>

. 佇列公共操作介面IQueue

工廠模式的特性建立例項,因為這裡封裝的都是佇列,故而能提取出統一的規則來,因此定義瞭如下介面(這裡沒有考慮一些佇列相容的非同步方法請忽略):

 1 /// <summary>
 2     /// 佇列公共操作
 3     /// </summary>
 4     public interface IQueue : IDisposable
 5     {
 6         /// <summary>
 7         /// 建立佇列
 8         /// </summary>
 9         void Create();
10 
11         /// <summary>
12         /// 總數
13         /// </summary>
14         /// <returns></returns>
15         int Total();
16 
17         /// <summary>
18         /// 讀取一個佇列
19         /// </summary>
20         /// <returns></returns>
21         Message Read();
22 
23         ///// <summary>
24         ///// 讀取多個佇列
25         ///// </summary>
26         ///// <returns></returns>
27         //List<Message> ReadAll();
28 
29         /// <summary>
30         /// 寫入佇列
31         /// </summary>
32         /// <returns></returns>
33         bool Write(string content, string name = "");
34     }

. 配置檔案操作類ConfClass<T>

因為每個佇列的都有自己的配置資訊,因此封裝了統一管理的配置檔案讀取類ConfClass<T>,來讀取配置在同一個xml檔案中的配置資訊,如下封裝了自定義配置檔案的屬性和讀取方法:

  1 #region 檔案操作類
  2         /// <summary>
  3         /// 配置檔案操作類
  4         /// </summary>
  5         /// <typeparam name="T"></typeparam>
  6         public class ConfClass<T> where T : class,new()
  7         {
  8 
  9             public ConfClass() {
 10 
 11                 var apiNodeName = this.GetType().Name;
 12                 Reader(apiNodeName);
 13             }
 14 
 15             #region 單例模式
 16 
 17             public static readonly object Singleton_Lock = new object();
 18 
 19             /// <summary>
 20             /// 單例物件
 21             /// </summary>
 22             private static T t = default(T);
 23 
 24             /// <summary>
 25             /// 通過方法獲取單例
 26             /// </summary>
 27             /// <param name="t"></param>
 28             /// <returns></returns>
 29             public static T GetInstance(T t)
 30             {
 31                 t = t ?? new T();
 32                 return t;
 33             }
 34 
 35             /// <summary>
 36             /// 通過屬性獲取單例(在繼承的時候使用)
 37             /// </summary>
 38             public static T Current
 39             {
 40                 get
 41                 {
 42                     t = t ?? new T();
 43                     return t;
 44                 }
 45             }
 46 
 47             #endregion
 48 
 49             #region 配置檔案操作
 50 
 51             #region  配置檔案屬性
 52             /// <summary>
 53             /// 配置檔案地址
 54             /// </summary>
 55             //public string ConfPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Conf", "ShenNiuApi.xml");
 56             public string ConfPath = @"C:\Conf\ShenNiuApi.xml";
 57 
 58             /// <summary>
 59             /// 配置檔案父節點名稱
 60             /// </summary>
 61             public string ConfParentNodeName = "ShenNiuApi";
 62 
 63             /// <summary>
 64             /// 配置檔案內容
 65             /// </summary>
 66             public string ConfContent { get; set; }
 67 
 68             /// <summary>
 69             /// 配置檔案文件doc物件
 70             /// </summary>
 71             public XmlDocument doc { get; set; }
 72 
 73 
 74             /// <summary>
 75             /// 賬號
 76             /// </summary>
 77             public string UserName { get; set; }
 78 
 79             /// <summary>
 80             /// 密碼
 81             /// </summary>
 82             public string UserPwd { get; set; }
 83 
 84             /// <summary>
 85             /// 介面地址
 86             /// </summary>
 87             public string ApiUrl { get; set; }
 88 
 89             /// <summary>
 90             /// 祕鑰
 91             /// </summary>
 92             public string ApiKey { get; set; }
 93 
 94             #endregion
 95 
 96             public ConfClass(string ConfPath, string ConfParentNodeName="")
 97             {
 98 
 99                 this.ConfPath = string.IsNullOrWhiteSpace(ConfPath) ? this.ConfPath : ConfPath;
100                 this.ConfParentNodeName = string.IsNullOrWhiteSpace(ConfParentNodeName) ? this.ConfParentNodeName : ConfParentNodeName;
101 
102                 var apiNodeName = this.GetType().Name;
103                 Reader(apiNodeName);
104             }
105 
106             /// <summary>
107             /// 讀取配置資訊
108             /// </summary>
109             /// <param name="apiNodeName"></param>
110             public void Reader(string apiNodeName)
111             {
112                 try
113                 {
114                     if (string.IsNullOrWhiteSpace(ConfPath) || string.IsNullOrWhiteSpace(ConfParentNodeName))
115                     {
116                         throw new Exception("配置檔案地址或者配置檔案父節點名稱不能為空");
117                     }
118 
119                     if (!File.Exists(ConfPath)) { return; }
120 
121                     //獲取配置檔案資訊
122                     using (StreamReader reader = new StreamReader(ConfPath))
123                     {
124                         this.ConfContent = reader.ReadToEndAsync().Result;
125                     }
126 
127                     if (string.IsNullOrWhiteSpace(this.ConfContent)) { return; }
128 
129                     //加入doc中
130                     this.doc = new XmlDocument();
131                     this.doc.LoadXml(this.ConfContent);
132 
133                     //解析
134                     var parentNode = string.Format("{0}/{1}", this.ConfParentNodeName, apiNodeName);
135                     var apiNode = this.doc.SelectSingleNode(parentNode);
136                     if (apiNode == null) { throw new Exception("未能找到" + parentNode + "節點"); }
137 
138                     this.UserName = apiNode.SelectSingleNode("UserName").InnerText;
139                     this.UserPwd = apiNode.SelectSingleNode("UserPwd").InnerText;
140                     this.ApiUrl = apiNode.SelectSingleNode("ApiUrl").InnerText;
141                     this.ApiKey = apiNode.SelectSingleNode("ApiKey").InnerText;
142                 }
143                 catch (Exception ex)
144                 {
145 
146                     throw new Exception("載入配置檔案" + this.ConfPath + "異常:" + ex.Message);
147                 }
148             }
149             #endregion
150         }
151         #endregion

這個配置檔案的類主要運用在佇列例項繼承上,只要繼承了預設就會讀取響應的配置節點資訊;配置xml檔案預設儲存的地址: C:\Conf\ShenNiuApi.xml ,最大父節點名稱預設:ShenNiuApi,格式如下所示:

1 <ShenNiuApi>
2     <QMsmq>
3         <UserName></UserName>
4         <UserPwd></UserPwd>
5         <ApiUrl>.\Private$\MyMsmq</ApiUrl>
6         <ApiKey></ApiKey>
7     </QMsmq>
8 </ShenNiuApi>

. 非安全單例建立佇列例項

由於工廠都是專門用來提供例項的存在,建立例項的模式也有很多這種,這裡我選擇的是非安全單例建立佇列例項,所有在ConfClass類中預設加入了單例模式:

 1 #region 單例模式
 2 
 3             public static readonly object Singleton_Lock = new object();
 4 
 5             /// <summary>
 6             /// 單例物件
 7             /// </summary>
 8             private static T t = default(T);
 9 
10             /// <summary>
11             /// 通過方法獲取單例
12             /// </summary>
13             /// <param name="t"></param>
14             /// <returns></returns>
15             public static T GetInstance(T t)
16             {
17                 t = t ?? new T();
18                 return t;
19             }
20 
21             /// <summary>
22             /// 通過屬性獲取單例(在繼承的時候使用)
23             /// </summary>
24             public static T Current
25             {
26                 get
27                 {
28                     t = t ?? new T();
29                     return t;
30                 }
31             }
32 
33             #endregion

因此這裡所說的工廠模式通過泛型傳遞型別,再建立例項的具體程式碼只有這麼點,簡短精煉:

 1 /// <summary>
 2     /// 佇列工廠
 3     /// </summary>
 4     public class QueueReposity<T> where T : class,IQueue, new()
 5     {
 6         public static IQueue Current
 7         {
 8             get
 9             {
10                 return PublicClass.ConfClass<T>.Current;
11             }
12         }
13     }

 

» Win7和Server2008安裝MSMQ支援

上面分享的是佇列工廠的結構,到這裡就要開始我們的第一個MSMQ佇列的安裝和封裝分享了;首先來看Win7測試環境上怎麼安裝MSMQ的支援:開始選單-》控制皮膚-》程式和功能:

-》開啟或關閉Windows功能-》勾選如圖所示佇列安裝元件:

-》確定等待安裝完成;到此win7安裝msmq就完成了,因為msmq是系統預設的所以安裝起來很方便,當然server2008也差不多,按照如下操作安裝(這裡我使用租的阿里雲Server2008R2伺服器為例):開始-》控制皮膚-》程式(下面的開啟或關閉Window功能)->功能-》新增功能-》訊息佇列:

在server上安裝的步驟基本沒啥變化,是不是很簡單;安裝完成後這樣你的電腦或伺服器就支援msmq了,此刻的你是不是很興奮,覺得又能學到新東西了呵呵;

 

» MSMQ測試用例(服務端+客戶端)

首先,這裡我用控制檯程式做測試用例,我分為客戶端和服務端,用服務端通過分裝的插入佇列方法插入資料,然後通過客戶端讀取佇列資訊,先來上個圖撐撐場面吧:

這裡我建立了MSMQ的分裝類 public class QMsmq : PublicClass.ConfClass<QMsmq>, IQueue 實現了佇列介面IQueue和繼承配置檔案類ConfClass<QMsmq>,此時具體的方法體如下:

 1  public class QMsmq : PublicClass.ConfClass<QMsmq>, IQueue
 2     {
 3 
 4 
 5         private MessageQueue _msmq = null;
 6 
 7         public void Create()
 8         {
 9             if (string.IsNullOrWhiteSpace(this.ApiUrl)) { throw new Exception("建立佇列需要指定佇列:地址"); }
10 
11             _msmq = MessageQueue.Exists(this.ApiUrl) ?
12                 new MessageQueue(this.ApiUrl) :
13                 _msmq ?? MessageQueue.Create(this.ApiUrl);
14             //設定資料格式
15             _msmq.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
16         }
17 
18         public int Total()
19         {
20             if (_msmq == null) { throw new Exception("請先建立佇列"); }
21             return _msmq.GetAllMessages().Length; 
22         }
23 
24         public Message Read()
25         {
26             try
27             {
28                 if (_msmq == null) { throw new Exception("請先建立佇列"); }
29 
30                 //60s超時
31                 return _msmq.Receive(TimeSpan.FromSeconds(60));
32             }
33             catch (Exception ex)
34             {
35                 throw new Exception(ex.Message);
36             }
37         }
38 
39         //public List<Message> ReadAll()
40         //{
41         //    try
42         //    {
43         //        if (_msmq == null) { throw new Exception("請先建立佇列"); }
44 
45         //        var messages = _msmq.GetAllMessages();
46         //        return messages.ToList();
47         //    }
48         //    catch (Exception ex)
49         //    {
50         //        throw new Exception(ex.Message);
51         //    }
52         //}
53 
54         public bool Write(string content, string name = "")
55         {
56             try
57             {
58                 if (_msmq == null) { throw new Exception("請先建立佇列"); }
59                 if (string.IsNullOrWhiteSpace(content)) { throw new Exception("填充內容不能為空"); }
60 
61                 var message = new Message();
62                 message.Body = content;
63                 message.Label = name;
64                 _msmq.Send(message);
65                 return true;
66             }
67             catch (Exception ex)
68             {
69                 throw new Exception(ex.Message);
70             }
71         }
72 
73         public void Dispose()
74         {
75             if (_msmq != null)
76             {
77                 _msmq.Close();
78                 _msmq.Dispose();
79                 _msmq = null;
80             }
81         }
82     }

到這裡我們的MSMQ簡單封裝程式碼已經完成了,咋們再來通過控制檯呼叫下這個佇列客戶端程式碼

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Client();
 6         }
 7 
 8         /// <summary>
 9         /// 客戶端
10         /// </summary>
11         private static void Client()
12         {
13             //例項化QMsmq物件
14             var msmq = QueueReposity<QMsmq>.Current;
15             try
16             {
17                 Console.WriteLine("建立:msmq");
18                 msmq.Create();
19 
20                 while (true)
21                 {
22                     try
23                     {
24                         var result = msmq.Read();
25                         Console.WriteLine(string.Format("接受第{0}個:{1}", result.Label, result.Body));
26                     }
27                     catch (Exception ex)
28                     { Console.WriteLine("異常資訊:" + ex.Message); }
29                 }
30             }
31             catch (Exception ex)
32             {
33                 throw ex;
34             }
35             finally
36             {
37                 Console.WriteLine("釋放。");
38                 msmq.Dispose();
39             }
40         }
41     }

這裡能夠看出客戶端程式碼中使用MSMQ步驟主要有:QueueReposity<QMsmq>.Current工廠建立自定義佇列例項-》Create()建立-》Read()讀取-》Dispose()釋放mq,流程還算清晰吧;如下服務端程式碼:

 1  class Program
 2     {
 3         static void Main(string[] args)
 4         {
 5             Server();
 6         }
 7 
 8         /// <summary>
 9         /// 服務端
10         /// </summary>
11         private static void Server()
12         {
13             //例項化QMsmq物件
14             var msmq = QueueReposity<QMsmq>.Current;
15 
16             try
17             {
18                 Console.WriteLine("建立:msmq");
19                 msmq.Create();
20 
21                 var num = 0;
22                 do
23                 {
24                     Console.WriteLine("輸入迴圈數量(數字,0表示結束):");
25                     var readStr = Console.ReadLine();
26                     num = string.IsNullOrWhiteSpace(readStr) ? 0 : Convert.ToInt32(readStr);
27 
28                     Console.WriteLine("插入資料:");
29                     for (int i = 0; i < num; i++)
30                     {
31                         var str = "我的編號是:" + i;
32                         msmq.Write(str, i.ToString());
33                         Console.WriteLine(str);
34                     }
35                 } while (num > 0);
36             }
37             catch (Exception ex)
38             {
39             }
40             finally
41             {
42                 Console.WriteLine("釋放。");
43                 msmq.Dispose();
44             }
45             Console.ReadLine();
46         }
47     }

服務端的步驟幾乎和客戶端差不多,區別在於一個讀取一個寫入,服務端步驟:QueueReposity<QMsmq>.Current工廠建立自定義佇列例項-》Create()建立-》Write()寫入-》Dispose()釋放mq;以上對MSMQ的程式碼分享和環境搭建講解,希望能給您帶來好的幫助,謝謝閱讀;

相關文章