代码改变世界

MSMQ消息队列

2016-09-29 11:32  孙启亮  阅读(5609)  评论(0编辑  收藏  举报

message queue(微软消息队列)是在多个不同的应用之间实现相互通信的一种异步传输模式,相互通信的应用可以分布于同一台机器上,也可以分布于相连的网络空间中的任一位置。它的实现原理是:消息的发送者把自己想要发送的信息放入一个容器中(我们称之为message),然后把它保存至一个系统公用空间的消息队列(message queue)中;本地或者是异地的消息接收程序再从该队列中取出发给它的消息进行处理。

实质

  在消息传递机制中,有两个比较重要的概念。一个是消息,一个是队列。消息是由通信的双方所需要传递的信息,它可以是各式各样的媒体,如文本、声音、图象等等。消息最终的理解方式,为消息传递的双方事先商定,这样做的好处是,一是相当于对数据进行了简单的加密,二则采用自己定义的格式可以节省通信的传递量。消息可以含有发送和接收者的标识,这样只有指定的用户才能看到只传递给他的信息和返回是否操作成功的回执。消息也可以含有时间戳,以便于接收方对某些与时间相关的应用进行处理。消息还可以含有到期时间,它表明如果在指定时间内消息还未到达则作废,这主要应用与时间性关联较为紧密的应用。

  消息队列是发送和接收消息的公用存储空间,它可以存在于内存中或者是物理文件中。消息可以以两种方式发送,即快递方式(express)和可恢复模式(recoverable),它们的区别在于,快递方式为了消息的快速传递,把消息放置于内存中,而不放于物理磁盘上,以获取较高的处理能力;可恢复模式在传送过程的每一步骤中,都把消息写入物理磁盘中,以得到较好的故障恢复能力。消息队列可以放置在发送方、接收方所在的机器上,也可以单独放置在另外一台机器上。正是由于消息队列在放置方式上的灵活性,形成了消息传送机制的可靠性。当保存消息队列的机器发生故障而重新启动以后,以可恢复模式发送的消息可以恢复到故障发生之前的状态,而以快递方式发送的消息则丢失了。另一方面,采用消息传递机制,发送方不必要再担心接收方是否启动、是否发生故障等等非必要因素,只要消息成功发送出去,就可以认为处理完成,而实际上对方可能甚至未曾开机,或者实际完成交易时可能已经是第二天了。

作用

  采用msmq带来的好处是:由于是异步通信,无论是发送方还是接收方都不用等待对方返回成功消息,就可以执行余下的代码,因而大大地提高了事物处理的能力;当信息传送过程中,信息发送机制具有一定功能的故障恢复能力;msmq的消息传递机制使得消息通信的双方具有不同的物理平台成为可能。

  在微软的.net平台上利用其提供的msmq功能,可以轻松创建或者删除消息队列、发送或者接收消息、甚至于对消息队列进行管理。

一、Windows 7安装、管理消息队列
1、安装消息队列

   执行用户必须要有本地 Administrators 组中的成员身份,或等效身份。
   具体步骤:
   开始—》控制面板—》程序—》程序和功能—》打开或关闭Windows功能—》依次展开Microsoft Message Queue (MSMQ) 服务器、Microsoft Message Queue (MSMQ) 服务器核心—》确定
   如果系统提示您重新启动计算机,请单击“确定”以完成安装。
2、管理消息队列
   计算机—》右键—》管理—》服务和应用程序—》消息队列。
二、Windows Server 2008安装、管理消息队列
1、安装消息队列

   开始—》控制面板—》管理工具—》服务器管理器—》功能—》添加功能—》依次展开MSM、MSMQ服务—》确定。
2、管理消息队列
   计算机—》右键—》管理—》功能—》消息队列。
三、创建、删除和管理队列
   要开发MSMQ程序就必须学习一个很重要的类(MessageQueue),该类位于名称空间System.Messageing下。
常用方法:
   --Create()方法:创建使用指定路径的新消息队列。
   --Delete()方法:删除现有的消息队列。
   --Existe()方法:查看指定消息队列是否存在。
   --GetAllMessages()方法:得到队列中的所有消息。
   --GetPublicQueues()方法:在“消息队列”网络中定位消息队列。
   --Peek()/BeginPeek()方法:查看某个特定队列中的消息队列,但不从该队列中移出消息。
   --Receive()/BeginReceive()方法:检索指定消息队列中最前面的消息并将其从该队列中移除。
   --Send()方法:发送消息到指定的消息队列。
   --Purge()方法:清空指定队列的消息。
常用属性:
   --Priority:设置消息优先级,MessagePriority枚举里全部进行了封装,MessagePriority.High();
              AboveNormal:hight与Normal消息优先级之间;
              High:高级消息优先级;
              Highest:最高消息优先级;
              Low:低消息优先级;
              Lowest:最低消息优先级;
              Normal:普通消息优先级;
              VeryHigh:Highest和High消息优先级之间;
              VeryLow:Low和Lowest消息优先级之间;
四、发送和序列化消息
   MSMQ消息队列中定义的消息由一个主体(body)和若干属性构成。消息的主体可以由文本、二进制构成,根据需要还可以被加密。
   在MSMQ中消息的大小不能够超过4MB。发送消息是通过Send方法来完成的,需要一个Message参数。
1、发送消息:
   步骤:连接队列-->指定消息格式-->提供要发送的数据(主体)-->调用Send()方法将消息发送出去。详细见后面的示例程序。
2、序列化消息:
   消息序列化可以通过.NET Framework附带的三个预定义格式化程序来完成:
   --  XMLMessageFormatter对象----MessageQueue组件的默认格式化程序设置。
   --  BinaryMessageFormatter对象;
   --  ActiveXMessageFormatter对象;
   由于后两者格式化后的消息通常不能为人阅读,所以我们经常用到的是XMLMessageFormatter对象。该对象构造方法有三种重载:
   1、public XmlMessageFormatter();
   2、public XmlMessageFormatter(string[] targetTypeNames);
   3、public XmlMessageFormatter(Type[] targetTypes);
   如我们后面的示例程序中用到的序列化语句:
   //序列化为字符串
   XmlMessageFormatter formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
五、读取和接收消息
1、读取消息:
   也就是从指定队列中获取消息。
2、接收消息有两种方式:
   --> 通过Receive()方法。
   --> 通过Peek()方法。

C# 添加引用System.Messaging,using System.Messaging;

简单的小例子

创建程序MSMQ1及MSMQ2

using System;
using System.Messaging;
using System.Windows;

namespace MSMQ
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //新建消息循环队列或连接到已有的消息队列
            string path = ".\\private$\\killf";
            mq = MessageQueue.Exists(path) ? new MessageQueue(path) : MessageQueue.Create(path);
            mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
        }

        private MessageQueue mq;

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            mq.Send(this.textBox1.Text);
        }
    }
}

MSMQ2

using System;
using System.Messaging;
using System.Windows;

namespace MSMQ2
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            //新建消息循环队列或连接到已有的消息队列
            string path = ".\\private$\\killf";
            mq = MessageQueue.Exists(path) ? new MessageQueue(path) : MessageQueue.Create(path);
            mq.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
            mq.ReceiveCompleted += mq_ReceiveCompleted;
            mq.BeginReceive();
        }
        MessageQueue mq;

        void mq_ReceiveCompleted(object sender, ReceiveCompletedEventArgs e)
        {
            //throw new NotImplementedException();
            MessageQueue mq = (MessageQueue)sender;
            System.Messaging.Message m = mq.EndReceive(e.AsyncResult);
            //处理消息
            string str = m.Body.ToString();
            this.Dispatcher.Invoke(new Action<string>(ShowMsg), str);

            //继续下一条消息
            mq.BeginReceive();
        }
        private void ShowMsg(string msg)
        {
            this.textBlock1.Text += msg + Environment.NewLine;
            return;
        }
    }
}

运行结果

截图20160929112602880截图20160929112555409

 

其他前辈经验

摘自 http://www.makaidong.com/%E5%8D%9A%E5%AE%A2%E5%9B%AD%E7%9F%A5%E8%AF%86%E5%BA%93/2779.shtml

发送:个人觉得发送问题比较重要的就是客户端与远程msmq服务器传送msmq消息的问题,经过本人查资料发现远程msmq通信必须配置ad和开放对应权限,因为比较麻烦,而且本人所在项目本身没跨网络的需求,所以没有深入了解。其他os tcp等内网方式通信都ok, 不过还有一点值得注意的就是:与虚拟机的msmq通信未能成功

     接收: 这里先贴一下本人测试的接收代码,这里是客户端发送和服务器接收分开做的测试,服务器接收端放到各种场景(虚拟机、外网、内网本机、内网其他机器)测试

class program
    {
// define static class members.
static manualresetevent signal = new manualresetevent(false);
static int count = 0;
static void main(string[] args)
        {
string serverformatname = "formatname:direct=os:vis-pc\\private$\\msmqtest";
            console.writeline("msmq测试:");
try
            {
                messagequeue queue = new messagequeue(serverformatname,queueaccessmode.receive);
                queue.receivecompleted += new receivecompletedeventhandler(queue_receivecompleted);
                queue.beginreceive();
                signal.waitone();
            }
catch (messagequeueexception e)
            {
                console.writeline(e.tostring());
            }
return;
        }
//判断成功接收后可更新队列表状态-需要队列表提供接口
protected static void queue_receivecompleted(object source, receivecompletedeventargs e)
        {
try
            {
string result = string.empty;
if (source.equals(null))
                {
return;
                }        
                messagequeue mq = (messagequeue)source;
                message m = mq.endreceive(e.asyncresult);
//var query = m.body;
//result = (string)m.body;
                count += 1;
if (count == 10)
                {
                    signal.set();
                }
//可更新系统的队列表状态-证明信息接收成功
string filepath = @"e:\log.txt";
using (streamwriter sw = new streamwriter(filepath, true))
                {
                    string logmsg = string.format("[{0}]{1}", datetime.now.tostring(), "接收成功");
                    sw.writeline(logmsg);
                }
                mq.beginreceive();
            }
catch (messagequeueexception _mqe)
            {
                console.writeline(_mqe.tostring());
            }
return;
        }
    }

  这里参考了微软官方的示例,加了线程等待等,而且模拟了监听功能,但是接收中出现了接收不到主体信息内容等(描述信息能收到),需要强调的是发送和接收在本机测试都没问题 把接收程序放在其他机器上会引发message对象boby属性的:system.invalidoperationexception异常 经分析可能是权限问题

  同事找到msdn上这样一篇文章:启用安全的远程读取 http://technet.microsoft.com/zh-cn/library/cc737783(ws.10).aspx 在这里做过测试,但是未能成功,不知道是不是其他权限没有给足:马开东博客也有人碰到过类似问题 http://www.cnblogs.com/yjmyzz/archive/2007/12/04/982440.html 和给出解决思路,这里就不细说了。

     消息队列是微软对消息服务领域的开创性尝试。它采用了特殊的通信机制,对改善和提供系统的可扩展性和高可用性具有重要意义:

     (1)对异步的消息发送方式和离线通信方式的支持

     (2)消息发送方和消息处理方可以完全分离

     (3)可靠的消息传输,消息队列通过特殊的传输机制,比如消息确认、超时处理、消息日志以及死信队列等,从分保证了消息的可靠传输

     (4)事物的支持,提供对本地事物和分布式事物的支持,可以可以把一个消息队列的操作和一个基于sqlserver的操作纳入同一事物中

     消息队列按照可访问性可分为两种类型的队列:

     (1)公共消息队列,公共消息队列发布于活动目录ad并被复制windows域。因为可以在不知道队列所在机器名称的情况下对公共队列进行检索,因而将公共队列从一台计算机移到另一台上,并不会对客户端应用造成任何影响

     (2)私有消息队列,私有消息队列一般在没有ad的工作组环境中使用,它们不支持身份验证,并且需要队列所在的计算机名称方能定位

     最终抛弃了framework的msmq操作,使用wcf的msmq操作测试成功。wcf的可靠性会话是基于ws-rm标准的,而msmq只是ms自家的

     wcf下基于消息队列的url具有net.msmq前缀。net.msmq地址中必须要指明队列的类型