一、MSMQ介绍

MSMQ(Microsoft Message Queuing)是微软开发的消息队列技术,支持事务,支持异步发送和接收消息。

两个重要的概念:队列和消息。队列是存放消息的容器和传输消息的通道。消息是在队列上存储和传输的数据的基本单元;这个消息在计算机上的存在形式可以是任意格式的文件;在C#程序中的消息是各种类型的Class类的实例,在程序中的消息类型可以在创建XmlMessageFormatter实例时指定。消息最大4M。

应用场景可以是异步处理,如系统短时间收到大量数据/请求,到达程序能够处理的请求数上限,可以将待处理的数据/请求全部放入队列中,程序再从队列中读取消息逐个处理。应用场景也可以是系统解耦,最常见的举例如电商平台中,订单系统要将订单数据发给支付系统时,订单系统可以将数据存入队列中,支付系统从队列中读取后处理。这时订单系统不需要调用支付系统的接口,这样订单系统和支付系统可以独立部署减少了依赖。

二、工具类封装

封装的好处:甲方早期项目中大量用到微软的技术,消息队列都是用的MSMQ。我们的多个项目都有对接MQ的需求,将消息收发的功能从多个项目中提取出来放一个独立程序中,后期维护中只需要修改一份代码。同时MQ这部分功能也从业务系统中剥离出来,更新MQ程序时不影响业务系统。封装中需要兼容的两点:

  • 功能较全,适用性强。满足多种格式的数据收发,同时兼容事务性队列和非事务性队列。

  • 配置简单。

  • 同时支持多个队列的收发处理。

几个核心类

  • MQHandler:抽象类,实现消息发送和备份、消息接收的主流程,抽象方法Send和Receive,这两个方法在下一层的实现类中实现具体功能。为什么要有这个抽象类?这里主要考虑项目中将来可能出现别的类型队列,但对【发送-备份-接收】的主流程来说不管什么类型的队列都不变,那么这部分功能对不同队列来说是相同的代码,因此在MQHandler实现,因队列类型不同的部分代码分别在下一层中实现。

   public abstract class MQHandler
{
protected MqCfgEntity cfg = null;
//是否正在处理消息
private bool IsProcessingMessage = false; public MQHandler(MqCfgEntity cfg)
{
this.cfg = cfg;
} public void Start()
{
while (true)
{
try
{
if (IsProcessingMessage) return;
IsProcessingMessage = true;//正在处理消息
if ("Send".Equals(cfg.ProcessType))
{
Send();
}
else if ("Receive".Equals(cfg.ProcessType))
{
Receive();
}
IsProcessingMessage = false; //消息处理完成
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
IsProcessingMessage = false; //消息处理完成
}
Thread.Sleep(cfg.SplitSeconds * 1000);
}
} /// <summary>
/// 备份已发送的文件,file为待备份完整文件名
/// </summary>
protected void BackSendFile(string file)
{
if (File.Exists(file) && cfg.BackPath.IsNotNullOrEmpty())
{
FileInfo info = new FileInfo(file);
string backPath = cfg.BackPath;
if (cfg.BackFormat.IsNotNullOrEmpty())
{
backPath = $"{backPath}/{DateTime.Now.ToString(cfg.BackFormat)}";
if (!Directory.Exists(backPath))
{
Directory.CreateDirectory(backPath);
}
}
string backName = $"{backPath}/{info.Name}";
if (File.Exists(backName))
{
backName = $"{backPath}/{Guid.NewGuid().ToString()}_{info.Name}";
}
File.Move(file, backName);
}
} private void Send()
{
var names = Directory.GetFiles(cfg.FilePath, cfg.FileFilter);
if (names != null && names.Count() > 0)
{
var files = names.ToList().Select(f => f.ForMatPath());
foreach (var file in files)
{
if (file.TryOpenFile() && Send(file))
{
Log4Net.Info($"{file}已发送");
BackSendFile(file);
Log4Net.Info($"{file}已备份");
}
}
}
} public abstract bool Send(string file); public abstract void Receive();
}
  • MSMQHandler,继承自MQHandler,实现Send和Receive方法。这里面的XmlMessageFormatter参数用来指定消息类型。如文件byte[],文本string,xml对象XmlDocument。

   public class MSMQHandler : MQHandler
{
private XmlMessageFormatter formatter = null;
//把xml当作txt读取在msmq中传输时,使用utf8编码,Unicode可能会造成部分报文数据紊乱
private Encoding encoding = Encoding.UTF8; public MSMQHandler(MqCfgEntity cfg) : base(cfg)
{
Type type = GetMsgType(cfg.MessageType);
formatter = new XmlMessageFormatter(new Type[] { type });
} public override void Receive()
{
MessageQueue queue = null;
try
{
queue = new MessageQueue(cfg.Queue);
int num = queue.GetAllMessages().Length;
for (int i = 0; i < num; i++)
{
ReceiveMessage(queue);
}
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (queue != null) queue.Dispose();
}
} private void ReceiveMessage(MessageQueue queue)
{
System.Messaging.Message message = null;
try
{
message = queue.Receive();
message.Formatter = formatter;
string toFile = $"{cfg.FilePath}/{message.Label}";
if ("file".Equals(cfg.MessageType))
{
SaveMessageAsBinaryFile(message, toFile);
}
else if ("xml".Equals(cfg.MessageType))
{
var doc = (XmlDocument)message.Body;
doc.Save(toFile);
}
else if ("txt".Equals(cfg.MessageType))
{
var txt = (string)message.Body;
SaveMessageAsTxt(message, toFile);
}
Log4Net.Info($"收到消息,已保存,{toFile}");
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (message != null) message.Dispose();
}
} private void SaveMessageAsTxt(Message message, string toFile)
{
FileStream fs = null;
try
{
fs = new FileStream(toFile, FileMode.Create);
string content = (string)message.Body;
var bts = encoding.GetBytes(content);
fs.Write(bts, 0, bts.Length);
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (fs != null) fs.Dispose();
}
} private void SaveMessageAsBinaryFile(Message message, string toFile)
{
FileStream fs = null;
try
{
fs = new FileStream(toFile, FileMode.Create);
var bts = (byte[])message.Body;
fs.Write(bts, 0, bts.Length);
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (fs != null) fs.Dispose();
}
} public override bool Send(string file)
{
bool success = true;
FileInfo fileInfo = new FileInfo(file);
MessageQueue myQueue = null;
try
{
myQueue = new MessageQueue(cfg.Queue);
object body = null;
if ("file".Equals(cfg.MessageType))
{
FileStream fs = null;
try
{
fs = new FileStream(file, FileMode.Open);
byte[] bts = new byte[fs.Length];
fs.Read(bts, 0, bts.Length);
body = bts;
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (fs != null) fs.Dispose();
}
}
else if ("xml".Equals(cfg.MessageType))
{
XmlDocument doc = new XmlDocument();
doc.Load(file);
body = doc;
}
else if ("txt".Equals(cfg.MessageType))
{
FileStream fs = null;
try
{
fs = new FileStream(file, FileMode.Open);
byte[] bts = new byte[fs.Length];
fs.Read(bts, 0, bts.Length);
string content = encoding.GetString(bts);
body = content;
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (fs != null) fs.Dispose();
}
}
Push(fileInfo.Name, myQueue, body);
}
catch (Exception ex)
{
success = false;
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (myQueue != null) myQueue.Dispose();
}
return success;
} //往队列上推送消息
private void Push(string fileName, MessageQueue myQueue, object body)
{
System.Messaging.Message message = null;
try
{
message = new System.Messaging.Message(body);
message.Formatter = formatter;
message.Label = fileName;
if (cfg.IsTransQueue)
{
using (MessageQueueTransaction trans = new MessageQueueTransaction())
{
trans.Begin();
myQueue.Send(message, trans);
trans.Commit();
}
}
else
{
myQueue.Send(message);
}
}
catch (Exception ex)
{
Log4Net.Error($"{ex.Message},{ex.StackTrace}");
}
finally
{
if (message != null) message.Dispose();
}
} /// <summary>
/// 根据配置文件的类型,返回MQ队列上的消息类型
/// </summary>
private Type GetMsgType(string code)
{
Type type = null;
switch (code)
{
case "file": type = typeof(byte[]); break;
case "txt": type = typeof(string); break;
case "xml": type = typeof(XmlDocument); break;
}
return type;
}
}

配置文件说明

  • mq.xml,在exe同级目录中,根节点为Config,其中可以包含多个Msmq节点,一个Msmq节点对应一个接收或发送任务。

  • Msmq节点字段说明:

    • ProcessType:Send或Receive,表示用于发送或接收消息。

    • Queue:队列名称。

    • FilePath:待发送的文件所在目录,或接收到的文件的存放目录。

    • FileFilter:Send时才配置,表示待发送目录中哪些后缀格式的文件需要处理,如*.txt,*.xml,*.jpg,*.*。

    • SplitSeconds:每一轮任务处理完成后暂停多少秒再进入下一个轮循。

    • BackPath:Send时才配置,消息发送以后文件备份到哪个目录。

    • BackFormat:跟BackPath配合使用,BackPath是备份目录,BackPath表示备份文件在BackPath下按小时/天/月/年来分文件夹备份。可以为yyyyMM、yyyyMMdd等。

    • MessageType:消息类型,可以为file、xml、txt,表示消息以哪种类型(对应XmlMessageFormatter中的文件byte[]、文本string、xml对象XmlDocument)发送。

    • IsTransQueue:true或false,表示队列是否为事务性队列。

其它说明

  • 程序运行环境:.net framework 4.5+

  • 程序启动:直接运行MsmqClient.exe,后台进程,无前台界面。

  • 完整项目代码:关注以下公众号,后台回复"msmq"获取

手写MSMQ微软消息队列收发工具类的更多相关文章

  1. 【转】MSMQ 微软消息队列 简单 示例

    MSMQ它的实现原理是:消息的发送者把自己想要发送的信息放入一个容器中(我们称之为Message),然后把它保存至一个系统公用空间的消息队列(Message Queue)中:本地或者是异地的消息接收程 ...

  2. 微软消息队列-MicroSoft Message Queue(MSMQ)队列的C#使用

    目录 定义的接口 接口实现 建立队列工厂 写入队列 获取消息 什么是MSMQ Message Queuing(MSMQ) 是微软开发的消息中间件,可应用于程序内部或程序之间的异步通信.主要的机制是:消 ...

  3. .Net下的MSMQ(微软消息队列)的同步异步调用

    一.MSMQ简介 MSMQ(微软消息队列)是Windows操作系统中消息应用程序的基础,是用于创建分布式.松散连接的消息通讯应用程序的开发工具.消息队列 和电子邮件有着很多相似处,他们都包含多个属性, ...

  4. .net微软消息队列(msmq)简单案例

    1.首先我们需要安装消息队列服务,它是独立的消息记录的服务,并保存在硬盘文件中. 我们添加名为:DMImgUpload的私有消息队列. 2.定义消息队列的连接字符串建议采用IP: (1)FormatN ...

  5. 【c#】队列(Queue)和MSMQ(消息队列)的基础使用

    首先我们知道队列是先进先出的机制,所以在处理并发是个不错的选择.然后就写两个队列的简单应用. Queue 命名空间 命名空间:System.Collections,不在这里做过多的理论解释,这个东西非 ...

  6. EQueue - 一个纯C#写的分布式消息队列介绍2

    一年前,当我第一次开发完EQueue后,写过一篇文章介绍了其整体架构,做这个框架的背景,以及架构中的所有基本概念.通过那篇文章,大家可以对EQueue有一个基本的了解.经过了1年多的完善,EQueue ...

  7. 微软消息队列MessageQueue(MQ)

    首先本地安装微软的消息队列服务器. 基础类: namespace Core.MessageQueueTest { public class TestQueue : IDisposable { prot ...

  8. RabbitMQ消息队列+安装+工具介绍

    1.MQ为Message Queue,消息队列是应用程序和应用程序之间的通信方法 2. 多种开发语言支持,其实就是一个驱动,如连接数据库的mysql驱动,oracle驱动等. 3. 4.采用以下语言开 ...

  9. 微软 消息队列 MessageQueue 简单使用

    1.在服务电脑上打开 消息队列 ①进入控制面板>程序>启用或关闭windows功能 ②将需要的勾选(我自己全选了哈哈哈) ③我的电脑 右键 打开管理 见到消息队列 在专用队列上新建专用队列 ...

  10. 手写一个HTTP框架:两个类实现基本的IoC功能

    jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架 国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章&l ...

随机推荐

  1. gym库中from gym.wrappers import FlattenObservation的理解

    看代码的过程中看到有这样的调用: from gym.wrappers import FlattenObservation if sinstance(env.observation_space, gym ...

  2. C# 反射以及实际场景使用

    1 什么是反射 首先要复习一下C#的编译过程,可以解释为下图 其中dll/exe中,包括元数据(metadata)和IL(中间语言Intermediate Language) 另外还出现的其他名词:C ...

  3. java零基础到架构师学习线路(附视频教程)

    1.背景 很多人都在问,如何学java,要学那些内容,感觉学起来很痛苦,没得方向,学到什么程度才可以去找工作等, 在这里我以自己的学习经验工作经验和辅导学生的经验给大家梳理了一个学习线路和准备了我自己 ...

  4. SMU Summer 2023 Contest Round 2

    SMU Summer 2023 Contest Round 2 A. Treasure Hunt 当\(x1 - x2\)的差值与\(y1-y2\)的差值都能被\(x,y\)整除时,且商之和为2的倍数 ...

  5. 处理一直显示npm WARN using –force Recommended protections disabled.的问题

    使用 npm config set force false 可以消除.

  6. Win32 插入符光标跟随的打字小程序

    1.先创建插入符光标 在WM_CREATE消息中 LRESULT OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam) { HDC hdc = GetDC ...

  7. AI的那些名词

    AI 是什么? Artificial Intelligence,即人工智能,1956年于Dartmouth学会上提出,一种旨在以类似人类反应的方式对刺激做出反应并从中学习的技术,其理解和判断水平通常只 ...

  8. Windows C 盘瘦身

    修改 Window 服务器虚拟内存位置 | 博客园 怎么更改电脑默认储存位置呢?| CSDN Win11 磁盘清理怎么没有了?Win11 磁盘清理在哪打开?| 搜狐网 快速清理 Windows 大文件 ...

  9. 2024-09-04:用go语言,给定一个长度为n的数组 happiness,表示每个孩子的幸福值,以及一个正整数k,我们需要从这n个孩子中选出k个孩子。 在筛选过程中,每轮选择一个孩子时,所有尚未选

    2024-09-04:用go语言,给定一个长度为n的数组 happiness,表示每个孩子的幸福值,以及一个正整数k,我们需要从这n个孩子中选出k个孩子. 在筛选过程中,每轮选择一个孩子时,所有尚未选 ...

  10. 调用微信红包接口,本地可以服务器不可以。 请求被中止: 未能创建 SSL/TLS 安全通道

    微信红包的地址接口地址是: https://api.mch.weixin.qq.com/pay/micropay 当时造成这个不能用的原因是:我把服务器从windows server 2008升级到w ...