C#应用 - 实现MQTT协议

前言

本文部分实现了MQTT协议V3.1.1版本。只关注协议核心部分,用尽量少的代码写一个简化版的MqttBroker,能够与第三方MQTT工具进行通信。

1,简介

简化版实现了Broker的核心功能:

  • CONNECT/CONNACK:客户端连接管理
  • PUBLISH:消息发布
  • SUBSCRIBE/SUBACK:主题订阅
  • PINGREQ/PINGRESP:心跳检测
  • DISCONNECT:客户端断开

2,设计

2.1 基础类型

namespace 简单MqttBroker
{
    /// <summary>
    /// MQTT控制报文类型
    /// </summary>
    public enum ControlPacketType : byte
    {
        CONNECT = 1,    // 客户端连接请求
        CONNACK = 2,    // 连接确认
        PUBLISH = 3,    // 发布消息
        PUBACK = 4,     // 发布确认(QoS 1)
        PUBREC = 5,     // 发布收到(QoS 2)
        PUBREL = 6,     // 发布释放(QoS 2)
        PUBCOMP = 7,    // 发布完成(QoS 2)
        SUBSCRIBE = 8,  // 订阅请求
        SUBACK = 9,     // 订阅确认
        UNSUBSCRIBE = 10, // 取消订阅
        UNSUBACK = 11,  // 取消订阅确认
        PINGREQ = 12,   // 心跳请求
        PINGRESP = 13,  // 心跳响应
        DISCONNECT = 14 // 断开连接
    }
    
    /// <summary>
    /// MQTT客户端会话状态
    /// </summary>
    public enum MqttConnectionStatus
    {
        Disconnected,   // 未连接
        Connected      // 已连接
    }
    
    /// <summary>
    /// MQTT消息
    /// </summary>
    public class MqttMessage
    {
        public string Topic { get; set; }     // 主题
        public byte[] Payload { get; set; }    // 消息内容
        public bool Retain { get; set; }       // 是否保留消息
        public byte QosLevel { get; set; }     // QoS级别(0,1,2)
    }
    
    /// <summary>
    /// MQTT客户端会话信息
    /// </summary>
    public class ClientSession
    {
        public string ClientId { get; set; }                  // 客户端ID
        public TcpClient TcpClient { get; set; }              // TCP客户端连接
        public NetworkStream NetworkStream { get; set; }      // 网络流
        public MqttConnectionStatus Status { get; set; } = MqttConnectionStatus.Disconnected;      // 连接状态
        public ConcurrentBag<string> SubscribedTopics { get; } = new ConcurrentBag<string>(); // 已订阅主题集合
        public DateTime LastActivityTime { get; set; } = DateTime.UtcNow;        // 最后活动时间
        public SemaphoreSlim SendLock { get; } = new(1, 1);    // 发送锁(防止多线程发送冲突)

        public ClientSession(TcpClient tcpClient)
        {
            TcpClient = tcpClient ?? throw new ArgumentNullException(nameof(tcpClient));
            NetworkStream = tcpClient.GetStream();
        }
        public void Dispose()
        {
            SendLock.Dispose();
            NetworkStream.Dispose();
            TcpClient.Dispose();
        }
    }
}

2.2 核心类型

namespace 简单MqttBroker
{
    /// <summary>
    /// MQTT Broker实现
    /// </summary>
    public class MqttBroker : IDisposable
    {
        private const byte PROTOCOL_LEVEL = 0x04; // MQTT 3.1.1
        private static readonly byte[] PROTOCOL_NAME = Encoding.ASCII.GetBytes("MQTT");

        private readonly TcpListener _listener;                            // TCP监听器
        private readonly ConcurrentDictionary<string, ClientSession> _clientSessions = new(); // 客户端会话字典
        private readonly ConcurrentDictionary<string, List<ClientSession>> _topicSubscriptions = new(); // 主题订阅字典
        private readonly ConcurrentBag<MqttMessage> _retainedMessages = new(); // 保留消息集合
        private readonly CancellationTokenSource _cts = new();             // 取消令牌源
        private readonly ThreadPool _workerThreadPool;                     // 工作线程池
        private bool _disposed;                                            // 是否已释放资源

        private const int MaxWorkerThreads = 100;                           // 最大工作线程数
        private const int KeepAliveCheckInterval = 60000;                  // 保持连接检查间隔(ms)

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="port">监听端口</param>
        public MqttBroker(int port = 1883)
        {
            // 配置线程池
            _workerThreadPool = new ThreadPool(MaxWorkerThreads);

            // 初始化TCP监听器
            _listener = new TcpListener(IPAddress.Any, port);
        }

        /// <summary>
        /// 启动Broker
        /// </summary>
        public void Start()
        {
            _listener.Start();
            Debug.WriteLine($"MQTT Broker started on port {((IPEndPoint)_listener.LocalEndpoint).Port}");

            // 启动客户端接收任务
            _workerThreadPool.QueueUserWorkItem(_ => AcceptClientsAsync().ConfigureAwait(false));

            // 启动不活跃连接清理任务
            _workerThreadPool.QueueUserWorkItem(_ => CleanInactiveSessionsAsync().ConfigureAwait(false));
        }

        /// <summary>
        /// 异步接受客户端连接
        /// </summary>
        private async Task AcceptClientsAsync()
        {
            try
            {
                while (!_cts.IsCancellationRequested)
                {
                    TcpClient tcpClient = await _listener.AcceptTcpClientAsync().ConfigureAwait(false);

                    // 为每个客户端连接创建独立处理任务
                    _workerThreadPool.QueueUserWorkItem(async () =>
                    {
                        await HandleClientAsync(tcpClient).ConfigureAwait(false);
                    });
                }
            }
            catch (Exception ex) when (!_cts.IsCancellationRequested)
            {
                Debug.WriteLine($"Accept clients error: {ex.Message}");
            }
        }

        /// <summary>
        /// 处理客户端连接
        /// </summary>
        private async Task HandleClientAsync(TcpClient tcpClient)
        {
            ClientSession session = new ClientSession(tcpClient);
            try
            {
                while (!_cts.IsCancellationRequested && tcpClient.Connected)
                {
                    // 接收报文
                    var packet = await ReadPacketAsync(session.NetworkStream);
                    if (packet == null)
                    {
                        break;
                    }
                    // 更新最后活动时间
                    session.LastActivityTime = DateTime.UtcNow;
                    // 处理数据包
                    await ProcessPacketAsync(session, packet).ConfigureAwait(false);
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Client {session.ClientId} error: {ex.Message}");
            }
            finally
            {
                DisconnectClient(session);
            }
        }

        /// <summary>
        /// 从网络流中读取MQTT数据包
        /// </summary>
        private async Task<byte[]> ReadPacketAsync(NetworkStream stream)
        {
            // 读取固定头(至少2字节)
            var fixedHeader = new byte[2];
            var bytesRead = await stream.ReadAsync(fixedHeader, 0, 2, _cts.Token).ConfigureAwait(false);
            if (bytesRead < 2)
            {
                return null;
            }

            // 解析剩余长度
            int remainingLength = 0;
            int multiplier = 1;
            byte digit;
            int offset = 1;

            do
            {
                digit = fixedHeader[offset++];
                remainingLength += (digit & 127) * multiplier;
                multiplier *= 128;
            } while ((digit & 128) != 0 && offset < 5);

            // 读取可变头和有效载荷
            var packet = new byte[offset + remainingLength];
            Array.Copy(fixedHeader, 0, packet, 0, offset);

            if (remainingLength > 0)
            {
                bytesRead = await stream.ReadAsync(packet, offset, remainingLength, _cts.Token).ConfigureAwait(false);
                if (bytesRead < remainingLength) return null;
            }

            return packet;
        }

        /// <summary>
        /// 处理MQTT数据包
        /// </summary>
        private async Task ProcessPacketAsync(ClientSession session, byte[] packet)
        {
            var packetType = (ControlPacketType)(packet[0] >> 4);

            switch (packetType)
            {
                case ControlPacketType.CONNECT:
                    await HandleConnectAsync(session, packet).ConfigureAwait(false);
                    break;

                case ControlPacketType.PUBLISH:
                    await HandlePublishAsync(session, packet).ConfigureAwait(false);
                    break;

                case ControlPacketType.SUBSCRIBE:
                    await HandleSubscribeAsync(session, packet).ConfigureAwait(false);
                    break;

                case ControlPacketType.PINGREQ:
                    await SendPingRespAsync(session).ConfigureAwait(false);
                    break;

                case ControlPacketType.DISCONNECT:
                    DisconnectClient(session);
                    break;
            }
        }

        /// <summary>
        /// 处理CONNECT报文
        /// </summary>
        private async Task HandleConnectAsync(ClientSession session, byte[] packet)
        {
            // 检查协议名 (偏移量4-7应为"MQTT")
            var protocolName = Encoding.UTF8.GetString(packet, 4, 4);
            if (protocolName != "MQTT")
            {
                await SendConnAck(session, 0x01); // 返回不支持协议版本
                return;
            }

            // 检查协议级别 (偏移量9)
            var protocolLevel = packet[8];
            if (protocolLevel != 0x04)
            {   // MQTT 3.1.1的协议级别是4,MQTT 5.0.0的协议级别是5
                await SendConnAck(session, 0x01);
                return;
            }

            // 处理客户端ID
            var clientIdLength = (packet[12] << 8) | packet[13];
            session.ClientId = Encoding.UTF8.GetString(packet, 14, clientIdLength);

            // 检查重复ClientID
            if (_clientSessions.ContainsKey(session.ClientId))
            {
                await SendConnAck(session, 0x02); // 0x02表示标识符被占用
                return;
            }

            // 添加到会话字典
            if (!_clientSessions.TryAdd(session.ClientId, session))
            {
                await SendConnAck(session, 0x01); // 0x01表示服务器不可用
                return;
            }
            session.Status = MqttConnectionStatus.Connected;

            // 必须返回CONNACK (Accepted)
            await SendConnAck(session, 0x00);
            Debug.WriteLine($"客户端已连接: {session.ClientId}");
        }

        private async Task SendConnAck(ClientSession session, byte returnCode)
        {
            //CONNACK格式:
            //Byte1: 固定头 (0x20)
            //Byte2: 剩余长度 (0x02)
            //Byte3: 保留位 (0x00)
            //Byte4: Return Code
            var connAck = new byte[] {
                0x20,  // CONNACK类型
                0x02,   // 剩余长度
                0x00,   // 保留位
                returnCode // 0=成功, 1-5=错误码
            };
            await SendDataAsync(session, connAck);
        }

        /// <summary>
        /// 处理PUBLISH报文
        /// </summary>
        private async Task HandlePublishAsync(ClientSession session, byte[] packet)
        {
            // 解析PUBLISH报文
            int offset = 1; // 跳过固定头

            // 读取剩余长度
            int remainingLength = 0;
            int multiplier = 1;
            byte digit;

            do
            {
                digit = packet[offset++];
                remainingLength += (digit & 127) * multiplier;
                multiplier *= 128;
            } while ((digit & 128) != 0 && offset < 5);

            // 读取主题长度(2字节)
            if (offset + 2 > packet.Length)
                throw new ArgumentException("Invalid topic length");
            int topicLength = (packet[offset] << 8) | packet[offset + 1];
            offset += 2;

            // 读取主题内容
            if (offset + topicLength > packet.Length)
                throw new ArgumentException("Topic exceeds packet length");
            string topic = Encoding.UTF8.GetString(packet, offset, topicLength);
            offset += topicLength;

            // 读取消息内容
            var payloadLength = remainingLength - offset + 2;  // 即packet.Length - offset
            var payload = new byte[payloadLength];
            Array.Copy(packet, offset, payload, 0, payloadLength);

            var qosLevel = (byte)((packet[0] & 0x06) >> 1);
            var retain = (packet[0] & 0x01) != 0;

            var message = new MqttMessage
            {
                Topic = topic,
                Payload = payload,
                QosLevel = qosLevel,
                Retain = retain
            };

            // 处理保留消息
            if (retain)
            {
                _retainedMessages.Add(message);
            }

            // 路由消息给订阅者
            _workerThreadPool.QueueUserWorkItem(async () =>
            {
                await RouteMessageAsync(message).ConfigureAwait(false);
            });
        }

        /// <summary>
        /// 路由消息给订阅者
        /// </summary>
        private async Task RouteMessageAsync(MqttMessage message)
        {
            // 获取所有匹配的订阅者
            var subscribers = _topicSubscriptions
                .Where(kv => TopicMatches(kv.Key, message.Topic))
                .SelectMany(kv => kv.Value)
                .Distinct()
                .ToList();

            // 并行发送
            var sendTasks = subscribers.Select(async sub =>
            {
                try
                {
                    await SendPublishAsync(sub, message);
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"发送到{sub.ClientId}失败: {ex.Message}");
                    DisconnectClient(sub);
                }
            });

            await Task.WhenAll(sendTasks).ConfigureAwait(false);
        }

        /// <summary>
        /// 检查主题是否匹配
        /// </summary>
        private bool TopicMatches(string subscription, string topic)
        {
            return subscription == topic; // 要支持通配符,懒得搞了
        }

        /// <summary>
        /// 发送PUBLISH报文给客户端
        /// </summary>
        private async Task SendPublishAsync(ClientSession session, MqttMessage message)
        {
            try
            {
                var topicBytes = Encoding.UTF8.GetBytes(message.Topic);
                var variableHeaderLength = 2 + topicBytes.Length;
                var remainingLength = variableHeaderLength + message.Payload.Length;

                var packet = new List<byte>();

                // 固定头
                byte fixedHeader = (byte)((byte)ControlPacketType.PUBLISH << 4);
                fixedHeader |= (byte)(message.QosLevel << 1);
                if (message.Retain) fixedHeader |= 0x01;
                packet.Add(fixedHeader);

                // 剩余长度
                packet.AddRange(EncodeRemainingLength(remainingLength));

                // 可变头
                packet.Add((byte)(topicBytes.Length >> 8));
                packet.Add((byte)topicBytes.Length);
                packet.AddRange(topicBytes);

                // 有效载荷
                packet.AddRange(message.Payload);

                // 确保线程安全发送
                await session.SendLock.WaitAsync(_cts.Token).ConfigureAwait(false);
                try
                {
                    await session.NetworkStream.WriteAsync(packet.ToArray(), 0, packet.Count, _cts.Token).ConfigureAwait(false);
                }
                finally
                {
                    session.SendLock.Release();
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine($"Send publish to {session.ClientId} error: {ex.Message}");
            }
        }

        /// <summary>
        /// 处理SUBSCRIBE报文
        /// </summary>
        private async Task HandleSubscribeAsync(ClientSession session, byte[] packet)
        {
            // 解析SUBSCRIBE报文
            int offset = 1; // 跳过固定头
            // 读取剩余长度
            int remainingLength = packet[offset++];
            // 读取Packet Identifier(2字节)
            int packetId = (packet[offset] << 8) | packet[offset + 1];
            offset += 2;

            // 读取订阅列表
            List<string> topics = new List<string>();
            List<byte> returnCodes = new List<byte>();
            while (offset < packet.Length)
            {
                // 读取主题长度
                int topicLength = (packet[offset] << 8) | packet[offset + 1];
                offset += 2;
                // 读取主题
                string topic = Encoding.UTF8.GetString(packet, offset, topicLength);
                offset += topicLength;
                // 读取请求的QoS级别(只取后2位)
                byte requestedQos = (byte)(packet[offset] & 0x03);
                offset++;

                // 检查主题有效性
                if (string.IsNullOrEmpty(topic))
                {
                    returnCodes.Add(0x80); // 0x80表示订阅失败
                    continue;
                }
                topics.Add(topic);
                
                // 添加到主题订阅字典
                _topicSubscriptions.AddOrUpdate(
                    topic,
                    new List<ClientSession> { session },
                    (_, list) => { lock (list) { list.Add(session); } return list; });

                session.SubscribedTopics.Add(topic); // 添加到会话订阅列表
                returnCodes.Add(requestedQos); // 返回实际授予的QoS
            }

            // 构建SUBACK报文
            List<byte> subAckPacket = new List<byte>
            {
                0x90,                          // SUBACK固定头
                (byte)(2 + returnCodes.Count), // 剩余长度
                (byte)(packetId >> 8),         // Packet Identifier MSB
                (byte)(packetId & 0xFF)        // Packet Identifier LSB
            };
            subAckPacket.AddRange(returnCodes); // 添加返回码列表

            await SendDataAsync(session, subAckPacket.ToArray()).ConfigureAwait(false);

            // 发送保留消息
            _workerThreadPool.QueueUserWorkItem(async () =>
            {
                await SendRetainedMessagesAsync(session, topics).ConfigureAwait(false);
            });
        }

        /// <summary>
        /// 发送保留消息给新订阅者
        /// </summary>
        private async Task SendRetainedMessagesAsync(ClientSession session, IEnumerable<string> topics)
        {
            var retainedMessages = _retainedMessages
                .Where(msg => topics.Contains(msg.Topic))
                .ToList();

            foreach (var msg in retainedMessages)
            {
                await SendPublishAsync(session, msg).ConfigureAwait(false);
            }
        }

        /// <summary>
        /// 发送PINGRESP响应
        /// </summary>
        private async Task SendPingRespAsync(ClientSession session)
        {
            var pingResp = new byte[] { 0xD0, 0x00 };
            await SendDataAsync(session, pingResp).ConfigureAwait(false);
        }

        /// <summary>
        /// 通用数据发送方法
        /// </summary>
        private async Task SendDataAsync(ClientSession session, byte[] data)
        {
            await session.SendLock.WaitAsync(_cts.Token).ConfigureAwait(false);
            try
            {
                await session.NetworkStream.WriteAsync(data, 0, data.Length, _cts.Token).ConfigureAwait(false);
            }
            finally
            {
                session.SendLock.Release();
            }
        }

        /// <summary>
        /// 断开客户端连接
        /// </summary>
        private void DisconnectClient(ClientSession session)
        {
            if (session.Status == MqttConnectionStatus.Disconnected)
            {
                return;
            }
            Debug.WriteLine($"客户端断开: {session.ClientId}");

            // 1. 从会话字典移除
            if (session.ClientId != null)
            {
                _clientSessions.TryRemove(session.ClientId, out _);
            }

            // 2. 从所有订阅中移除
            foreach (var topic in session.SubscribedTopics)
            {
                if (_topicSubscriptions.TryGetValue(topic, out var subscribers))
                {
                    lock (subscribers)  // 确保线程安全修改列表
                    {
                        subscribers.Remove(session);
                    }
                }
            }

            // 3. 释放资源
            session.Status = MqttConnectionStatus.Disconnected;
            session.Dispose();

            Debug.WriteLine($"Client disconnected: {session.ClientId}");
        }

        /// <summary>
        /// 清理不活跃会话
        /// </summary>
        private async Task CleanInactiveSessionsAsync()
        {
            while (!_cts.IsCancellationRequested)
            {
                await Task.Delay(KeepAliveCheckInterval, _cts.Token).ConfigureAwait(false);

                var now = DateTime.UtcNow;
                var inactiveSessions = _clientSessions.Values
                    .Where(s => now - s.LastActivityTime > TimeSpan.FromMinutes(5))
                    .ToList();

                foreach (var session in inactiveSessions)
                {
                    DisconnectClient(session);
                }
            }
        }

        /// <summary>
        /// 编码剩余长度字段
        /// </summary>
        private static IEnumerable<byte> EncodeRemainingLength(int length)
        {
            var bytes = new List<byte>();
            do
            {
                byte digit = (byte)(length % 128);
                length /= 128;
                if (length > 0) digit |= 0x80;
                bytes.Add(digit);
            } while (length > 0);

            return bytes;
        }

        public void Dispose()
        {
            if (_disposed) return;

            _disposed = true;
            _cts.Cancel();

            // 断开所有客户端
            foreach (var session in _clientSessions.Values)
            {
                DisconnectClient(session);
            }

            _listener.Stop();
            _workerThreadPool.Dispose();
            _cts.Dispose();

            Debug.WriteLine("MQTT Broker stopped");
        }
    }
}

MQTT是基于TCP的一个应用层协议,所以很像写一个TCP服务器。无非是接收客户端连接,然后给每个客户端分配一个线程处理消息的收发。

2.3 线程池

为了方便分配和管理线程,再写个线程池。

namespace 简单MqttBroker
{
    /// <summary>
    /// 自定义线程池实现
    /// </summary>
     public class ThreadPool : IDisposable
    {
        private readonly BlockingCollection<(Action WorkItem, ManualResetEventSlim CompletionEvent)> _workQueue = new();
        private readonly List<Thread> _workerThreads;
        private readonly CancellationTokenSource _cts = new();
        private volatile bool _disposed;
        private long _completedCount;

        public int ActiveThreads => _workerThreads.Count(t => t.IsAlive);
        public long CompletedCount => Interlocked.Read(ref _completedCount);
        public int MaxThreads { get; }

        public ThreadPool(int maxThreads)
        {
            MaxThreads = maxThreads > 0 ? maxThreads : throw new ArgumentOutOfRangeException(nameof(maxThreads));
            _workerThreads = new List<Thread>(maxThreads);

            // 预热工作线程
            for (int i = 0; i < maxThreads; i++)
            {
                var thread = new Thread(WorkerThreadProc)
                {
                    IsBackground = true,
                    Name = $"NativeThreadPool Worker #{i}"
                };
                thread.Start();
                _workerThreads.Add(thread);
            }
        }

        private void WorkerThreadProc()
        {
            while (!_disposed && !_cts.IsCancellationRequested)
            {
                try
                {
                    var (workItem, completionEvent) = _workQueue.Take(_cts.Token);
                    try
                    {
                        workItem();
                        Interlocked.Increment(ref _completedCount);
                    }
                    finally
                    {
                        completionEvent?.Set();
                    }
                }
                catch (OperationCanceledException) { /* 正常退出 */ }
                catch (Exception ex)
                {
                    Debug.WriteLine($"Worker thread error: {ex.Message}");
                }
            }
        }

        public bool QueueUserWorkItem(WaitCallback callBack, object? state = null)
        {
            if (_disposed)
            {
                throw new ObjectDisposedException(nameof(ThreadPool));
            }
            ManualResetEventSlim mre = new ManualResetEventSlim(false);

            _workQueue.Add((() => callBack(state), mre));
            return true;
        }

        public bool QueueUserWorkItem(Action workItem)
        {
            if (_disposed) throw new ObjectDisposedException(nameof(ThreadPool));
            var mre = new ManualResetEventSlim(false);

            _workQueue.Add((workItem, mre));
            return true;
        }

        public void GetAvailableThreads(out int workerThreads, out int completionPortThreads)
        {
            workerThreads = MaxThreads - ActiveThreads;
            completionPortThreads = 0; // 原生线程池不区分I/O线程
        }

        public void Dispose()
        {
            if (_disposed)
            {
                return;
            }
            _disposed = true;

            _cts.Cancel();
            _workQueue.CompleteAdding();

            foreach (var thread in _workerThreads)
            {
                if (thread.IsAlive)
                {
                    thread.Join(500); // 等待线程退出
                }
            }

            _workQueue.Dispose();
            _cts.Dispose();
        }
    }
}

3,用起来

3.1 Broker

随便创建一个项目,比如winform项目,引用上面写的简单MqttBroker,把Broker跑起来。

public partial class Form1 : Form
{
    //实现的MQTT协议版本是3.1.1
    MqttBroker broker = new MqttBroker(1883);

    public Form1()
    {
        InitializeComponent();
    }

    private void 开始_Click(object sender, EventArgs e)
    {
        broker.Start();
        开始.Enabled = false;
    }
}

3.2 Client

市面上Client很多,我选了两种来测试,一种是现成的客户端MQTTX,另一种是引用MQTTnet自己写一个。然后让几个客户端订阅同一个主题相互通信。

3.2.1 MQTTX

打开MQTTX,新建连接,然后订阅主题abc。

3.2.2 MQTTnet

新建一个winform项目,nuget安装MQTTnet

public partial class Form1 : Form
{
    IMqttClient mqttClient = new MqttFactory().CreateMqttClient();
    IMqttClient mqttClient2 = new MqttFactory().CreateMqttClient();

    public Form1()
    {
        InitializeComponent();
    }

    private async void 开始Client1_Click(object sender, EventArgs e)
    {
        try
        {
            开始Client1.Enabled = false;
            MqttClientOptionsBuilder optionbuilder = new MqttClientOptionsBuilder()
                .WithClientId("ccclient1")
                .WithTcpServer("127.0.0.1", 1883)
                .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311);
            MqttClientConnectResult ret = await mqttClient.ConnectAsync(optionbuilder.Build());
            SetString($"Client1连接:{ret.ResultCode}\r\n");
        }
        catch(Exception ex)
        {
            SetString($"Client1连接:{ex.Message}\r\n");
        }
        finally
        {
            开始Client1.Enabled = true;
        }
    }

    private async void Client1发布_Click(object sender, EventArgs e)
    {
        if (mqttClient.IsConnected)
        {
            MqttClientPublishResult ret = await mqttClient.PublishAsync(new MqttApplicationMessage()
            {
                Topic = "abc",
            	PayloadSegment = Encoding.UTF8.GetBytes($"我是Client1,现在时间是{DateTime.Now}")
			}, CancellationToken.None);
            SetString($"Client1发布:{ret.IsSuccess}\r\n");
        }
    }

    private async void 开始Client2_Click(object sender, EventArgs e)
    {
        try
        {
            开始Client2.Enabled = false;
            MqttClientOptionsBuilder optionbuilder = new MqttClientOptionsBuilder()
                .WithClientId("ccclient2")
                .WithTcpServer("127.0.0.1", 1883)
                .WithProtocolVersion(MQTTnet.Formatter.MqttProtocolVersion.V311);
            MqttClientConnectResult ret = await mqttClient2.ConnectAsync(optionbuilder.Build());
            SetString($"Client2连接:{ret.ResultCode}\r\n");
        }
        catch (Exception ex)
        {
            SetString($"Client2连接:{ex.Message}\r\n");
        }
        finally
        {
            开始Client2.Enabled = true;
        }
    }

    private async void Client2订阅_Click(object sender, EventArgs e)
    {
        if (mqttClient2.IsConnected)
        {
            MqttClientSubscribeResult ret = await mqttClient2.SubscribeAsync(new MqttClientSubscribeOptions()
            {
                TopicFilters = new List<MqttTopicFilter>
                {
                    new MqttTopicFilter()
                    {
                        Topic = "abc"
                    }
                }
            });
            SetString($"Client2订阅:{string.Join(',', ret.Items.Select(i => i.ResultCode))}\r\n");
            mqttClient2.ApplicationMessageReceivedAsync += MqttClient2_ApplicationMessageReceivedAsync;
        }
    }

    private async Task MqttClient2_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)
    {
        var msg = arg.ApplicationMessage;
        var topic = msg.Topic;
        var payload = msg.ConvertPayloadToString();
        SetString($"收到消息 clientId:{arg.ClientId}, topic:{topic}, payload:{payload}\r\n");
    }

    private void SetString(string str)
    {
        if (this.InvokeRequired)
        {
            this.Invoke(new Action(() => textBox1.AppendText(str)));
        }
        else
        {
            textBox1.AppendText(str);
        }
    }
}

跑起来,然后开始Client1,开始Client2,Client2订阅。

3.3 收发消息

目前的状态共有3个客户端连接到Broker,1号是MQTTX(mqttx_d6bcd3f7),2号是Client1(ccclient1),3号是Client2(ccclient2)。其中mqttx_d6bcd3f7和ccclient2订阅了主题abc。然后分别用mqttx_d6bcd3f7和ccclient1向主题abc发送消息,mqttx_d6bcd3f7和ccclient2可以收到消息并打印出来。

没毛病,符合预期。

如遇问题,可参考MQTT 3.1.1协议规范 https://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html

posted @ 2025-04-18 15:58  tossorrow  阅读(819)  评论(0)    收藏  举报