C#使用消息队列(MSMQ)

(1)、认识消息队列

 

首先说一下,消息队列 (MSMQ Microsoft Message Queuing)是MS提供的服务,也就是Windows操作系统的功能,并不是.Net提供的。

MSDN上的解释如下:

Message Queuing (MSMQ) technology enables applications running at different times to communicate across heterogeneous networks and systems that may be temporarily offline.

Applications send messages to queues and read messages from queues.

The following illustration shows how a queue can hold messages that are generated by multiple sending applications and read by multiple receiving applications.

消息队列(MSMQ)技术使得运行于不同时间的应用程序能够在各种各样的网络和可能暂时脱机的系统之间进行通信。

应用程序将消息发送到队列,并从队列中读取消息。

下图演示了消息队列如何保存由多个发送应用程序生成的消息,并被多个接收应用程序读取。

消息一旦发送到队列中,便会一直存在,即使发送的应用程序已经关闭。

 MSMQ服务默认是关闭的,(Window7及以上操作系统)按以下方式打开

1、打开运行,输入"OptionalFeatures",钩上Microsoft Message Queue(MSMQ)服务器。

(Windows Server 2008R2及以上)按以下方式打开

2、打开运行,输入"appwiz.cpl",在任务列表中选择“打开或关闭Windows功能”

然后在"添加角色"中选择消息队列

 消息队列分为以下几种,每种队列的路径表示形式如下:

公用队列 MachineName\QueueName

专用队列 MachineName\Private$\QueueName

日记队列 MachineName\QueueName\Journal$

计算机日记队列 MachineName\Journal$

计算机死信队列 MachineName\Deadletter$

计算机事务性死信队列 MachineName\XactDeadletter$

这里的MachineName可以用 “."代替,代表当前计算机

 需要先引用System.Messaging.dll

创建消息队列

 

 1            //消息队列路径
 2             string path = ".\\Private$\\myQueue";
 3             MessageQueue queue;
 4             //如果存在指定路径的消息队列 
 5             if(MessageQueue.Exists(path))
 6             {
 7                 //获取这个消息队列
 8                 queue = new MessageQueue(path);
 9             }
10             else
11             {
12                 //不存在,就创建一个新的,并获取这个消息队列对象
13                 queue = MessageQueue.Create(path);
14             }

 

发送字符串消息

 

1                     System.Messaging.Message msg = new System.Messaging.Message();
2                     //内容
3                     msg.Body = "Hello World";
4                     //指定格式化程序
5                     msg.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
6                     queue.Send(msg);

 

 

发送消息的时候要指定格式化程序,如果不指定,格式化程序将默认为XmlMessageFormatter(使用基于 XSD 架构定义的 XML 格式来接收和发送消息。)

接收字符串消息

1               //接收到的消息对象
2               System.Messaging.Message msg = queue.Receive();
3               //指定格式化程序
4               msg.Formatter = new XmlMessageFormatter(new Type[] { typeof(string) });
5               //接收到的内容
6               string str = msg.Body.ToString();

 发送二进制消息(如图像)

引用 System.Drawing.dll

1             System.Drawing.Image image = System.Drawing.Bitmap.FromFile("a.jpg");
2             Message message = new Message(image, new BinaryMessageFormatter());          
3             queue.Send(message);

接收二进制消息

1             System.Messaging.Message message = queue.Receive();
2             System.Drawing.Bitmap image= (System.Drawing.Bitmap)message.Body;       
3             image.Save("a.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

获取消息队列的消息的数量 

1 int num  = queue.GetAllMessages().Length;

清空消息队列

1 queue.Purge();

 

以图形化的方式查看消息队列中的消息

运行输入 compmgmt.msc,打开计算机管理,选择[服务和应用程序-消息队列]。只要消息队列中的消息没有被接收,就可以在这里查看得到。

 

示例程序:

https://files.cnblogs.com/files/zhaotianff/MSMQ_Demo.zip

 

=====================================================================================

消息队列的使用:

MSMQ的基本使用

           参考了PetShop里MSMQ的代码,为了考虑到在扩展中会有其他的数据数据对象会使用到MSMQ,因此定义了一个DTcmsQueue的基类,实现消息Receive和Send的基本操作,使用到MSMQ的数据对象需要继承DTcmsQueue基类,需要注意的是:在MSMQ中使用事务的话,需要创建事务性的专用消息队列,代码如下:

using System;
using System.Messaging;
using log4net;

namespace DTcms.Web.UI
{
    /// <summary>
    /// 该类实现从消息对列中发送和接收消息的主要功能 
    /// </summary>
    public class DTcmsQueue : IDisposable {

        private static ILog logger = LogManager.GetLogger(typeof(DTcmsQueue));
        //指定消息队列事务的类型,Automatic 枚举值允许发送外部事务和从处部事务接收
        protected MessageQueueTransactionType transactionType = MessageQueueTransactionType.Automatic;
        protected MessageQueue queue;
        protected TimeSpan timeout;
        //实现构造函数
        public DTcmsQueue(string queuePath, int timeoutSeconds) {
            Createqueue(queuePath);
            queue = new MessageQueue(queuePath);
            timeout = TimeSpan.FromSeconds(Convert.ToDouble(timeoutSeconds));

            //设置当应用程序向消息对列发送消息时默认情况下使用的消息属性值
            queue.DefaultPropertiesToSend.AttachSenderId = false;
            queue.DefaultPropertiesToSend.UseAuthentication = false;
            queue.DefaultPropertiesToSend.UseEncryption = false;
            queue.DefaultPropertiesToSend.AcknowledgeType = AcknowledgeTypes.None;
            queue.DefaultPropertiesToSend.UseJournalQueue = false;
        }

        /// <summary>
        /// 继承类将从自身的Receive方法中调用以下方法,该方法用于实现消息接收
        /// </summary>
        public virtual object Receive() {
            try
            {
                using (Message message = queue.Receive(timeout, transactionType))
                    return message;
            }
            catch (MessageQueueException mqex)
            {
                if (mqex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
                    throw new TimeoutException();

                throw;
            }
        }

        /// <summary>
        /// 继承类将从自身的Send方法中调用以下方法,该方法用于实现消息发送
        /// </summary>
        public virtual void Send(object msg) {
            queue.Send(msg, transactionType);
        }

        /// <summary>
        /// 通过Create方法创建使用指定路径的新消息队列
        /// </summary>
        /// <param name="queuePath"></param>
        public static void Createqueue(string queuePath)
        {
            try
            {
                if (!MessageQueue.Exists(queuePath))
                {
                    MessageQueue.Create(queuePath, true);  //创建事务性的专用消息队列
                    logger.Debug("创建队列成功!");
                }
            }
            catch (MessageQueueException e)
            {
                logger.Error(e.Message);
            }
        }

        #region 实现 IDisposable 接口成员
        public void Dispose() {
            queue.Dispose();
        }
        #endregion
    }
}

MSMQ的具体实现方式

       上面我们已经创建了DTcmsQueue基类,我们具体实现的时候需要继承此基类,使用消息队列的时候,传递的是一个对象,所以我们首先要创建这个对象,但是需要注意的一点:此对象是必须可序列化的,否则不能被插入到消息队列里,代码如下:

/// <summary>
    /// 枚举,操作类型是增加还是删除
    /// </summary>
    public enum JobType { Add, Remove }
    /// <summary>
    /// 任务类,包括任务的Id ,操作的类型
    /// </summary>
    [Serializable]
    public class IndexJob
    {
        public int Id { get; set; }
        public JobType JobType { get; set; }
    }

  在具体的实现类里面,我们只需要继承此基类,然后重写基类的方法,具体代码如下:

using System;
using System.Configuration;
using System.Messaging;

namespace DTcms.Web.UI
{

    /// <summary>
    /// 该类实现从消息队列中发送和接收订单消息
    /// </summary>
    public class OrderJob : DTcmsQueue {

        // 获取配置文件中有关消息队列路径的参数
        private static readonly string queuePath = ConfigurationManager.AppSettings["OrderQueuePath"];
        private static int queueTimeout = 20;
        //实现构造函数
        public OrderJob()
            : base(queuePath, queueTimeout)
        {
            // 设置消息的序列化采用二进制方式 
            queue.Formatter = new BinaryMessageFormatter();
        }

        /// <summary>
        /// 调用PetShopQueue基类方法,实现从消息队列中接收订单消息
        /// </summary>
        /// <returns>订单对象 OrderInfo</returns>
        public new IndexJob Receive()
        {
            // 指定消息队列事务的类型,Automatic枚举值允许发送发部事务和从外部事务接收
            base.transactionType = MessageQueueTransactionType.Automatic;
            return (IndexJob)((Message)base.Receive()).Body;
        }
        //该方法实现从消息队列中接收订单消息
        public IndexJob Receive(int timeout)
        {
            base.timeout = TimeSpan.FromSeconds(Convert.ToDouble(timeout));
            return Receive();
        }

        /// <summary>
        /// 调用PetShopQueue基类方法,实现从消息队列中发送订单消息
        /// </summary>
        /// <param name="orderMessage">订单对象 OrderInfo</param>
        public void Send(IndexJob orderMessage)
        {
            // 指定消息队列事务的类型,Single枚举值用于单个内部事务的事务类型
            base.transactionType = MessageQueueTransactionType.Single;
            base.Send(orderMessage);
        }
    }
}

 

项目中MSMQ的具体应用

     将任务添加到消息队列代码就很简单了,没啥好说的,直接上代码:

#region 任务添加
        public void AddArticle(int artId)
        {
            OrderJob orderJob = new OrderJob();
            IndexJob job = new IndexJob();
            job.Id = artId;
            job.JobType = JobType.Add;
            logger.Debug(artId + "加入任务列表");
            orderJob.Send(job);//把任务加入消息队列
        }

        public void RemoveArticle(int artId)
        {
            OrderJob orderJob = new OrderJob();
            IndexJob job = new IndexJob();
            job.JobType = JobType.Remove;
            job.Id = artId;
            logger.Debug(artId + "加入删除任务列表");
            orderJob.Send(job);//把任务加入消息队列
        }
        #endregion

    接下来就是如下得到消息队列的任务,并将任务完成,因为消息队列是系统的一个组件跟我们的项目是完全分开的,我们可以完全独立的完成接收消息队列的任务并处理后来的动作,这样就做到了异步处理,例如做一个Windows Service,更重要的是MSMQ还是一种分布式处理技术,在本项目中,我们主要是开辟了多线程来接收消息队列的任务并处理后来的动作,具体代码如下:

public void CustomerStart()
        {
            log4net.Config.XmlConfigurator.Configure();

            PanGu.Segment.Init(PanGuPath);

            //声明线程
            Thread workTicketThread;
            Thread[] workerThreads = new Thread[threadCount];

            for (int i = 0; i < threadCount; i++)
            {
                //创建 Thread 实例 
                workTicketThread = new Thread(new ThreadStart(ProcessOrders));

                // 设置线程在后台工作和线程启动前的单元状态(STA表示将创建并进入一个单线程单元 )
                workTicketThread.IsBackground = true;
                workTicketThread.SetApartmentState(ApartmentState.STA);

                //启动线程,将调用ThreadStart委托
                workTicketThread.Start();
                workerThreads[i] = workTicketThread;
            }

            logger.Debug("进程已经开始启动. 按回车键停止.");
        }
        private static void ProcessOrders()
        {

            // 总事务处理时间(tsTimeout )就该超过批处理任务消息的总时间 
            TimeSpan tsTimeout = TimeSpan.FromSeconds(Convert.ToDouble(transactionTimeout * batchSize));

            OrderJob orderJob = new OrderJob();
            while (true)
            {

                // 消息队列花费时间
                TimeSpan datetimeStarting = new TimeSpan(DateTime.Now.Ticks);
                double elapsedTime = 0;

                int processedItems = 0;

                ArrayList queueOrders = new ArrayList();

                using (TransactionScope ts = new TransactionScope(TransactionScopeOption.Required, tsTimeout))
                {
                    // 接收来自消息队列的任务消息
                    for (int j = 0; j < batchSize; j++)
                    {

                        try
                        {
                            //如果有足够的时间,那么接收任务,并将任务存储在数组中 
                            if ((elapsedTime + queueTimeout + transactionTimeout) < tsTimeout.TotalSeconds)
                            {
                                queueOrders.Add(orderJob.Receive(queueTimeout));
                            }
                            else
                            {
                                j = batchSize;   // 结束循环
                            }

                            //更新已占用时间
                            elapsedTime = new TimeSpan(DateTime.Now.Ticks).TotalSeconds - datetimeStarting.TotalSeconds;
                        }
                        catch (TimeoutException)
                        {

                            //结束循环因为没有可等待的任务消息
                            j = batchSize;
                        }
                    }

                    //从数组中循环取出任务对象,并将任务插入到数据库中
                    for (int k = 0; k < queueOrders.Count; k++)
                    {
                        SearchHelper sh = new SearchHelper();
                        sh.IndexOn((IndexJob)queueOrders[k]);
                        processedItems++;
                        totalOrdersProcessed++;
                    }

                    //指示范围中的所有操作都已成功完成
                    ts.Complete();
                }
                //完成后显示处理信息
                logger.Debug("(线程 Id " + Thread.CurrentThread.ManagedThreadId + ") 批处理完成, " + processedItems + " 任务, 处理花费时间: " + elapsedTime.ToString() + " 秒.");
            }
        }

转载地址:https://www.cnblogs.com/beimeng/p/3298190.html#!comments

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值