利用MSMQ傳送訊息(物件)到NServiceBus終結點(不採用Send-Only方式)

小弟季義欽發表於2013-09-21

待解決的問題:

某些應用程式需要非同步傳送訊息到遠端機器的NServiceBus終結點,但是又不希望知道接收並處理非同步訊息的是NServiceBus這種ESB終結點,也許非同步訊息處理者將來會換成其他的ESB終結點,所以不能採用Send-Only的方式,否則應用程式(訊息傳送方)還是需要引入NserviceBus.dll等類庫。


所以我的思路是:

應用程式呼叫我的介面傳送訊息到指定的私有訊息佇列,然後NServiceBus終結點有一個執行緒,接收這樣一個訊息,這個訊息沒有實現IComman/Imessage介面,所以還要再將這個訊息轉換為NServiceBus能夠處理的訊息型別(實現ICommand,IMessage等介面,可以出發MessageHandler呼叫),然後將這個訊息通過:

Bus.Send("EndPointName@localhost", message);

傳送給本機的NServiceBus終結點。

後面會展示詳細的程式碼。。。。


其實我還想過提供給應用程式的介面是一個WCF服務,服務的實現中採用Send-Only的方式將接收到的訊息轉換為NServiceBus能夠識別的訊息,然後採用Send方式傳送到本機的NServiceBus終結點,但是我試了一下,WCF服務的實現中配置Send-Only方式那段程式碼會丟擲一個很奇怪的異常,到現在我還沒有分析出來為什麼。


所以綜上所述,提供給應用程式一個介面,將訊息傳送到我的ESB終結點,同時又沒有任何耦合,我們採用的方式是直接操作訊息佇列,程式碼如下(關鍵片段),如要下載我的完整程式碼,地址為百度雲盤


提供給應用程式的介面,包括訊息定義,和訊息傳送介面:

namespace MSMQMessageSender
{
    [Serializable]
    public class AsynMessage
    {
        public AsynMessage() { }

        public int id { set; get; }
        public string body { set; get; }
    }
}
public static bool SendMessage(AsynMessage msg)
        {
            try
            {
                MessageQueue msmq = new MessageQueue(@"FormatName:Direct=TCP:192.1.11.186\Private$\NServiceBus.EndPoint"); //這裡的訊息佇列地址是能夠將訊息傳送到遠端機器的訊息佇列的關鍵。
                msmq.Formatter = new XmlMessageFormatter(new Type[] { typeof(AsynMessage) });

                System.Messaging.Message message = new System.Messaging.Message();
                message.Label = "訊息標題";
                message.Body = msg;
                msmq.Send(message);
            }
            catch (Exception ee)
            {
                Console.WriteLine("傳送訊息出現異常,原因是:" + ee.Message);
                return false;
            }
            return true;
        }


下面是NserviceBus中,首先是啟動的時候需要建立訊息佇列:

namespace CBIP.Server
{
    class EndConfigPoint : IConfigureThisEndpoint, AsA_Publisher { }

    class ConfiguringTheDistributorWithTheFluentApi : INeedInitialization
    {
        public void Init()
        {
            if (!MessageQueue.Exists(@".\Private$\NServiceBus.EndPoint"))
            {
                System.Messaging.MessageQueue.Create(@".\Private$\NServiceBus.EndPoint");
            }
        }
    }
}

然後定義NserviceBus中能夠識別的訊息:

namespace NServiceBus.Messages
{
    [Serializable]
    public class CBIPAsynMessage : ICommand
    {
        public int id { get; set; }
        public string body { get; set; }
    }
}

然後是在NServiceBus的Start()方法中開啟一個輪訓執行緒,輪訓訊息佇列中的訊息:

namespace CBIP.Server
{
    public class ServerEndPoint : IWantToRunWhenBusStartsAndStops
    {
        public IBus Bus { get; set; }

        public void Start()
        {
            Thread thread = new Thread(MyThread);
            thread.Start();        
        }

        public void MyThread()
        {

            while (true)
            {
                MessageQueue MQ = new MessageQueue(@".\Private$\NServiceBus.EndPoint");

                //呼叫MessageQueue的Receive方法接收訊息
                System.Messaging.Message message = null;
                try
                {
                    message = MQ.Receive(TimeSpan.FromSeconds(5));
                }
                catch (MessageQueueException ee)
                {
                    message = null;
                    Console.WriteLine("超時:" + ee.Message);
                }

                if (message != null)
                {                    
                    message.Formatter = new XmlMessageFormatter(new Type[] { typeof(MSMQMessageSender.AsynMessage) });
                    AsynMessage msg = (AsynMessage)message.Body;
                    Console.WriteLine(msg.id + ", " + msg.body);
                    
                    CBIPAsynMessage cbipMsg = new CBIPAsynMessage() //轉換成NServiceBus能夠識別的訊息
                    {
                        id = msg.id,
                        body = msg.body
                    };
                    Bus.Send("CBIP.Server@localhost", cbipMsg);	//傳送到本機NServiceBus節點
                    Console.WriteLine("傳送到ESB完成");
                    
                }
                else
                {
                    Console.WriteLine("沒有找到訊息!");
                }                
            }
        }

        public void Stop()
        {

        }

    }
}

就這樣在NServiceBus的訊息處理者中就能正確收到訊息了:

namespace CBIP.Server
{
    public class AsynMessageHandler : IHandleMessages<CBIPAsynMessage>
    {
        public void Handle(CBIPAsynMessage message)
        {
            Console.WriteLine("AsynMessageHandler收到一個訊息");
        }
    }
}


直接以程式的方式啟動NServiceBus終結點,上面的程式碼是可以正確工作的,但是當我將我的NServiceBus終結點安裝位Windows NT服務之後,問題來了,輪詢執行緒無論如何也收不到訊息,然後我去檢視訊息佇列NServiceBus.EndPoint,發現裡面是有訊息的,那就是輪詢執行緒沒辦法取出來。


後來除錯才發現時沒有訪問許可權,錯誤是“Access to Message Queuing System id denied”

網上查了一下是需要在建立訊息佇列的時候,給指定使用者配置訪問許可權的,NServiceBus中建立佇列的程式碼改為下面這樣就可以了:

if (!MessageQueue.Exists(@".\Private$\NServiceBus.EndPoint"))
{
        MessageQueue mq = System.Messaging.MessageQueue.Create(@".\Private$\NServiceBus.EndPoint");
	mq.SetPermissions("Administrator", MessageQueueAccessRights.FullControl);
}



======================================= 華麗的分割線 ==============================

另外,正常來說,NserviceBus的Master終結點安裝了NServiceBus的軟體,有實現了IConfigureThisEndpoint, AsA_Publisher { }這兩個介面的類,有訊息處理者,寄宿到NServiceBus.Host.exe,填寫好命令列引數NServiceBus.Integration和NServiceBus.Master,並且電腦安裝了訊息佇列之後,是可以沒有問題地啟動起來,並自動建立訊息佇列的,我的專案中開始也是這樣,但是不知道為什麼一段時間之後啟動報錯:“無法建立訊息佇列,或者沒有對應許可權”。


這個問題還沒找到原因,目前採用的辦法只能是顯示判斷是否存在訊息佇列,不存在就顯示建立一個。

相關文章