使用 MSMQ Tiggers

原本有一支排程定期執行的 EXE ,提供用 Web 站台去手動啟動,原本 EXE 與 Web 站台是同一台電腦,這樣執行是沒有問題,但是 EXE 與 Web 站台改成不同台電腦執行上就有些問題,我又不想為了一個久久才發生的事情寫成24小時執行的監聽服務,所以這一次就決定使用MSMQ的Triggers來處理這一個問題。

原本有一支排程定期執行的 EXE ,提供用 Web 站台去手動啟動,原本 EXE 與 Web 站台是同一台電腦,這樣執行是沒有問題,但是 EXE 與 Web 站台改成不同台電腦執行上就有些問題,我又不想為了一個久久才發生的事情寫成24小時執行的監聽服務,所以這一次就決定使用MSMQ的Triggers來處理這一個問題。

當然執行 UNC Path 的 EXE 也是有其他決解方案,但不是這一篇主打的項目。

簡單說明什麼是MSMQ

MSMQ 全名是Microsoft Message Queuing 中文是微軟訊息佇列,而什麼是訊息佇列呢? 簡單說是給程式使用的寄信與收信的元件,在分散式運算中時常要與遠端的程式做互動,MQ 會幫我們處理訊息的傳遞與失敗重試甚至還有交易的處理,還有 Queue 的特性是先進先出,收信元件處理完一件事,才會收下一個信,比較不容易有搶資源的事發生。

 

而什麼是MSMQ Tiggers

你可以想像 MQ 像現實世界的郵局,我們要寄一封郵件會到當地的郵局寄信,當地的郵局會將郵件傳遞到遠方的郵局,但不同的是收件的人需要主動問郵局有沒有信件,轉換成程式就是要寫程式去向 MSMQ 收信,對一個小程式來說有點不方便,而 Triggers 作用就是要郵局主動把信送到收件人手上,也就是說不用寫與 MSMQ 連接收信的程式,MSMQ 的 Tiggers 會主動啟動 EXE 並可以設定要傳什麼參數。

 

一般的MSMQ的運作方式

寄信程式 > 本機 MSMQ > 傳遞 > 遠端 MSMQ < 收信程式

 

本篇使用MSMQ Triggers的運作方式

寄信程式 > 本機 MSMQ > 傳遞 > 遠端 MSMQ > 發生Triggers > 啟動設定的 EXE

 

安裝 MSMQ 與 MSMQ Triggers

在 Windows Server 2003 與 Windows XP 專業版 以上版本的作業系統,MSMQ 與 MSMQ Triggers 都是 Windows 內建的功能,但預設是關閉的,必需到 [控制台] > [程式集] > [開啟或關閉 Windows 功能] 裡將 MSMQ 與 MSMQ Triggers 啟動(圖一)。

image
圖一 啟用 MSMQ 與 MSMQ Triggers

 

設定MSMQ 與 MSMQ Triggers

1.打開 [系統管理工具] > [電腦管理] 在 [電腦管理]視窗中的[服務與應用程式]裡可以看到[訊息佇列]的管理項目。

2.建立新的私有佇列命名為 [RunExe]

私有佇列與公有佇列最大的差別是公有佇列需要在 Active Dictionary 註冊,要傳送訊息會先向 AD 取得公有佇列所在的 Server 後才開始連線,雷同 DNS 般好處有不用寫死公有佇列 Server 的電腦名稱或 IP。

3.建立觸發程式的規則命名為[RunExeRule]。

4.設定規則的條件(如: 訊息標題包含什麼文字),一個私有佇列可以包含很多個Triggers,每個Trigger可以有不同的規則,可以設定不同的規則來觸發不同的 EXE,但本範例不使用任何條件。

5.設定觸發的 EXE

image
圖二 建立規則

 

6.設定參數,本範例使用第一個參數為文字、第二個參數為訊息識別碼、第三個參數為訊息標籤、第四個參數為訊息本文。

image
圖三 設定啟用執行檔的參數

 

7.新增觸發程序命名為 [RunExeTrigger] 並使用剛剛建立的 [RunExeRule] 規則。

image
圖四 新增觸發程序 (Trigger)

訊息處理類型的中文翻譯看的有點怪,像什麼是重建?
後來查了英文的介面才知道是 Peeking (查看)、Retrieval (重建),二者的差異為 查看 只會讀取,訊息不會移出 Queue 中,而 重建 讀取後會將訊息移出 Queue,如果建立的 MQ 在 Trigger 處理後 還有給其他的程式讀取就選擇查看,反之就選擇重建,而交易式重建則是必需程式也正常的處理完成後才會將訊息移出 Queue,但交易式重建還必需使用 MSDTC

 

範例

寄信端程式


//在.NET使用MSMQ常見的有二種,1.WCF 2.System.Messaging,因為 WCF 太肥,要用一堆設定
//所以本範例使用 System.Messaging 
using (var queue = new System.Messaging.MessageQueue(@".\private$\RunExe"))
{
    //預設是使用XmlMessageFormatter,但因為想簡化Console還要處理XML
    //這邊使用自己寫的StringMessageFormatter
    queue.Formatter = new StringMessageFormatter();
    queue.Send("Wade寫的文章其實也是不錯的", "我要推薦");
}


public class StringMessageFormatter : IMessageFormatter, ICloneable
{

    public bool CanRead(Message message)
    {
        return message.BodyStream != null;
    }

    public object Read(Message message)
    {
        if (message.BodyStream == null)
        {
            return null;
        }

        var bytes = new byte[message.BodyStream.Length];
        message.BodyStream.Read(bytes, 0, bytes.Length);
        message.Body = System.Text.Encoding.Unicode.GetString(bytes); ;
        return message.Body;
    }

    public void Write(Message message, object obj)
    {
        var str = obj as string;
        if (str != null)
        {
            //MSMQ Triggers是將Body以Unicode的方式傳給Console
            //如果不用Unicode就要在Console處理轉碼
            //還有因為UTF-8是1-4 Byte,但Unicode 是 2 Byte
            //被MSMQ Triggers硬轉成Unicode時,奇數Byte會掉一個Byte
            var bytes = Encoding.Unicode.GetBytes(str);
            message.BodyStream = new System.IO.MemoryStream(bytes);
        }
    }

    public object Clone()
    {
        return new StringMessageFormatter();
    }
}

 

觸發的 Console.exe


internal class Program
{
    private static void Main(string[] args)
    {
        //單純將參數寫到文字檔
        using (var writer = new StreamWriter(File.Open(@"e:\msnq.txt", FileMode.Create)))
        {
            for (int i = 0; i < args.Length; i++)
            {
                writer.WriteLine("{0} - {1}", i, args[i]);
            }

            writer.Flush();
        }
    }
}

實際上我是不想動原本的 Console 程式,所以才在寄信端把內容事先處理,使 Console 用原本的格式就可以執行,但一般比較常見的作法是只傳訊息識別碼,在用訊息識別碼來讀取訊息。

 

MSMQ 還有很技巧與雷,我也只使用了一些東西,有趣興可以上 Google 查詢,在台北的朋友可以星期四來 twMVC 的聚會,我們一起交流。

 

參考來源:

MSDN-MSMQ