Message

Message是MSMQ的数据存储单元,我们的用户数据一般也被填充在Message的body当中,因此很重要,让我们来看一看其在.net中的体现,如图:

在图上我们可以看见,Message提供了三个构造函数,参数body表示我们的用户数据,当我们在构造函数中传入,数据最终会被赋值给其同名属性body,参数formatter对应同名属性Formatter,它是一个序列化器,当我们的用户数据是一个复杂类型,比如类的时候Message会自动采用该序列化器将我们的复杂类型序列化。message支持3种序列化对象:

-- XMLMessageFormatter对象----MessageQueue组件的默认格式化程序设置。

-- BinaryMessageFormatter对象;

-- ActiveXMessageFormatter对象;

由于后两者格式化后的消息通常不能为人阅读,所以我们经常用到的是XMLMessageFormatter对象。该对象构造方法有三种重载:

  1. public XmlMessageFormatter();
  2. public XmlMessageFormatter(string[] targetTypeNames);
  3. public XmlMessageFormatter(Type[] targetTypes);

MSMQ队列

消息(Message)需要保存在msmq队列中,.net中采用System.Messaging.MessageQueue来管理MSMQ队列,它提供能操作MSMQ的绝大多数API,比如

1.判断指定路径的队列是否存在

其中path代表队列的路径,表示形式为"主机名\队列名称",例如:".\private$\myQueue",其中"."代表本地主机,"\private$\myQueue"则代表队列的名称,"private$"表示我们创建的是专用队列,在网络上我们可以通过路径来唯一确定一个队列。

  1. public static bool Exists(string path);

2.创建队列

path代表队列的路径,本地:".\private$\myQueue"   远程:"FormatName:Direct=TCP:192.168.22.232\private$\myQueue"

transactional表示是否创建事务队列,默认为fasle。

事务性消息,可以确保事务中的消息按照顺序传送,只传送一次,并且从目的队列成功的被检索。

  1. public static MessageQueue Create(string path);
  2. public static MessageQueue Create(string path, bool transactional);

3.删除队列

  1. public static void Delete(string path);

4.发送消息到MSMQ

obj代表我们的用户数据,transation表示将我们的发送操作纳入事务当中。

在前面我们说过MSMQ接收的是Message,但是在这里我们看到Send操作并未强制要求我们采用Message类型参数。这是因为当我传入一个Object参数数据时,在Send操作的内部自动的给我们创建了一个Message消息对象,并且将我们的传入的Object参数采用默认的序列化器序列化,然后装入Message的body属性当中,如果我们在Send方法中指定label属性,它将被赋值给Message的同名Label属性。

当然我们完全可以自定义一个message对象传入Send方法中,可以传输简单消息类型,也可以传输复杂消息类型。

  1. public void Send(object obj);
  2. public void Send(object obj, MessageQueueTransaction transaction);
  3. public void Send(object obj, string label);

5.接收消息。同理接收消息也可以被纳入事务当中,采用Receive方法在取MSMQ的消息时,如果成功,会把MSMQ的对应消息给删除掉,并且只能取到消息队里中的排队头的消息。

  1. public Message Receive();
  2. public Message Receive(MessageQueueTransaction transaction);
  3. public Message Receive(TimeSpan timeout);

如果我们想取指定标识的消息,就的采用如下的方法了,id代表消息的唯一标示。

  1. public Message ReceiveById(string id);
  2. public Message ReceiveById(string id, MessageQueueTransaction transaction);

如果我们在接收消息的后,不想把MSMQ队列中的消息删除怎么办呢?那么采用Peek方法,因为这两个方法接收MSMQ的消息,不会删除MSMQ中对应的消息,所以他们不支持事务,即没有提供事务的参数。

  1. public Message Peek();
  2. public Message PeekById(string id);

我们也可以一次性吧队列里面的所有消息取出来

  1. public Message[] GetAllMessages();

实例

说了这么多,下面让我们来代码实战一下,我们采用控制台程序做测试,我把MSMQ队列做了简单的封装,如下

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Messaging;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace Test
  9. {
  10. public class QueueManger
  11. {
  12. /// <summary>
  13. /// 创建MSMQ队列
  14. /// </summary>
  15. /// <param name="queuePath">队列路径</param>
  16. /// <param name="transactional">是否事务队列</param>
  17. public static void Createqueue(string queuePath, bool transactional = false)
  18. {
  19. try
  20. {
  21. //判断队列是否存在
  22. if (!MessageQueue.Exists(queuePath))
  23. {
  24. MessageQueue.Create(queuePath);
  25. Console.WriteLine(queuePath + "已成功创建!");
  26. }
  27. else
  28. {
  29. Console.WriteLine(queuePath + "已经存在!");
  30. }
  31. }
  32. catch (MessageQueueException e)
  33. {
  34. Console.WriteLine(e.Message);
  35. }
  36. }
  37. /// <summary>
  38. /// 删除队列
  39. /// </summary>
  40. /// <param name="queuePath"></param>
  41. public static void Deletequeue(string queuePath)
  42. {
  43. try
  44. {
  45. //判断队列是否存在
  46. if (MessageQueue.Exists(queuePath))
  47. {
  48. MessageQueue.Delete(@".\private$\myQueue");
  49. Console.WriteLine(queuePath + "已删除!");
  50. }
  51. else
  52. {
  53. Console.WriteLine(queuePath + "不存在!");
  54. }
  55. }
  56. catch (MessageQueueException e)
  57. {
  58. Console.WriteLine(e.Message);
  59. }
  60. }
  61. /// <summary>
  62. /// 发送消息
  63. /// </summary>
  64. /// <typeparam name="T">用户数据类型</typeparam>
  65. /// <param name="target">用户数据</param>
  66. /// <param name="queuePath">队列名称</param>
  67. /// <param name="tran"></param>
  68. /// <returns></returns>
  69. public static bool SendMessage<T>(T target, string queuePath, MessageQueueTransaction tran = null)
  70. {
  71. try
  72. {
  73. //连接到本地的队列
  74. MessageQueue myQueue = new MessageQueue(queuePath);
  75. System.Messaging.Message myMessage = new System.Messaging.Message();
  76. myMessage.Body = target;
  77. myMessage.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) });
  78. //发送消息到队列中
  79. if (tran == null)
  80. {
  81. myQueue.Send(myMessage);
  82. }
  83. else
  84. {
  85. myQueue.Send(myMessage, tran);
  86. }
  87. Console.WriteLine("消息已成功发送到"+queuePath + "队列!");
  88. return true;
  89. }
  90. catch (ArgumentException e)
  91. {
  92. Console.WriteLine(e.Message);
  93. return false;
  94. }
  95. }
  96. /// <summary>
  97. /// 接收消息
  98. /// </summary>
  99. /// <typeparam name="T">用户的数据类型</typeparam>
  100. /// <param name="queuePath">消息路径</param>
  101. /// <returns>用户填充在消息当中的数据</returns>
  102. public static T ReceiveMessage<T>(string queuePath,MessageQueueTransaction tran=null)
  103. {
  104. //连接到本地队列
  105. MessageQueue myQueue = new MessageQueue(queuePath);
  106. myQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) });
  107. try
  108. {
  109. //从队列中接收消息
  110. System.Messaging.Message myMessage = tran == null ? myQueue.Receive() : myQueue.Receive(tran);
  111. return (T)myMessage.Body; //获取消息的内容
  112. }
  113. catch (MessageQueueException e)
  114. {
  115. Console.WriteLine(e.Message);
  116. }
  117. catch (InvalidCastException e)
  118. {
  119. Console.WriteLine(e.Message);
  120. }
  121. return default(T);
  122. }
  123. /// <summary>
  124. /// 采用Peek方法接收消息
  125. /// </summary>
  126. /// <typeparam name="T">用户数据类型</typeparam>
  127. /// <param name="queuePath">队列路径</param>
  128. /// <returns>用户数据</returns>
  129. public static T ReceiveMessageByPeek<T>(string queuePath)
  130. {
  131. //连接到本地队列
  132. MessageQueue myQueue = new MessageQueue(queuePath);
  133. myQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) });
  134. try
  135. {
  136. //从队列中接收消息
  137. System.Messaging.Message myMessage = myQueue.Peek();
  138. return (T)myMessage.Body; //获取消息的内容
  139. }
  140. catch (MessageQueueException e)
  141. {
  142. Console.WriteLine(e.Message);
  143. }
  144. catch (InvalidCastException e)
  145. {
  146. Console.WriteLine(e.Message);
  147. }
  148. return default(T);
  149. }
  150. /// <summary>
  151. /// 获取队列中的所有消息
  152. /// </summary>
  153. /// <typeparam name="T">用户数据类型</typeparam>
  154. /// <param name="queuePath">队列路径</param>
  155. /// <returns>用户数据集合</returns>
  156. public static List<T> GetAllMessage<T>(string queuePath)
  157. {
  158. MessageQueue myQueue = new MessageQueue(queuePath);
  159. myQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(T) });
  160. try
  161. {
  162. Message[] msgArr= myQueue.GetAllMessages();
  163. List<T> list=new List<T>();
  164. msgArr.ToList().ForEach((o) =>
  165. {
  166. list.Add((T)o.Body);
  167. });
  168. return list;
  169. }
  170. catch(Exception e)
  171. {
  172. Console.WriteLine(e.Message);
  173. }
  174. return null;
  175. }
  176. }
  177. }

用户实体

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading.Tasks;
  6.  
  7. namespace Test
  8. {
  9. public class Student
  10. {
  11. /// <summary>
  12. /// 年龄
  13. /// </summary>
  14. public int Age { get; set; }
  15. /// <summary>
  16. /// 姓名
  17. /// </summary>
  18. public string Name { get; set; }
  19. }
  20. }

创建一个队列,并发送数据。【通过 自带的管理器可以查看】

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Messaging;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace Test
  9. {
  10. class Program
  11. {
  12. static void Main(string[] args)
  13. {
  14. string queuepath = @".\private$\myQueue";
  15. QueueManger.Createqueue(queuepath);
  16. Student stu = new Student() { Name="shaoshun",Age=};
  17. QueueManger.SendMessage<Student>(stu, queuepath);
  18. Console.ReadKey();
  19. }
  20. }
  21. }

接着我们采用Peek方法接收消息(即不移除MSMQ的对应消息),很显然Message依然存在MSMQ队列中

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Messaging;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace Test
  9. {
  10. class Program
  11. {
  12. static void Main(string[] args)
  13. {
  14. string queuepath = @".\private$\myQueue";
  15. //QueueManger.Createqueue(queuepath);
  16. //Student stu = new Student() { Name="shaoshun",Age=18};
  17. //QueueManger.SendMessage<Student>(stu, queuepath);
  18. Student stu= QueueManger.ReceiveMessageByPeek<Student>(queuepath);
  19. Console.WriteLine(stu.Name);
  20. Console.ReadKey();
  21. }
  22. }
  23. }

接着我们采用Receive方法来接收消息。这个时候我们可以很明显的看见MSMQ原来对应的消息被删除了。

最后让我来测试,MSMQ的事务性。我们先删除我们的队列,在重新创建。我们连续向队列中插入5个消息,但是在插入第5个消息的时候我们抛出异常,如果MSMQ支持事务的话那么前面发送的4个Message将被回滚掉,MSMQ队列中应该为0个消息。

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Messaging;
  5. using System.Text;
  6. using System.Threading.Tasks;
  7.  
  8. namespace Test
  9. {
  10. class Program
  11. {
  12. static void Main(string[] args)
  13. {
  14. string queuepath = @".\private$\myQueue";
  15. //QueueManger.Createqueue(queuepath);
  16. //Student stu = new Student() { Name="shaoshun",Age=18};
  17. //QueueManger.SendMessage<Student>(stu, queuepath);
  18. //Student stu= QueueManger.ReceiveMessageByPeek<Student>(queuepath);
  19. //Student stu = QueueManger.ReceiveMessage<Student>(queuepath);
  20. //Console.WriteLine(stu.Name);
  21.  
  22. QueueManger.Deletequeue(queuepath);
  23. QueueManger.Createqueue(queuepath);
  24. MessageQueueTransaction tran = new MessageQueueTransaction();
  25. tran.Begin();
  26. try
  27. {
  28. Student stu;
  29. for (int i = ; i < ; i++)
  30. {
  31. stu=new Student(){Name="shaoshun"+i,Age=i};
  32. QueueManger.SendMessage<Student>(stu, queuepath, tran);
  33. if (i == )
  34. {
  35. throw new Exception();
  36. }
  37. }
  38. tran.Commit();
  39. }
  40. catch
  41. {
  42. tran.Abort();
  43. }
  44. Console.ReadKey();
  45. }
  46. }
  47. }

另外值得注意的是,MSMQ的消息发送与接收,采用的是同步的方式。

这样假如我们的消息队列中一个消息都没有,我们调用Receive()去接收该队列的消息会怎么样呢? 程序会被阻塞在这里,直到消息队列中有消息,程序才会接着往下走。

碰到这种情况是很要命的,但是不怕,MSMQ支持异步消息。

异步消息处理

  在MSMQ中对消息的操作分有同步化操作和异步化操作两种,那两者到底有什么区别?

简单来说同步化操作就是一项操作没有完成前它将堵塞整个进程直到操作完成,下一项操作才会执行。

所谓的异步化操作则相反,不会堵塞启动操作的调用线程。如果想在检索消息但不想堵塞其他程序的执行,则可使用异步消息处理。

在MSMQ中异步接收消息使用BeginReceive方法和EndReceive方法来标记操作的开始和结束,异步查看消息则使用BeginPeek和EndPeek两个方法来标记异步读取的开始和结束。同时,异步接收和查看消息还可以指定超时时间。发送消息需加上事务处理,同上。异步接收消息:利用委托机制

myQueue.ReceiveCompleted += newReceiveCompletedEventHandler(MyReceiveCompleted);

  1. //通知一个或多个正在等待的线程已发生事件
  2. static ManualResetEvent signal = new ManualResetEvent(false);
  3. /// <summary>
  4. /// 异步接收消息
  5. /// </summary>
  6. private static void AsyncReceiveMessage()
  7. {
  8. MessageQueue myQueue = new MessageQueue(".\\private$\\myAsyncQueue");
  9. if (myQueue.Transactional)
  10. {
  11. MessageQueueTransaction myTransaction = new MessageQueueTransaction();
  12. //这里使用了委托,当接收消息完成的时候就执行MyReceiveCompleted方法
  13. myQueue.ReceiveCompleted += new ReceiveCompletedEventHandler(MyReceiveCompleted);
  14. myQueue.Formatter = new XmlMessageFormatter(new Type[] { typeof(MSMQ.Async.Book) });
  15. myTransaction.Begin();
  16. myQueue.BeginReceive();//启动一个没有超时时限的异步操作
  17. signal.WaitOne();
  18. myTransaction.Commit();
  19. }
  20. }
  21. private static void MyReceiveCompleted(Object source, ReceiveCompletedEventArgs asyncResult)
  22. {
  23. try
  24. {
  25. MessageQueue myQueue = (MessageQueue)source;
  26. //完成指定的异步接收操作
  27. Message message = myQueue.EndReceive(asyncResult.AsyncResult);
  28. signal.Set();
  29. Book book = message.Body as Book;
  30. myQueue.BeginReceive();
  31. }
  32. catch (MessageQueueException me){}
  33. }

遇到的问题

1MSMQ队列不存在,或您没有足够的权限执行该操作异常

原始代码:

  1. string path = ".\\Private$\\TestQueue";
  2. MessageQueue queue = new MessageQueue(path );
  3. queue.Send(msgR);

解决方式:

打开Computer Management – Message Queuing,在Private Queues下创建MSMQDemo队列

也可以用代码解决

if (!MessageQueue.Exists(path)

{  MessageQueue.Create(path); }

2:代码中创建的事务性队列,导致消息发送不到队列

1、不要手动添加队列,最好用程序创建

2、发送的时候 用事务性质的send()

MSMQ使用的更多相关文章

  1. MSMQ学习

    一.理论准备 MSMQ(MicroSoft Message Queue,微软消息队列)官方的解释是:在多个不同的应用之间实现相互通信的一种异步传输模式,相互通信的应用可以分布于同一台机器上,也可以分布 ...

  2. 基于ajax与msmq技术的消息推送功能实现

    周末在家捣鼓了一下消息推送的简单例子,其实也没什么技术含量,欢迎大伙拍砖.我设计的这个推送demo是基于ajax长轮询+msmq消息队列来实现的,具体交互过程如下图: 先说说这个ajax长轮询,多长时 ...

  3. 已经过事务处理的 MSMQ 绑定(转载)

    https://msdn.microsoft.com/zh-cn/biztalk/ms751493 本示例演示如何使用消息队列 (MSMQ) 执行已经过事务处理的排队通信. 注意 本主题的末尾介绍了此 ...

  4. 基于WCF MSMQ 的企业应用解决方案

    最近研究了一下基于MSMQ的WCF应用,从书上.网上查了很多资料,但始终没能彻底理解WCF-MSMQ的工作原理,也没能得到一个合理的应用解决方案.索性还是自己做个实验,探索一下吧.经过反复试验,颇有收 ...

  5. MSMQ

    1.安装MSMQ 2.添加私有的队列 3.MSMQ可以发送的类型可以是任意类型,包括类 static string strServer = @"FormatName:Direct=TCP:1 ...

  6. 使用MSMQ 远程队列

    ------------------------------------------------------------------------------------------------- -- ...

  7. Redis学习笔记~实现消息队列比MSMQ更方便

    什么是队列:简单的说就是数据存储到一个空间里(可以是内存,也可以是物理文件),先存储的数据对象,先被取出来,这与堆栈正好相反,消息队列也是这样,将可能出现高并发的数据进行队列存储,并按着入队的顺序依次 ...

  8. 详解.Net消息队列(MSMQ)应用

    [IT168 技术文档]MSMQ是Windows 2000.Windows XP.Windows Server 2003的一个组件,并将继续包含在Windows Vista和以后的Windows服务器 ...

  9. 利用Ajax+MSMQ(消息队列)+WebService实现服务器端向客户端的信息推送

    需求: 每当数据库有数据更新时,推送到客户端 软需求: 1.服务器资源有限,要求资源占用尽可能小: 2.项目可控,不许调用第三方不可信不稳定的方法. 已有事例: 1.58到家采用的方法是TCP的长连接 ...

  10. MSMQ消息队列 用法

    引言 接下来的三篇文章是讨论有关企业分布式开发的文章,这三篇文章筹划了很长时间,文章的技术并不算新,但是文章中使用到的技术都是经过笔者研究实践后总结的,正所谓站在巨人的肩膀上,笔者并不是巨人,但也希望 ...

随机推荐

  1. 作业12:List集合类

    一 为什么使用List? 1 List与数组 List 数组 可变长度 固定长度 方便的接口(支持Java8的Stream) / 2 List的实现方式 数组:ArrayList 查询效率比较高 插入 ...

  2. 【php设计模式】装饰器模式

    装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能.其结构图如下: Component为统一接口,也是装饰类和被装饰类的基本类型. ConcreteComponent为具体实现类 ...

  3. ES6入门:数据劫持、Proxy、Reflect

    什么是数据劫持 Object数据劫持实现原理 Array数据劫持的实现原理 Proxy.Reflect 一.什么是数据劫持 定义:访问或者修改对象的某个属性时,在访问和修改属性值时,除了执行基本的数据 ...

  4. vue 实现textarea展示时自动换行

    利用 v-html 展示 str.replace(/\n|\r\n/g, '<br>') 数据 完美解决

  5. JSP 内置对象的说明

    1 page:指的是当前的JSP页面本身,它是java.lang.object类的对象 2 config对象:它是ServletConfig类的一个实例 3 exception对象:它是java.la ...

  6. 关于操作git

    手册:http://www.yiibai.com/git/ 一.安装git,可以通过git bash执行命令行:安装tortoiseGit执行git相关操作,在那之前需要了解下git命令行操作 二.在 ...

  7. SpringCloud之Feign声明式调用原理及配置

    1 什么是Feign Feign是一种声明式.模板化的HTTP客户端(仅在Application Client中使用).声明式调用是指,就像调用本地方法一样调用远程方法,无需感知操作远程http请求. ...

  8. Spring Cloud(七)服务网关 Zuul Filter 使用

    上一篇文章中,讲了Zuul 转发,动态路由,负载均衡,等等一些Zuul 的特性,这个一篇文章,讲Zuul Filter 使用,关于网关的作用,这里就不再次赘述了,重点是zuul的Filter ,我们可 ...

  9. db2 with用法

    最近在研究db2 递归查询时想到了with,一直以为with只是用来查询递归,但是实际with功能强大,还有更加强大的功能,偶然读到一位大神的文章,对with做了很详细的解读,特贴出来供大家学习研究 ...

  10. ffmpeg 命令行 杂记

    输入mp4文件中的音频每一帧的信息 ffprobe -show_streams -select_streams a -show_format -show_frames .\HYUNDAIMOBIS.m ...