一、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. 控制台报IPV6问题的解决

    IPv6问题调试 Burp Suite是不支持IPv6地址进行数据通信的,这时在cmd控制台里就会抛出如下异常 java.net.SocketException: Permission denied ...

  2. 9组-Alpha冲刺-6/4

    一.基本情况 队名:不行就摆了吧 组长博客:https://www.cnblogs.com/Microsoft-hc/p/15546712.html 小组人数: 8 二.冲刺概况汇报 卢浩玮 过去两天 ...

  3. 2023 ICPC网络赛第一场(A,D,J,L)

    2023 ICPC网络赛第一场(A,D,J,L) A Qualifiers Ranking Rules 先把两场比赛的学校排名处理出来,然后两场比赛的同位次进行合并即可 #include <bi ...

  4. 电子行业MES系统流程图梳理

  5. 防止npm被墙的小技巧

    方法一: 全局安装中国服务器的包管理工具 npm i cnpm --global 下载包的时候用cnpm取代npm 方法二: 在小黑板输入:npm config set registry https: ...

  6. 导出 LaTeX 为 SVG

    LaTeX 本身并不直接支持导出 SVG 格式的文档或图片,但可以通过一些工具和插件实现将 LaTeX 文档或图形转换为 SVG 格式. 使用 dvisvgm 我们可以先将 LaTeX 文档编译为 D ...

  7. 一个故事讲解https

    1   序言 今天来聊一聊https 安全传输的原理. 在开始之前,我们来虚构两个人物, 一个是位于中国的张大胖(怎么又是你?!), 还有一个是位于米国的Bill (怎么还是你?!). 这俩哥们隔着千 ...

  8. FCA-FineBI最新版考试答案,全全全!!!

    FCA-FineBI最新版考试答案,全全全!!!-CSDN博客同博客​ Part.1:判断 第1题 判断题 「TODATE」函数或者「DATE」函数,可以将文本字段或数值字段转变成时间类型的字段.(得 ...

  9. Qt连连看(一)需求分析

    虽然开发经验并不多,但是结合以往的一点实践项目和理论学习,自认为不管是开发什么工程项目,只要遵循软件工程的构建原则,如选择一个合理的架构体系,再掌握一门语言,利用数据结构组织数据,算法实现想法和功能, ...

  10. Git 客户端基本使用及新手常见问题

    Git作为一个版本管理工具,在企业中的应用越来越普遍.作为一个测试工程师,不可避免会需要接触到Git的相关操作,以下整理Git客户端的常见操作,以及应用中新手常碰到的一些问题. 1.环境安装及配置 G ...