手写MSMQ微软消息队列收发工具类
一、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;
}
}
XmlNodeSerializer:实体与xml互转,用于解析自定义的配置文件。代码见公众号《手写xml序列化与反序列化工具类》
配置文件说明
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微软消息队列收发工具类的更多相关文章
- 【转】MSMQ 微软消息队列 简单 示例
MSMQ它的实现原理是:消息的发送者把自己想要发送的信息放入一个容器中(我们称之为Message),然后把它保存至一个系统公用空间的消息队列(Message Queue)中:本地或者是异地的消息接收程 ...
- 微软消息队列-MicroSoft Message Queue(MSMQ)队列的C#使用
目录 定义的接口 接口实现 建立队列工厂 写入队列 获取消息 什么是MSMQ Message Queuing(MSMQ) 是微软开发的消息中间件,可应用于程序内部或程序之间的异步通信.主要的机制是:消 ...
- .Net下的MSMQ(微软消息队列)的同步异步调用
一.MSMQ简介 MSMQ(微软消息队列)是Windows操作系统中消息应用程序的基础,是用于创建分布式.松散连接的消息通讯应用程序的开发工具.消息队列 和电子邮件有着很多相似处,他们都包含多个属性, ...
- .net微软消息队列(msmq)简单案例
1.首先我们需要安装消息队列服务,它是独立的消息记录的服务,并保存在硬盘文件中. 我们添加名为:DMImgUpload的私有消息队列. 2.定义消息队列的连接字符串建议采用IP: (1)FormatN ...
- 【c#】队列(Queue)和MSMQ(消息队列)的基础使用
首先我们知道队列是先进先出的机制,所以在处理并发是个不错的选择.然后就写两个队列的简单应用. Queue 命名空间 命名空间:System.Collections,不在这里做过多的理论解释,这个东西非 ...
- EQueue - 一个纯C#写的分布式消息队列介绍2
一年前,当我第一次开发完EQueue后,写过一篇文章介绍了其整体架构,做这个框架的背景,以及架构中的所有基本概念.通过那篇文章,大家可以对EQueue有一个基本的了解.经过了1年多的完善,EQueue ...
- 微软消息队列MessageQueue(MQ)
首先本地安装微软的消息队列服务器. 基础类: namespace Core.MessageQueueTest { public class TestQueue : IDisposable { prot ...
- RabbitMQ消息队列+安装+工具介绍
1.MQ为Message Queue,消息队列是应用程序和应用程序之间的通信方法 2. 多种开发语言支持,其实就是一个驱动,如连接数据库的mysql驱动,oracle驱动等. 3. 4.采用以下语言开 ...
- 微软 消息队列 MessageQueue 简单使用
1.在服务电脑上打开 消息队列 ①进入控制面板>程序>启用或关闭windows功能 ②将需要的勾选(我自己全选了哈哈哈) ③我的电脑 右键 打开管理 见到消息队列 在专用队列上新建专用队列 ...
- 手写一个HTTP框架:两个类实现基本的IoC功能
jsoncat: 仿 Spring Boot 但不同于 Spring Boot 的一个轻量级的 HTTP 框架 国庆节的时候,我就已经把 jsoncat 的 IoC 功能给写了,具体可以看这篇文章&l ...
随机推荐
- Dolphinscheduler Docker部署全攻略
作者| 陈逸飞 Docker部署的目的是在容器中快速启动部署Apache Dolphinscheduler服务. 先决条件 docker-compose docker 使用容器单机部署Dolphins ...
- Sentry 产品指南文档(附:详细脑图整理)
Sentry 基础知识 https://docs.sentry.io/product/ https://docs.sentry.io/product/sentry-basics/ 问题 https:/ ...
- 2023 CCPC 女生题解
gym B. 终焉之茧 \(\star\) 显然两个维度分别做 单谷函数,起始点 \(A\) 是一个端点.一个 naive 的想法是三分目标点 \(B\),但交互次数会超.二分关于 \(B\) 对称点 ...
- 禅道项目管理系统权限绕过漏洞(QVD-2024-15263)
本文所涉及的任何技术.信息或工具,仅供学习和参考之用,请勿将文章内的相关技术用于非法目的,如有相关非法行为与文章作者无关.请遵守<中华人民共和国网络安全法>. 1. 概述 1.1 基本信息 ...
- 关于EF延时加载的面试题
public async Task<ActionResult> GetData() { var data = (from leftdata in GetLeft() join rightd ...
- k8s手动安装
一.主节点安装 设置主机名hostnamectl set-hostname masterhostnamectl set-hostname node01 修改hosts文件vim /etc/hosts1 ...
- 一加8t救砖教程
关机 按住音量上下和电源键 重启到fastboot 取到原系统boot ./fastboot flash boot_a boot.img ./fastboot flash boot_b boot.im ...
- 一个小小空格问题引起的bug
程序员会遇到一种情况,一个bug排查到最后是由一个很小的问题导致的.在昨天的日常搬砖中遇到一个问题,耽搁了我大半天的时间,最后查明原因让我很无语. 首先介绍一下背景,我是做算法模型训练,目前手上的工作 ...
- java.lang.NoClassDefFoundError: javax/servlet/jsp/tagext/TagLibraryValidator 问题解决
目录 报错情况 分析原因 解决方法 本文author:@愷龍 报错情况 在一次使用JSP的jstl时候按照正常引入jstl和使用for each标签出现了如下报错: 分析原因 经过一番调查研究发现原因 ...
- LLog:Spring轻量级请求日志监控组件,集成管理面板,支持多条件查询检索
开源地址 https://gitee.com/lboot/LLog 简介 LLog是基于AOP构建的请求日志记录和查询工具库,通过引入该工具库,完成配置,实现对接口请求日志的记录.查询检索等功能. 请 ...