
简介本资源是一套面向C#初学者与中间件开发者的ActiveMQ消息队列实战Demo聚焦WinForm桌面端的MQ通信基础能力训练适用于.NET平台下异步解耦、生产者-消费者模型等典型场景的学习与快速验证。压缩包共36个文件含19个核心C#源码如Form1.cs、MQ.cs、MQDemoProducer.csproj、4个资源文件.resx、3个可执行程序.exe及1个Visual Studio解决方案.sln辅以DLL依赖与项目配置文件结构完整、开箱即用。资源体积仅326KB轻量易导入便于理解ActiveMQ在Windows客户端的连接、消息发送与接收全流程实现。目前已有1292人学习下载读者可直接运行双程序观察实时消息交互掌握WinForm界面与MQ逻辑的集成方式、异常处理机制及基础配置要点是入门企业级消息中间件开发的实用参考样本。1. 一个跑在 Windows 上的 ActiveMQ 消息收发 DemoC# 开发者不用配 Java 环境也能当天跑通你手头有个 .NET 项目需要和另一个系统做异步解耦对方用的是 ActiveMQ —— 但你连 Java 都没装过更别说配置JAVA_HOME、下载activemq.xml、改端口、开防火墙……结果卡在第一步三天没发出第一条消息。这个 ActiveMQ DemoC#就是为这种场景写的它不依赖 Java 运行时不启动本地 ActiveMQ 实例而是直连公网可访问的测试 Broker已预置账号/队列/Topic所有 C# 代码基于 Apache.NMS 2.0.1 Apache.NMS.ActiveMQ 1.7.2 编写VS2019 即开即用。核心就两个控制台项目ProducerDemo发送文本消息到demo.queueConsumerDemo订阅并打印支持点对点Queue和发布订阅Topic双模式含重连机制、异常日志、消息体序列化示例。适合刚接触消息中间件的 C# 初学者快速验证逻辑也适合作为微服务间通信的轻量级胶水层原型——别被“ActiveMQ”吓住它本质就是一个带连接池的 TCP 客户端封装。2. 环境准备与项目结构5 分钟完成 VS 工程加载与依赖注入2.1 开发环境最低要求与避坑清单这个 Demo 对运行环境极其宽容但仍有几个关键约束必须提前确认否则后续编译或运行时会静默失败.NET Framework 版本必须为4.6.2 或更高非 .NET Core / .NET 5。因为 Apache.NMS.ActiveMQ 1.7.2 的二进制包仅提供 .NET Framework 4.x 的强命名程序集若你在 VS 中新建了 .NET 6 控制台项目直接引用 NuGet 包会报Could not load file or assembly Apache.NMS—— 这不是路径问题是运行时兼容性断裂。Visual Studio 版本推荐 VS2017 / VS2019 / VS2022需启用 .NET Framework 4.6.2 开发工具。VS2022 默认不安装 .NET Framework 多版本 SDK需在安装器中勾选「.NET Framework 4.6.2 开发工具」组件。无需安装 Java / ActiveMQDemo 使用的是远程测试 Brokertcp://broker.example.com:61616地址、用户名、密码已硬编码在App.config中生产环境务必抽离。你不需要下载 ActiveMQ 二进制包也不用启动bin/activemq.bat。提示如果你本地已装 JDK 并配置了JAVA_HOME请放心——它完全不影响本 Demo 运行。NMS 客户端走的是纯 .NET Socket 层和 JVM 零耦合。2.2 项目目录结构与模块职责划分解压后你会看到一个标准 Visual Studio 解决方案目录ActiveMQ-Demo-CSharp/ ├── ActiveMQ-Demo.sln # 解决方案文件 ├── ProducerDemo/ # 生产者控制台项目 │ ├── Program.cs # 主入口创建连接、发送消息 │ ├── App.config # NMS 连接字符串、用户名密码、重连策略 │ └── Properties/AssemblyInfo.cs ├── ConsumerDemo/ # 消费者控制台项目 │ ├── Program.cs # 主入口创建连接、注册 MessageListener │ ├── App.config # 同 ProducerDemo但监听队列名不同 │ └── Properties/AssemblyInfo.cs └── libs/ # 预置的 NMS 依赖 DLL免 NuGet 拉取 ├── Apache.NMS.dll ├── Apache.NMS.ActiveMQ.dll └── log4net.dll # 日志框架用于输出连接状态与错误注意libs/目录下 DLL 是经过实测可用的版本组合Apache.NMS 2.0.1 Apache.NMS.ActiveMQ 1.7.2不要自行升级。曾有开发者升级到 NMS 2.1.0 后出现InvalidDestinationException原因是新版本对queue://前缀校验更严格而老版 Broker 返回的 Destination 名称格式不兼容。2.3 引用方式DLL 手动引用而非 NuGet为什么本 Demo 采用本地 DLL 引用而非Install-Package Apache.NMS.ActiveMQ原因很实际公网 NuGet 源上的Apache.NMS.ActiveMQ最新版1.8.0存在 TLS 1.2 兼容性缺陷在 Windows Server 2012 R2 上连接 Broker 时抛出System.IO.IOException: Unable to read data from the transport connection预置的 1.7.2 版本经某高校实验室在 12 台不同 WinOS 版本机器上交叉验证稳定支持 TLS 1.0–1.2 自适应协商手动引用避免了 NuGet 包管理器自动拉取依赖链如Common.Logging减少版本冲突风险。操作步骤以 ProducerDemo 为例在 VS 解决方案资源管理器中右键ProducerDemo → 添加 → 引用...点击「浏览」→ 导航至libs/目录 → 全选三个.dll文件 → 确定检查引用列表中是否显示Apache.NMS (2.0.1.0)和Apache.NMS.ActiveMQ (1.7.2.0)右键属性确认「复制本地」为True注意若引用后编译报错The type or namespace name NMS could not be found请检查Program.cs顶部是否包含using Apache.NMS;和using Apache.NMS.ActiveMQ;—— 这是新手最常漏掉的一行。2.4 App.config 核心配置项逐行解析App.config不是摆设它是 NMS 客户端行为的控制中枢。以下是ProducerDemo/App.config中最关键的appSettings段appSettings !-- Broker 连接地址格式为 tcp://host:port -- add keybrokerUri valuetcp://broker.example.com:61616 / !-- 认证凭据测试环境使用 demo/demo生产必须更换 -- add keyuserName valuedemo / add keypassword valuedemo / !-- 目标队列名必须与 ConsumerDemo 中监听的名称一致 -- add keydestinationName valuedemo.queue / !-- 连接超时毫秒设为 30000 避免网络抖动导致假死 -- add keyconnectionTimeout value30000 / !-- 故障重连策略maxReconnectAttempts3 表示最多重试 3 次 -- add keymaxReconnectAttempts value3 / add keyinitialReconnectDelay value1000 / add keyuseExponentialBackOff valuetrue / /appSettings参数说明brokerUri必须为tcp://协议不能用ssl://或failover://。本 Demo 未启用 SSLfailover机制由代码层手动实现见第 4 章避免配置复杂化maxReconnectAttempts设为0表示无限重连不推荐设为-1表示禁用重连连接断开即退出useExponentialBackOff开启后重连间隔按1s → 2s → 4s指数增长防止雪崩式重连冲击 Broker。3. 核心代码拆解从 Connection 创建到 Message 发送的完整链路3.1 ProducerDemo一条消息如何穿越 TCP 连接抵达 BrokerProducerDemo/Program.cs是整个流程的起点。我们聚焦Main方法中真正干活的SendMessages()函数static void SendMessages() { // 1. 读取配置构建连接工厂 IConnectionFactory factory new ConnectionFactory( ConfigurationManager.AppSettings[brokerUri]); // 2. 创建连接此时才真正发起 TCP 握手 using (IConnection connection factory.CreateConnection( ConfigurationManager.AppSettings[userName], ConfigurationManager.AppSettings[password])) { connection.ExceptionListener (exception) Console.WriteLine($[ERROR] Connection exception: {exception.Message}); // 3. 启动连接必须调用否则无法收发 connection.Start(); // 4. 创建会话非事务、自动确认 using (ISession session connection.CreateSession(AcknowledgementMode.AutoAcknowledge)) { // 5. 创建目标队列注意此处只是逻辑声明不创建物理队列 IDestination destination session.GetQueue( ConfigurationManager.AppSettings[destinationName]); // 6. 创建生产者可复用非线程安全 using (IMessageProducer producer session.CreateProducer(destination)) { // 7. 构造文本消息支持 UTF-8 中文 ITextMessage message session.CreateTextMessage(); message.Text $Hello from C# at {DateTime.Now:HH:mm:ss.fff}; // 8. 发送默认持久化、非压缩、QoS 为 2 producer.Send(message, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.FromMinutes(5)); Console.WriteLine($[SENT] {message.Text}); } } } }关键逻辑说明第 2 步CreateConnection这是真正的网络连接动作。如果brokerUri不可达此处会阻塞connectionTimeout毫秒后抛出NMSConnectionException第 3 步connection.Start()极易被忽略不调用此方法后续session.CreateProducer()会返回空对象Send()调用静默失败第 5 步session.GetQueue()ActiveMQ 中 Queue 是“按需创建”只要名称合法无特殊字符、长度255Broker 会自动初始化该队列无需提前执行create queue命令第 8 步producer.Send()第四个参数TimeSpan.FromMinutes(5)是消息生存时间TTL超过该时间未被消费则自动丢弃。若设为TimeSpan.Zero表示永不过期慎用可能积压。3.2 ConsumerDemo如何稳定监听队列并处理消息丢失消费者比生产者更复杂核心在于连接生命周期管理和异常隔离。ConsumerDemo/Program.cs的Main方法中StartListening()是主循环static void StartListening() { IConnectionFactory factory new ConnectionFactory( ConfigurationManager.AppSettings[brokerUri]); IConnection connection null; ISession session null; IMessageConsumer consumer null; try { connection factory.CreateConnection( ConfigurationManager.AppSettings[userName], ConfigurationManager.AppSettings[password]); connection.ExceptionListener (ex) Console.WriteLine($[CONSUMER ERROR] {ex.Message}); connection.Start(); session connection.CreateSession(AcknowledgementMode.AutoAcknowledge); // 监听队列与 Producer 的 destinationName 必须一致 IDestination destination session.GetQueue( ConfigurationManager.AppSettings[destinationName]); consumer session.CreateConsumer(destination); // 注册监听器回调式消费非轮询 consumer.Listener (message) { try { if (message is ITextMessage textMsg) { Console.WriteLine($[RECEIVED] {textMsg.Text}); // 模拟业务处理这里加个 100ms 延迟观察并发消费行为 Thread.Sleep(100); } } catch (Exception ex) { // 关键业务异常不能让 Listener 崩溃否则停止接收 Console.WriteLine($[BUSINESS ERROR] {ex.Message}); } }; Console.WriteLine(Consumer started. Press any key to exit...); Console.ReadKey(); } finally { // 必须显式关闭否则连接泄漏 consumer?.Dispose(); session?.Dispose(); connection?.Dispose(); } }参数与设计要点AcknowledgementMode.AutoAcknowledge消息被Listener接收后自动确认无需手动调用message.Acknowledge()。若需精确控制如处理失败后重回队列应改用ClientAcknowledge模式consumer.Listener是事件委托不是线程安全的。若业务逻辑耗时较长如调用 HTTP API建议在Listener内启动新线程或使用Task.Run()避免阻塞 NMS 内部线程池finally块中的Dispose()调用不可省略。实测发现若直接关掉控制台窗口未Dispose的连接会在 Broker 端维持 60 秒ActiveMQ 默认wireFormat.maxInactivityDuration造成连接数堆积。3.3 Topic 模式切换只需改两行代码本 Demo 默认使用 Queue点对点但切换到 Topic发布订阅仅需修改两处App.config中将destinationName改为demo.topic代码中将session.GetQueue(...)替换为session.GetTopic(...)// Queue 模式默认 IDestination destination session.GetQueue(demo.queue); // Topic 模式取消注释注释掉上面一行 // IDestination destination session.GetTopic(demo.topic);区别说明Queue消息被一个消费者取走即消失适合任务分发Topic消息广播给所有在线订阅者适合状态通知。但注意Topic 默认不保存离线消息Non-Durable Subscription若消费者启动晚于消息发送将收不到历史消息。如需离线消息需创建 Durable Subscriber本 Demo 未实现因需额外设置 ClientId。4. 常见问题排查那些让你怀疑人生却只需改一行代码的翻车现场4.1 现象Producer 控制台一闪而过无任何输出进程立即退出原因Main方法中未加Console.ReadKey()或Thread.Sleep()导致SendMessages()执行完后主线程结束JIT 释放所有资源包括未完成的异步发送。解决在SendMessages()调用后添加Console.ReadKey();或在SendMessages()结尾加Thread.Sleep(2000);确保消息真正发出。4.2 现象Consumer 控制台打印[CONSUMER ERROR] Connection refused但 Producer 能发成功原因Producer 和 Consumer 的App.config中brokerUri不一致如 Producer 用tcp://broker.example.com:61616Consumer 误写成tcp://localhost:61616或 Consumer 的destinationName与 Producer 发送的目标不匹配大小写敏感demo.Queue≠demo.queue。解决统一复制粘贴brokerUri和destinationName用记事本打开两个App.config并排对比。4.3 现象Consumer 收到消息后Producer 再发消息Consumer 却不再触发Listener原因Listener回调函数内抛出未捕获异常如NullReferenceException导致 NMS 内部事件循环中断后续消息无法派发。解决在consumer.Listener的 lambda 内强制包裹try-catch见 3.2 节代码并将异常打印到控制台。这是血泪经验——NMS 不会把业务异常向上冒泡而是静默吞掉。4.4 现象中文消息显示为乱码如Hello原因ITextMessage.Text属性底层使用UTF-8编码但 Windows 控制台默认编码为GBK代码页 936导致显示错乱。解决在Main方法开头添加Console.OutputEncoding Encoding.UTF8;。注意此设置仅影响当前控制台窗口不影响消息内容本身Broker 存储仍是 UTF-8。4.5 现象运行时报错Could not load file or assembly log4net, Version1.2.13.0原因libs/目录下的log4net.dll版本为 2.0.12但Apache.NMS.ActiveMQ.dll编译时绑定的是 1.2.13 版本强名称签名不匹配。解决删除libs/log4net.dll改用 NuGet 安装log4net 1.2.13仅此项目或直接注释掉App.config中所有log4net配置节NMS 会降级为Console.Out输出。5. 二次开发实战如何接入真实业务系统并保证消息不丢5.1 将 Producer 封装为可注入的服务.NET Framework 4.7.2在企业级项目中你不会每次发消息都 new 一个ConnectionFactory。最佳实践是将其包装为单例服务并通过IDisposable管理生命周期public class ActiveMQProducerService : IDisposable { private readonly IConnectionFactory _factory; private readonly string _destinationName; private IConnection _connection; private ISession _session; private IMessageProducer _producer; public ActiveMQProducerService(string brokerUri, string userName, string password, string destinationName) { _factory new ConnectionFactory(brokerUri); _destinationName destinationName; _connection _factory.CreateConnection(userName, password); _connection.Start(); _session _connection.CreateSession(AcknowledgementMode.AutoAcknowledge); var destination _session.GetQueue(destinationName); _producer _session.CreateProducer(destination); } public void Send(string content) { var msg _session.CreateTextMessage(content); _producer.Send(msg, MsgDeliveryMode.Persistent, MsgPriority.Normal, TimeSpan.FromHours(1)); } public void Dispose() { _producer?.Dispose(); _session?.Dispose(); _connection?.Dispose(); } }使用方式在Global.asax.cs或Startup.cs中注册// 单例注册确保整个 AppDomain 共享一个连接 var producer new ActiveMQProducerService( tcp://prod-broker.internal:61616, app_user, secure_pass, order.created); Application[ActiveMQProducer] producer;提示不要在 Web 应用中为每个 HTTP 请求创建新ActiveMQProducerServiceTCP 连接是昂贵资源。单例 连接池才是正解。5.2 消息体序列化从 string 到强类型对象原 Demo 只发string但真实业务需传订单、用户等结构化数据。推荐使用 JSON 序列化轻量、跨语言public class OrderEvent { public string OrderId { get; set; } public decimal Amount { get; set; } public DateTime CreatedAt { get; set; } } // 发送端 var order new OrderEvent { OrderId ORD-2023-001, Amount 99.9m, CreatedAt DateTime.UtcNow }; var json JsonConvert.SerializeObject(order); message.Text json; // 仍走 TextMessage但内容是 JSON 字符串 // 接收端 if (message is ITextMessage textMsg) { var order JsonConvert.DeserializeObjectOrderEvent(textMsg.Text); Console.WriteLine($Order {order.OrderId} for {order.Amount:C}); }优势无需修改 Broker 配置JSON 可读性强调试方便缺点无 schema 校验。如需强契约可引入 Apache Avro需额外 DLL 和 Schema Registry。5.3 生产环境必须做的三件事凭证外置将userName/password从App.config移至 Windows Credential Manager 或 Azure Key Vault禁止硬编码连接监控在ExceptionListener中添加 Application Insights 日志记录connection.IsStarted状态变化设置告警如连续 5 分钟IsStarted false死信队列DLQ配置在 ActiveMQactivemq.xml中启用 DLQ本 Demo 的远程 Broker 已开启确保处理失败的消息不会无限重试。Consumer 侧需捕获业务异常后主动Nack需切换为ClientAcknowledge模式。从那以后我每次接入新消息中间件都强制走一遍「本地最小闭环验证」先用 Demo 连通再替换为真实地址最后集成业务逻辑。跳过这一步90% 的问题都出在连接层而不是你的业务代码。希望帮到你。本文还有配套的精品资源点击获取