在物联网开发中,MQTT 是设备与上位机通信的常用协议,而 **MQTTS(加密 MQTT)** 能保障数据传输的安全性。本文基于 C# WinForms + uPLibrary.M2Mqtt 库,实现一个可与 STM32 等设备通信的 MQTTS 客户端,涵盖连接、订阅、发布、设备控制、日志管理等核心功能。
一、项目概述
本项目实现的 MQTTS 客户端具备以下功能:
- 连接 / 断开加密 MQTT 服务器(MQTTS,端口 8883);
- 订阅设备主题、接收设备消息;
- 发布消息到设备(含 LED 开关等控制指令);
- 管理订阅主题列表;
- 日志记录与导出;
- 适配 WinForms 跨线程 UI 更新。
二、开发准备
2.1 环境配置
- 开发工具:Visual Studio 2019+;
- 框架版本:.NET Framework 4.7.2(兼容多数 Windows 环境);
- 协议:MQTTS(基于 TLS 1.2 加密)。
2.2 依赖库安装
通过 NuGet 安装 MQTT 客户端库:
- 右键项目 → 选择「管理 NuGet 程序包」;
- 搜索「M2Mqtt」,安装
uPLibrary.Networking.M2Mqtt(本文使用 4.3.0 版本)。
三、界面快速设计
WinForms 界面包含以下核心控件:
- 输入控件:服务器地址 / 端口、订阅主题、发布主题、消息、用户名 / 密码;
- 功能按钮:连接 / 断开、订阅、发布、LED 开关、清空日志、导出日志;
- 列表控件:
ListView(显示已订阅主题); - 日志控件:
RichTextBox(记录操作与消息日志)。
四、核心功能代码解析
4.1 MQTT 客户端初始化
在窗体构造函数中完成客户端、UI 状态、SSL 配置的初始化:
public partial class Form1 : Form
{
private MqttClient _mqttClient; // MQTT客户端对象
private bool _isConnected; // 连接状态
private string _clientId; // 唯一客户端ID(用GUID避免重复)
// 存储已订阅主题(主题→QoS)
private readonly Dictionary<string, byte> _subscribedTopics = new Dictionary<string, byte>();
public Form1()
{
InitializeComponent();
_clientId = Guid.NewGuid().ToString(); // 生成唯一客户端ID
_isConnected = false;
// 初始化UI:未连接时功能按钮置灰
btnSubscribe.Enabled = btnPublish.Enabled = false;
// 默认配置(替换为你的MQTTS服务器信息)
leHostPort.Text = "p6121ba8.ala.cn-hangzhou.emqxsl.cn:8883";
leSubTopic.Text = "/stm32topc/test"; // 设备→PC主题
lePubTopic.Text = "/pctostm32/test"; // PC→设备主题
// 测试环境跳过SSL证书验证(生产环境需启用验证)
ServicePointManager.ServerCertificateValidationCallback += (s, cert, chain, err) => true;
// 启用TLS 1.2(MQTTS必需)
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls11 | SecurityProtocolType.Tls12;
}
}
4.2 连接 / 断开 MQTTS 服务器
通过btnConnect_Click实现连接逻辑,包含地址校验、SSL 客户端初始化、连接状态更新:
private void btnConnect_Click(object sender, EventArgs e)
{
if (!_isConnected)
{
// 校验地址格式([地址]:[端口])
var parts = leHostPort.Text.Split(':');
if (parts.Length != 2 || !int.TryParse(parts[1], out int port))
{
AddLog("地址格式错误(例:broker.emqx.io:8883)", true);
return;
}
string broker = parts[0];
try
{
// 初始化MQTTS客户端(启用SSL)
_mqttClient = new MqttClient(
broker, port,
true, // 启用SSL(MQTTS关键配置)
MqttSslProtocols.None, null, null
);
// 绑定消息接收回调
_mqttClient.MqttMsgPublishReceived += client_MqttMsgPublishReceived;
// 连接服务器(带用户名密码)
byte connackCode = _mqttClient.Connect(_clientId, leUsername.Text, lePassword.Text);
if (_mqttClient.IsConnected)
{
_isConnected = true;
btnConnect.Text = "断开服务器";
btnSubscribe.Enabled = btnPublish.Enabled = true;
AddLog("✅ 成功连接MQTTS服务器");
}
else
{
// 解析连接失败原因(兼容C# 7.3的switch语句)
string reason;
switch (connackCode)
{
case 1: reason = "不支持的协议版本"; break;
case 2: reason = "客户端ID无效"; break;
case 4: reason = "用户名/密码错误"; break;
default: reason = $"未知原因(返回码:{connackCode})"; break;
}
AddLog($"❌ 连接失败:{reason}", true);
}
}
catch (Exception ex)
{
AddLog($"❌ 连接异常:{ex.Message}", true);
}
}
else
{
// 断开连接
_mqttClient.Disconnect();
_isConnected = false;
btnConnect.Text = "连接服务器";
AddLog("❌ 已断开MQTTS连接");
}
}
4.3 主题订阅与取消订阅
通过btnSubscribe_Click实现主题订阅,同时记录订阅信息到ListView和字典;通过btnClose_Click取消单个订阅:
private void btnSubscribe_Click(object sender, EventArgs e)
{
string topic = leSubTopic.Text.Trim();
byte qos = MqttMsgBase.QOS_LEVEL_AT_MOST_ONCE; // QoS 0
if (string.IsNullOrWhiteSpace(topic) || !_isConnected)
{
AddLog("请输入主题并确保已连接", true);
return;
}
if (_subscribedTopics.ContainsKey(topic))
{
MessageBox.Show("已订阅该主题");
return;
}
// 执行订阅
_mqttClient.Subscribe(new[] { topic }, new byte[] { qos });
_subscribedTopics.Add(topic, qos);
// 添加到ListView显示
ListViewItem item = new ListViewItem((listViewTopics.Items.Count + 1).ToString());
item.SubItems.Add(topic);
item.SubItems.Add(qos.ToString());
item.Tag = topic; // 存储主题用于取消订阅
listViewTopics.Items.Add(item);
AddLog($"✅ 订阅主题:{topic}");
}
// 取消单个订阅
private void btnClose_Click(object sender, EventArgs e)
{
if (listViewTopics.SelectedItems.Count == 0) return;
string topic = listViewTopics.SelectedItems[0].Tag.ToString();
_mqttClient.Unsubscribe(new[] { topic });
_subscribedTopics.Remove(topic);
listViewTopics.Items.Remove(listViewTopics.SelectedItems[0]);
AddLog($"❌ 取消订阅:{topic}");
}
4.4 消息发布与设备控制
通过btnPublish_Click发布自定义消息,通过sen_ctrl封装设备控制指令(如 LED 开关):
private void btnPublish_Click(object sender, EventArgs e)
{
string topic = lePubTopic.Text.Trim();
string msg = lePubMessage.Text.Trim();
if (string.IsNullOrWhiteSpace(topic) || string.IsNullOrWhiteSpace(msg))
{
AddLog("主题或消息不能为空", true);
return;
}
// 发布消息(UTF8编码)
byte[] msgBytes = Encoding.UTF8.GetBytes(msg);
_mqttClient.Publish(topic, msgBytes);
AddLog($"📤 发布到{topic}:{msg}");
}
// 设备控制指令封装
private void sen_ctrl(string cmd)
{
string topic = lePubTopic.Text.Trim();
if (!_isConnected || string.IsNullOrWhiteSpace(topic)) return;
_mqttClient.Publish(topic, Encoding.UTF8.GetBytes(cmd));
AddLog($"📤 发送指令:{cmd}");
}
// LED开启按钮
private void led_open_Click(object sender, EventArgs e)
{
sen_ctrl("led on");
}
4.5 消息接收与跨线程 UI 更新
MQTT 消息接收在独立线程,需通过Invoke跨线程更新 WinForms 控件:
static void client_MqttMsgPublishReceived(object sender, MqttMsgPublishEventArgs e)
{
// 解析设备消息(UTF8避免中文乱码)
string payload = Encoding.UTF8.GetString(e.Message);
string log = $"📩 收到[{e.Topic}]:{payload}";
// 跨线程更新日志(获取当前窗体实例)
Form1 form = Application.OpenForms.OfType<Form1>().FirstOrDefault();
if (form != null)
{
form.AddLog(log);
}
}
// 跨线程安全的日志添加方法
public void AddLog(string log, bool isError = false)
{
if (teLogDisplay.InvokeRequired)
{
teLogDisplay.Invoke(new Action<string, bool>(AddLog), log, isError);
return;
}
// 更新日志控件
teLogDisplay.AppendText($"[{DateTime.Now:HH:mm:ss}] {log}\n");
teLogDisplay.ScrollToCaret();
teLogDisplay.SelectionColor = isError ? Color.Red : Color.Black;
}
4.6 日志导出
通过SaveFileDialog实现日志导出为本地 TXT 文件:
private void btnExportLog_Click(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(teLogDisplay.Text))
{
MessageBox.Show("日志为空,无需导出");
return;
}
using (SaveFileDialog sfd = new SaveFileDialog())
{
sfd.FileName = $"MQTT日志_{DateTime.Now:yyyyMMddHHmmss}.txt";
sfd.Filter = "文本文档|*.txt";
if (sfd.ShowDialog() == DialogResult.OK)
{
File.WriteAllText(sfd.FileName, teLogDisplay.Text, Encoding.UTF8);
AddLog($"📥 日志导出到:{sfd.FileName}");
}
}
}
五、界面展示

服务器端可参考我上一篇文章基于 RT-Thread Studio 实战:ESP8266+MQTT-优快云博客
六、后续功能扩展建议
- 添加自动重连逻辑,应对网络波动;
- 保存服务器配置到本地(如 INI/JSON 文件);
- 支持多设备主题管理;
- 增加消息解析与可视化(如显示传感器数据图表)。
该客户端可直接与 STM32 等嵌入式设备的 MQTT 客户端通信,是物联网上位机开发的实用模板。

被折叠的 条评论
为什么被折叠?



