聚焦于 MQTT 安全机制,结合您之前的问题(WebSocket、S7.NET、TCP、UDP、QUIC、HTTP、串口、USB、车牌识别等),我将详细讲解 MQTT 协议的安全机制原理
聚焦于 MQTT 安全机制,结合您之前的问题(WebSocket、S7.NET、TCP、UDP、QUIC、HTTP、串口、USB、车牌识别等),我将详细讲解 MQTT 协议的安全机制原理,提供 C# 代码示例(基于 .NET 8 和 MQTTnet 库),并与 S7-1200/S7-200 SMART PLC 通信以及 WebSocket/QUIC 集成,针对车牌识别物联网场景。
所有内容确保跨平台兼容(Windows、Linux、macOS),包含详细的解释,并参考搜索结果(如 MQTTnet、HiveMQ)和 MQTT 协议规范(MQTT 5.0)确保准确性和实用性。
1. MQTT 安全机制原理
1.1 MQTT 概述MQTT(Message Queuing Telemetry Transport,OASIS 标准)是一种轻量级、发布/订阅模型的通信协议,基于 TCP/IP,广泛用于物联网、工业自动化(如 PLC 数据传输)、车牌识别等场景。MQTT 5.0(最新版本)增强了安全性和功能,适合低带宽、高延迟或不可靠的网络环境。
- 发布/订阅模型:
- 发布者(Publisher):发送消息到主题(Topic,如 /neuron/s7-1200/plate)。
- 订阅者(Subscriber):订阅主题,接收消息。
- Broker:中间服务器(如 EMQX、Mosquitto),路由消息。
- QoS(服务质量):
- QoS 0:最多一次,消息可能丢失。
- QoS 1:至少一次,可能重复。
- QoS 2:恰好一次,保证可靠传输。
- 端口:1883(TCP)、8883(TLS)、8080/8081(WebSocket)。
1.2 MQTT 安全机制MQTT 的安全机制涵盖以下方面,保护数据传输、身份验证和访问控制:
1.2.1 TLS 加密(mqtts://)
- 原理:
- MQTT 使用 TLS(Transport Layer Security,1.2 或 1.3)加密通信,防止中间人攻击(MITM)和数据窃听。
- 使用 mqtts://(端口 8883)代替 mqtt://(端口 1883)。
- TLS 提供:
- 机密性:加密消息内容(如车牌数据)。
- 完整性:通过 HMAC 或 AEAD(如 AES-GCM)检测篡改。
- 认证:服务器证书验证 Broker 身份,客户端可选提供证书(双向认证)。
- 实现:
- Broker 配置 SSL 证书(X.509),如 Let's Encrypt 或自签名证书(测试用)。
- 客户端验证服务器证书,生产环境需禁用 AllowUntrustedCertificates。
- 与 WebSocket 的联系(前文):MQTT 可通过 WebSocket(wss://)传输,共享 TLS 机制。
1.2.2 身份验证
- 用户名/密码:
- MQTT CONNECT 报文包含 ClientId、用户名和密码。
- Broker 验证客户端身份(如 EMQX 的用户认证插件)。
- 扩展认证(MQTT 5.0):
- 支持 OAuth、JWT(JSON Web Token)等高级认证。
- 使用 AUTH 报文进行动态认证。
- 实现:
- 配置 Broker(如 EMQX)的用户认证(如 emqx_auth_username)。
- 客户端使用 MQTTnet 设置 Credentials(用户名/密码或 JWT)。
- 与 WebSocket 的对比(前文):WebSocket 使用 HTTP 握手的 JWT,MQTT 直接在 CONNECT 报文认证。
1.2.3 访问控制(ACL)
- 原理:
- Broker 使用访问控制列表(ACL)限制客户端的发布/订阅权限。
- 基于 ClientId、用户名或主题(如 /neuron/s7-1200/plate)。
- 支持通配符(如 #、 +),如 /neuron/# 允许所有子主题。
- 实现:
- EMQX 使用 emqx_authz 插件配置 ACL。
- 示例:允许用户 plc_user 发布到 /neuron/s7-1200/plate,订阅 /neuron/#。
- 与 WebSocket 的对比:WebSocket 使用 Origin 检查和 IP 限制,MQTT 的 ACL 更细粒度。
1.2.4 数据完整性
- 原理:
- TLS 确保消息完整性,防止篡改。
- QoS 1/2 提供可靠传输,防止消息丢失或重复。
- 实现:
- 使用 MqttApplicationMessageBuilder 设置 QoS。
- 验证 Broker 的 TLS 配置(如 AES-GCM 加密)。
1.2.5 会话管理
- Clean Session:
- CleanSession=true:连接断开后清除会话状态。
- CleanSession=false:保留会话,断开重连后恢复订阅和未确认消息。
- Will Message:
- 客户端断开异常时,Broker 发布遗嘱消息(如通知设备离线)。
- 实现:
- MQTTnet 设置 ClientOptions 的 CleanSession 和 WillMessage。
1.2.6 其他安全措施
- 消息大小限制:Broker 限制单条消息大小,防止 DoS 攻击。
- 连接限制:限制每个客户端的连接数,防止洪泛。
- 主题隔离:使用命名空间(如 /tenant/neuron/plate)隔离不同用户。
- 心跳:MQTT 的 KeepAlive 机制检测连接状态,防止伪造。
1.3 与上下文的联系
- TCP(前文):MQTT 基于 TCP,依赖三次握手和可靠性。
- UDP:MQTT 不使用 UDP,QUIC 提供类似低延迟支持。
- WebSocket(前文):MQTT 可通过 WebSocket(wss://)传输,共享 TLS 和认证机制。
- QUIC(前文):QUIC 支持 WebSocket,可间接承载 MQTT,降低延迟。
- S7.NET(前文):S7-1200 支持 LMQTT_Client,S7-200 SMART 需 C# 桥接 MQTT。
- HTTP:MQTT 数据可通过 HTTP API 暴露,结合 WebSocket。
- ICMP:Ping 测试 Broker 和 PLC 连通性。
- 串口/USB:S7-200 SMART 支持串口,MQTT 可传输串口数据。
- 车牌识别(前文):MQTT 发布 PLC 读取的车牌数据,适合云端监控。
1.4 跨平台注意事项
- .NET 8:使用 MQTTnet(Install-Package MQTTnet),支持 Windows、Linux、macOS。
- Broker:EMQX、Mosquitto,配置 TLS 和 ACL。
- 端口:开放 1883(TCP)、8883(TLS)、8080/8081(WebSocket)。
- 证书:生产环境使用 Let's Encrypt,测试用自签名证书。
2. C# 实现安全的 MQTT 通信(集成 S7.NET)
2.1 原理使用 MQTTnet 实现安全的 MQTT 客户端,结合 S7.NET 读取 S7-1200/S7-200 SMART 数据,发布到 EMQX Broker,支持以下安全机制:
- TLS:使用 mqtts:// 加密通信。
- 用户名/密码:验证客户端身份。
- ACL:限制主题访问。
- QoS:确保消息可靠性。
- WebSocket/QUIC:通过 WebSocket(wss://)传输 MQTT,结合 QUIC 支持低延迟。
2.2 代码示例以下是一个 WinForm 程序,集成 S7.NET 和 MQTTnet,读取 PLC 数据,发布到 MQTT Broker,并通过 QUIC WebSocket 广播。
2.2.1 项目设置
- 创建 WinForm 项目:dotnet new winforms -n MqttPlc.
- 安装 NuGet 包:
- S7netplus:dotnet add package S7netplus.
- MQTTnet:dotnet add package MQTTnet.
- 配置 PLC(参考前文):
- S7-1200:DB1(DBB0 状态,DBD4 置信度,DBB10 车牌号),IP 192.168.0.1.
- S7-200 SMART:V 区(VB0 状态,VD4 置信度,VB10 车牌号),IP 192.168.0.2.
- 配置 EMQX:
- 启用 TLS(端口 8883),导入 SSL 证书。
- 配置 ACL:允许用户 plc_user 发布 /neuron/s7-1200/plate,订阅 /neuron/#.
- 用户认证:plc_user/password123.
- 配置 QUIC WebSocket 服务端(参考前文,wss://localhost:5001/ws/plate)。
2.2.2 WinForm 客户端代码csharp
using MQTTnet;
using MQTTnet.Client;
using S7.Net;
using System;
using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Channels;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace MqttPlc
{
public partial class Form1 : Form
{
private readonly ConcurrentDictionary<string, Plc> plcs = new();
private readonly Channel<(string, Action<Plc>)> requestQueue = Channel.CreateUnbounded<(string, Action<Plc>)>();
private IMqttClient mqttClient;
private ClientWebSocket quicWebSocket;
private TextBox textBoxIp1200, textBoxIp200Smart, textBoxDb1200, textBoxDb200Smart, textBoxMqttBroker, textBoxLog;
private Button btnConnectPlc, btnSync, btnConnectMqtt, btnConnectQuic;
private Timer timer;
public Form1()
{
InitializeComponent();
InitializeUI();
StartRequestProcessor();
}
private void InitializeUI()
{
textBoxIp1200 = new TextBox { Left = 20, Top = 20, Width = 200, Text = "192.168.0.1" };
textBoxIp200Smart = new TextBox { Left = 20, Top = 60, Width = 200, Text = "192.168.0.2" };
textBoxDb1200 = new TextBox { Left = 20, Top = 100, Width = 200, Text = "1" };
textBoxDb200Smart = new TextBox { Left = 20, Top = 140, Width = 200, Text = "1" };
textBoxMqttBroker = new TextBox { Left = 20, Top = 180, Width = 200, Text = "mqtts://192.168.10.174:8883" };
btnConnectPlc = new Button { Text = "连接 PLC", Left = 20, Top = 220, Width = 100 };
btnSync = new Button { Text = "同步数据", Left = 130, Top = 220, Width = 100 };
btnConnectMqtt = new Button { Text = "连接 MQTT", Left = 240, Top = 220, Width = 100 };
btnConnectQuic = new Button { Text = "连接 QUIC", Left = 350, Top = 220, Width = 100 };
textBoxLog = new TextBox { Left = 20, Top = 260, Width = 500, Height = 200, Multiline = true, ScrollBars = ScrollBars.Vertical };
Controls.AddRange(new Control[] { textBoxIp1200, textBoxIp200Smart, textBoxDb1200, textBoxDb200Smart, textBoxMqttBroker, btnConnectPlc, btnSync, btnConnectMqtt, btnConnectQuic, textBoxLog });
btnConnectPlc.Click += BtnConnectPlc_Click;
btnSync.Click += BtnSync_Click;
btnConnectMqtt.Click += BtnConnectMqtt_Click;
btnConnectQuic.Click += BtnConnectQuic_Click;
timer = new Timer { Interval = 2000 };
timer.Tick += async (s, e) => await SyncPlcDataAsync();
}
private async void BtnConnectPlc_Click(object sender, EventArgs e)
{
try
{
if (!plcs.ContainsKey("S7-1200"))
{
var plc1200 = new Plc(CpuType.S71200, textBoxIp1200.Text, 0, 0);
plcs.TryAdd("S7-1200", plc1200);
await Task.Run(() => plc1200.Open());
if (plc1200.IsConnected)
textBoxLog.AppendText($"已连接到 S7-1200: {textBoxIp1200.Text}\r\n");
else
textBoxLog.AppendText("S7-1200 连接失败\r\n");
}
if (!plcs.ContainsKey("S7-200 SMART"))
{
var plc200Smart = new Plc(CpuType.S71200, textBoxIp200Smart.Text, 0, 0);
plcs.TryAdd("S7-200 SMART", plc200Smart);
await Task.Run(() => plc200Smart.Open());
if (plc200Smart.IsConnected)
textBoxLog.AppendText($"已连接到 S7-200 SMART: {textBoxIp200Smart.Text}\r\n");
else
textBoxLog.AppendText("S7-200 SMART 连接失败\r\n");
}
btnConnectPlc.Text = plcs.Count > 0 ? "断开 PLC" : "连接 PLC";
timer.Enabled = plcs.Count > 0;
}
catch (Exception ex)
{
textBoxLog.AppendText($"PLC 连接错误: {ex.Message}\r\n");
}
}
private async void BtnSync_Click(object sender, EventArgs e)
{
await SyncPlcDataAsync();
}
private async Task SyncPlcDataAsync()
{
if (!plcs.TryGetValue("S7-1200", out var plc1200) || !plcs.TryGetValue("S7-200 SMART", out var plc200Smart) || !plc1200.IsConnected || !plc200Smart.IsConnected)
{
textBoxLog.AppendText("请先连接两个 PLC\r\n");
return;
}
try
{
int db1200 = int.Parse(textBoxDb1200.Text);
int db200Smart = int.Parse(textBoxDb200Smart.Text);
// 并行读取 S7-1200 数据
var readTasks = new[]
{
Task.Run(() => plc1200.Read(DataType.DataBlock, db1200, 0, VarType.Byte, 1)),
Task.Run(() => plc1200.Read(DataType.DataBlock, db1200, 4, VarType.Real, 1)),
Task.Run(() => plc1200.Read(DataType.DataBlock, db1200, 10, VarType.String, 10))
};
var results = await Task.WhenAll(readTasks);
var status = Convert.ToByte(results[0]);
var confidence = Convert.ToSingle(results[1]);
var plateNumber = results[2].ToString();
// 写入 S7-200 SMART
await Task.Run(() =>
{
plc200Smart.Write(DataType.DataBlock, db200Smart, 0, status);
plc200Smart.Write(DataType.DataBlock, db200Smart, 4, confidence);
plc200Smart.Write(DataType.DataBlock, db200Smart, 10, plateNumber);
});
// 发布到 MQTT
if (mqttClient?.IsConnected == true)
{
var plateData = new { PlateNumber = plateNumber, Confidence = confidence, Status = status, Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") };
var message = new MqttApplicationMessageBuilder()
.WithTopic("/neuron/s7-1200/plate")
.WithPayload(Encoding.UTF8.GetBytes(JsonSerializer.Serialize(plateData)))
.WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtLeastOnce)
.WithRetainFlag(true) // 保留消息
.Build();
await mqttClient.PublishAsync(message);
textBoxLog.AppendText($"MQTT 发布: {JsonSerializer.Serialize(plateData)}\r\n");
}
// 发布到 QUIC WebSocket
if (quicWebSocket?.State == WebSocketState.Open)
{
var plateData = new { PlateNumber = plateNumber, Confidence = confidence, Status = status, Timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") };
var message = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(plateData));
await quicWebSocket.SendAsync(new ArraySegment<byte>(message), WebSocketMessageType.Text, true, CancellationToken.None);
textBoxLog.AppendText($"QUIC WebSocket 发布: {JsonSerializer.Serialize(plateData)}\r\n");
}
textBoxLog.AppendText($"同步数据 - 状态: {status}, 置信度: {confidence:F2}, 车牌号: {plateNumber}\r\n");
}
catch (Exception ex)
{
textBoxLog.AppendText($"同步错误: {ex.Message}\r\n");
}
}
private async void BtnConnectMqtt_Click(object sender, EventArgs e)
{
try
{
if (mqttClient == null || !mqttClient.IsConnected)
{
var factory = new MqttFactory();
mqttClient = factory.CreateMqttClient();
var options = new MqttClientOptionsBuilder()
.WithTcpServer("192.168.10.174", 8883)
.WithTls(tlsOptions =>
{
tlsOptions.SslProtocol = System.Security.Authentication.SslProtocols.Tls13;
tlsOptions.IgnoreCertificateChainErrors = true; // 测试用,生产环境禁用
tlsOptions.IgnoreCertificateRevocationErrors = true;
})
.WithCredentials("plc_user", "password123")
.WithClientId("PlcClient-" + Guid.NewGuid().ToString())
.WithWillMessage(new MqttApplicationMessageBuilder()
.WithTopic("/neuron/disconnect")
.WithPayload(Encoding.UTF8.GetBytes("PLC Client Disconnected"))
.Build())
.Build();
await mqttClient.ConnectAsync(options);
btnConnectMqtt.Text = "断开 MQTT";
textBoxLog.AppendText($"已连接到 MQTT Broker: {textBoxMqttBroker.Text}\r\n");
// 订阅主题
await mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic("/neuron/#").Build());
mqttClient.ApplicationMessageReceivedAsync += e =>
{
textBoxLog.Invoke((Action)(() => textBoxLog.AppendText(
$"MQTT 收到: 主题={e.ApplicationMessage.Topic}, 消息={Encoding.UTF8.GetString(e.ApplicationMessage.PayloadSegment)}\r\n")));
return Task.CompletedTask;
};
}
else
{
await mqttClient.DisconnectAsync();
mqttClient = null;
btnConnectMqtt.Text = "连接 MQTT";
textBoxLog.AppendText("已断开 MQTT\r\n");
}
}
catch (Exception ex)
{
textBoxLog.AppendText($"MQTT 连接错误: {ex.Message}\r\n");
}
}
private async void BtnConnectQuic_Click(object sender, EventArgs e)
{
try
{
if (quicWebSocket == null || quicWebSocket.State != WebSocketState.Open)
{
quicWebSocket = new ClientWebSocket();
quicWebSocket.Options.AddSubProtocol("websocket");
quicWebSocket.Options.ClientCertificates = new System.Security.Cryptography.X509Certificates.X509CertificateCollection();
await quicWebSocket.ConnectAsync(new Uri("wss://localhost:5001/ws/plate"), CancellationToken.None);
btnConnectQuic.Text = "断开 QUIC";
textBoxLog.AppendText("已连接到 QUIC WebSocket: wss://localhost:5001/ws/plate\r\n");
_ = Task.Run(() => ReceiveQuicMessagesAsync(quicWebSocket));
}
else
{
await quicWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "客户端关闭", CancellationToken.None);
quicWebSocket = null;
btnConnectQuic.Text = "连接 QUIC";
textBoxLog.AppendText("已断开 QUIC WebSocket\r\n");
}
}
catch (Exception ex)
{
textBoxLog.AppendText($"QUIC WebSocket 错误: {ex.Message}\r\n");
}
}
private async Task ReceiveQuicMessagesAsync(ClientWebSocket webSocket)
{
var buffer = new byte[1024 * 4];
try
{
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text)
{
string message = Encoding.UTF8.GetString(buffer, 0, result.Count);
textBoxLog.Invoke((Action)(() => textBoxLog.AppendText($"QUIC WebSocket 收到: {message}\r\n")));
}
else if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "服务端关闭", CancellationToken.None);
break;
}
}
}
catch (Exception ex)
{
textBoxLog.Invoke((Action)(() => textBoxLog.AppendText($"QUIC WebSocket 接收错误: {ex.Message}\r\n")));
}
}
private async void StartRequestProcessor()
{
var semaphore = new SemaphoreSlim(1, 1);
while (await requestQueue.Reader.WaitToReadAsync())
{
if (requestQueue.Reader.TryRead(out var request))
{
await semaphore.WaitAsync();
try
{
if (plcs.TryGetValue(request.Item1, out var plc) && plc.IsConnected)
{
await Task.Run(() => request.Item2(plc));
}
}
catch (Exception ex)
{
textBoxLog.Invoke((Action)(() => textBoxLog.AppendText($"请求处理错误: {ex.Message}\r\n")));
}
finally
{
semaphore.Release();
}
}
}
}
protected override void OnFormClosing(FormClosingEventArgs e)
{
timer?.Stop();
foreach (var plc in plcs.Values)
plc.Close();
mqttClient?.DisconnectAsync().GetAwaiter().GetResult();
quicWebSocket?.CloseAsync(WebSocketCloseStatus.NormalClosure, "窗体关闭", CancellationToken.None).GetAwaiter().GetResult();
base.OnFormClosing(e);
}
}
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
2.2.3 EMQX 配置
- TLS:
- 生成自签名证书(测试用):bash
openssl req -x509 -newkey rsa:2048 -keyout emqx.key -out emqx.crt -days 365 -nodes - 配置 emqx.conf:conf
listener.ssl.external = 8883 listener.ssl.external.keyfile = /path/to/emqx.key listener.ssl.external.certfile = /path/to/emqx.crt
- 生成自签名证书(测试用):bash
- ACL(emqx_authz.conf):conf
{allow, {user, "plc_user"}, publish, ["/neuron/s7-1200/plate"]}. {allow, {user, "plc_user"}, subscribe, ["/neuron/#"]}. {deny, all}. - 用户认证(emqx_authn.conf):conf
authentication = [ {backend = "builtin", mechanism = "password_based", user = [{username = "plc_user", password = "password123"}]} ]
2.2.4 QUIC WebSocket 服务端参考前文(QUIC WebSocket 服务端代码),确保启用 HTTP/3 和 SSL 证书(certificate.pfx)。
2.3 解释
- 安全机制:
- TLS:MQTT 使用 mqtts://192.168.10.174:8883,QUIC WebSocket 使用 wss://localhost:5001/ws/plate。
- 用户名/密码:MQTT 客户端使用 plc_user/password123 认证。
- ACL:EMQX 限制 plc_user 发布 /neuron/s7-1200/plate,订阅 /neuron/#。
- QoS:使用 QoS 1,确保可靠传输。
- Will Message:客户端异常断开时发布 /neuron/disconnect。
- S7.NET 集成:
- 读取 S7-1200 DB1 数据(状态、置信度、车牌号)。
- 写入 S7-200 SMART V 区,同步数据。
- MQTT 发布:
- 发布 JSON 格式的车牌数据到 /neuron/s7-1200/plate。
- 订阅 /neuron/#,接收控制指令。
- QUIC WebSocket:
- 广播 PLC 数据到前端,结合 QUIC 低延迟。
- 多线程优化:
- 使用 ConcurrentDictionary 管理 PLC 实例。
- Channel 和 SemaphoreSlim 处理并发请求。
- Task.WhenAll 并行读取数据。
- 测试:
- 配置 EMQX(TLS、ACL、认证)。
- 运行 QUIC WebSocket 服务端(dotnet run)。
- 运行 WinForm 程序,连接 PLC、MQTT 和 QUIC WebSocket。
- 使用 MQTTX 订阅 /neuron/s7-1200/plate,验证数据。
- 使用 Wireshark(tcp.port == 8883, udp.port == 443)捕获通信。
- 测试 ACL(使用错误用户登录,应失败)。
3. MQTT 在车牌识别场景的应用3.1 原理在车牌识别物联网项目中,MQTT 提供可靠的发布/订阅机制:
- S7-1200:采集车牌数据(DB1),通过 LMQTT_Client 或 C# 发布到 MQTT。
- S7-200 SMART:接收数据(V 区),通过 C# 桥接。
- Broker:EMQX 路由消息,支持 TLS 和 ACL。
- 客户端:订阅 /neuron/s7-1200/plate,实时显示车牌数据。
- 安全:
- TLS 加密传输。
- 用户名/密码和 ACL 限制访问。
- QoS 1/2 确保可靠性。
3.2 与 WebSocket/QUIC 的对比
- WebSocket(前文):
- 全双工,适合点对点实时通信。
- TLS 和 JWT 认证,Origin 检查。
- 需手动管理连接,适合简单场景。
- MQTT:
- 发布/订阅,解耦发布者和订阅者,适合大规模物联网。
- TLS、用户名/密码、ACL 提供细粒度安全。
- 支持 QoS,适应不可靠网络。
- QUIC(前文):
- 低延迟,支持 WebSocket,可承载 MQTT。
- 内置 TLS 1.3,适合高实时性场景。
3.3 代码增强(心跳和消息大小限制)以下为 MQTT 客户端添加心跳和消息大小限制:csharp
private async Task SyncPlcDataAsync()
{
// ... 现有代码 ...
// 心跳机制
if (mqttClient?.IsConnected == true)
{
var heartbeat = new MqttApplicationMessageBuilder()
.WithTopic("/neuron/heartbeat")
.WithPayload(Encoding.UTF8.GetBytes(DateTime.Now.ToString()))
.WithQualityOfServiceLevel(MQTTnet.Protocol.MqttQualityOfServiceLevel.AtMostOnce)
.Build();
await mqttClient.PublishAsync(heartbeat);
textBoxLog.AppendText("MQTT 心跳发送\r\n");
}
// 消息大小限制
if (JsonSerializer.Serialize(plateData).Length > 1024)
{
textBoxLog.AppendText("消息过大,取消发布\r\n");
return;
}
// ... 发布代码 ...
}
- 心跳:每 2 秒发布 /neuron/heartbeat,检测连接状态。
- 消息大小限制:限制 JSON 数据为 1KB,防止 DoS。
4. 总结
- MQTT 安全机制原理:
- TLS:mqtts:// 加密通信,防止窃听和篡改。
- 身份验证:用户名/密码,MQTT 5.0 支持 JWT/OAuth。
- ACL:限制主题访问,细粒度控制。
- QoS:确保消息可靠性(QoS 1/2)。
- 会话管理:Clean Session 和 Will Message 处理断连。
- C# 实现:
- S7.NET:读取 S7-1200 数据,写入 S7-200 SMART。
- MQTTnet:发布车牌数据到 EMQX,支持 TLS 和 ACL。
- QUIC WebSocket:广播数据,结合 QUIC 低延迟。
- 多线程:使用 ConcurrentDictionary、Channel 和 SemaphoreSlim 优化并发。
- 与上下文的联系:
- TCP:MQTT 和 S7.NET 依赖 TCP。
- UDP:QUIC 基于 UDP,支持 WebSocket。
- WebSocket:MQTT 可通过 wss:// 传输,共享 TLS。
- QUIC:承载 WebSocket,低延迟传输 PLC 数据。
- S7.NET:S7-1200 支持 LMQTT_Client,S7-200 SMART 需桥接。
- HTTP:MQTT 数据可通过 HTTP API 暴露。
- ICMP:Ping 测试 Broker 和 PLC。
- 串口/USB:S7-200 SMART 支持串口,MQTT 传输数据。
- 车牌识别:MQTT 发布 PLC 数据,适合云端监控。
- 跨平台:.NET 8 支持 Windows、Linux、macOS,需开放 8883 和 443 端口。
测试建议:
- 配置 EMQX(TLS、ACL、认证),S7-1200(DB1),S7-200 SMART(V 区)。
- 运行 QUIC WebSocket 服务端(dotnet run)。
- 运行 WinForm 程序,连接 PLC、MQTT 和 QUIC WebSocket。
- 使用 MQTTX 订阅 /neuron/s7-1200/plate,验证数据。
- 使用 Wireshark(tcp.port == 8883, udp.port == 443)捕获通信。
- 测试 ACL(错误用户应失败)、TLS(禁用证书验证应失败)。
如需进一步优化(如双向 TLS、分布式 Broker 部署)或特定场景(如大规模 PLC 数据采集),请告知!
更多推荐



所有评论(0)