MQTT之NanoSDK-Windows篇(二)

一、开篇

  上文MQTT之NanoSDK-Windows篇(一) - !>Mon<! - 博客园,简单使用NanoSDK示例与NanoMQ进行发布和订阅的测试。本篇主要内容是关于基于NanoSDK封装的库,在C#端应用,使用也非常简单。可以前往uncle-fang-zh/MQ_Client_CSharp: NanoSDK开发包实例,开发语言C#,下载示例配合服务端进行简单的发布和订阅测试。注意当前编译的dll,指定在x86平台使用并先开启服务端程序。

二、演示

1

三、主要流程

  - 连接本地 Broker(ClientID: `csharp_client`)
  - 订阅主题 `test/topic`(QoS 1)
  - 连续发布 10 条消息到主题 `world`(QoS 1),间隔 1 秒
  - 按任意键退出并断开连接

四、相关事件

  - `Connected` / `Disconnected` - 连接状态变化
  - `MessageReceived` - 收到消息
  - `MessagePublished` - 发布完成回调
  - `Subscribed` - 订阅确认回调

五、开发调用

  将FileName.cs和NativeMethods.cs复制到工程;将WinNanoSDK.dll、nng.dll复制到可执行目录下

using System;
using System.Runtime.InteropServices;
using System.Text;


namespace NanoMQTT
{
    /// <summary>
    /// MQTT 客户端事件参数
    /// </summary>
    public class MQTTMessageEventArgs : EventArgs
    {
        public string Topic { get; set; }
        public byte[] Payload { get; set; }
        public byte Qos { get; set; }
        public string PayloadAsString => Encoding.UTF8.GetString(Payload);
    }

    public class MQTTConnectEventArgs : EventArgs
    {
        public int Reason { get; set; }
        public bool Success => Reason == 0;
    }

    public class MQTTDisconnectEventArgs : EventArgs
    {
        public int Reason { get; set; }
    }

    public class MQTTPublishEventArgs : EventArgs
    {
        public uint MessageId { get; set; }
        public int Result { get; set; }
        public bool Success => Result == 0;
    }

    public class MQTTSubscribeEventArgs : EventArgs
    {
        public byte[] ReasonCodes { get; set; }
        public bool Success => ReasonCodes != null && ReasonCodes.Length > 0 && ReasonCodes[0] < 0x80;
    }

    /// <summary>
    /// NanoSDK MQTT 客户端 (.NET 封装)
    /// </summary>
    public class NanoMQTTClient : IDisposable
    {
        private IntPtr _handle;
        private bool _disposed = false;
        private GCHandle _gcHandle;

        // 关键:保存委托引用,防止 GC 回收
        private NativeMethods.ConnectCallback _connectCallback;
        private NativeMethods.DisconnectCallback _disconnectCallback;
        private NativeMethods.MessageCallback _messageCallback;
        private NativeMethods.PublishCallback _publishCallback;
        private NativeMethods.SubscribeCallback _subscribeCallback;

        public event EventHandler<MQTTConnectEventArgs> Connected;
        public event EventHandler<MQTTDisconnectEventArgs> Disconnected;
        public event EventHandler<MQTTMessageEventArgs> MessageReceived;
        public event EventHandler<MQTTPublishEventArgs> MessagePublished;
        public event EventHandler<MQTTSubscribeEventArgs> Subscribed;

        public bool IsConnected { get; private set; }

        public NanoMQTTClient()
        {
            _handle = NativeMethods.mqtt_client_create();
            if (_handle == IntPtr.Zero)
                throw new Exception("Failed to create MQTT client");

            // 分配 GCHandle 防止对象被回收
            _gcHandle = GCHandle.Alloc(this);

            // 创建委托实例并保存到字段
            _connectCallback = OnConnectCallback;
            _disconnectCallback = OnDisconnectCallback;
            _messageCallback = OnMessageCallback;
            _publishCallback = OnPublishCallback;
            _subscribeCallback = OnSubscribeCallback;

            RegisterCallbacks();
        }

        private void RegisterCallbacks()
        {
            IntPtr ptr = GCHandle.ToIntPtr(_gcHandle);

            // 传递保存的委托,而不是直接传递方法
            NativeMethods.mqtt_client_set_connect_callback(_handle, _connectCallback, ptr);
            NativeMethods.mqtt_client_set_disconnect_callback(_handle, _disconnectCallback, ptr);
            NativeMethods.mqtt_client_set_message_callback(_handle, _messageCallback, ptr);
            NativeMethods.mqtt_client_set_publish_callback(_handle, _publishCallback, ptr);
            NativeMethods.mqtt_client_set_subscribe_callback(_handle, _subscribeCallback, ptr);
        }

        // ==================== 静态回调方法 ====================
        // 所有回调都添加 try-catch 防止异常崩溃

        private static void OnConnectCallback(int reason, IntPtr userData)
        {
            try
            {
                if (userData == IntPtr.Zero) return;

                var gcHandle = GCHandle.FromIntPtr(userData);
                var client = gcHandle.Target as NanoMQTTClient;
                if (client == null || client._disposed) return;

                client.IsConnected = reason == 0;
                client.Connected?.Invoke(client, new MQTTConnectEventArgs { Reason = reason });
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[ConnectCallback] ERROR: {ex.Message}");
            }
        }

        private static void OnDisconnectCallback(int reason, IntPtr userData)
        {
            try
            {
                if (userData == IntPtr.Zero) return;

                var gcHandle = GCHandle.FromIntPtr(userData);
                var client = gcHandle.Target as NanoMQTTClient;
                if (client == null || client._disposed) return;

                client.IsConnected = false;
                client.Disconnected?.Invoke(client, new MQTTDisconnectEventArgs { Reason = reason });
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[DisconnectCallback] ERROR: {ex.Message}");
            }
        }

        private static void OnMessageCallback(string topic, IntPtr payload,
                                              uint payloadLen, byte qos, IntPtr userData)
        {
            try
            {
                if (userData == IntPtr.Zero || payloadLen == 0) return;

                var gcHandle = GCHandle.FromIntPtr(userData);
                var client = gcHandle.Target as NanoMQTTClient;
                if (client == null || client._disposed) return;

                byte[] data = new byte[payloadLen];
                Marshal.Copy(payload, data, 0, (int)payloadLen);

                client.MessageReceived?.Invoke(client, new MQTTMessageEventArgs
                {
                    Topic = topic ?? string.Empty,
                    Payload = data,
                    Qos = qos
                });
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[MessageCallback] ERROR: {ex.Message}");
            }
        }

        private static void OnPublishCallback(uint msgId, int result, IntPtr userData)
        {
            try
            {
                if (userData == IntPtr.Zero) return;

                var gcHandle = GCHandle.FromIntPtr(userData);
                var client = gcHandle.Target as NanoMQTTClient;
                if (client == null || client._disposed) return;

                client.MessagePublished?.Invoke(client, new MQTTPublishEventArgs
                {
                    MessageId = msgId,
                    Result = result
                });
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[PublishCallback] ERROR: {ex.Message}");
            }
        }

        private static void OnSubscribeCallback(IntPtr reasonCodes, uint count, IntPtr userData)
        {
            try
            {
                if (userData == IntPtr.Zero || count == 0) return;

                var gcHandle = GCHandle.FromIntPtr(userData);
                var client = gcHandle.Target as NanoMQTTClient;
                if (client == null || client._disposed) return;

                byte[] codes = new byte[count];
                Marshal.Copy(reasonCodes, codes, 0, (int)count);

                client.Subscribed?.Invoke(client, new MQTTSubscribeEventArgs
                {
                    ReasonCodes = codes
                });
            }
            catch (Exception ex)
            {
                Console.WriteLine($"[SubscribeCallback] ERROR: {ex.Message}");
            }
        }

        // ==================== 公共 API ====================

        public int Connect(string url, string clientId = null,
                          string username = null, string password = null,
                          ushort keepAlive = 60, bool cleanSession = true,
                          byte protocolVersion = 4)
        {
            return NativeMethods.mqtt_client_connect(
                _handle, url, clientId, username, password,
                keepAlive, cleanSession, protocolVersion);
        }

        public int Disconnect(int timeoutMs = 5000)
        {
            return NativeMethods.mqtt_client_disconnect(_handle, timeoutMs);
        }

        public int Publish(string topic, byte[] payload, byte qos = 0, bool retain = false)
        {
            return NativeMethods.mqtt_client_publish(
                _handle, topic, payload, (uint)payload.Length, qos, retain);
        }

        public int Publish(string topic, string message, byte qos = 0, bool retain = false)
        {
            byte[] payload = Encoding.UTF8.GetBytes(message);
            return Publish(topic, payload, qos, retain);
        }

        public int Subscribe(string topic, byte qos = 0)
        {
            return NativeMethods.mqtt_client_subscribe(_handle, topic, qos);
        }

        public int Unsubscribe(string topic)
        {
            return NativeMethods.mqtt_client_unsubscribe(_handle, topic);
        }

        public void SetWill(string topic, string message, byte qos = 0, bool retain = false)
        {
            NativeMethods.mqtt_client_set_will(_handle, topic, message, qos, retain);
        }

        public string GetLastError()
        {
            return NativeMethods.mqtt_client_last_error(_handle);
        }

        // ==================== Dispose ====================

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

            try
            {
                if (_handle != IntPtr.Zero)
                {
                    Disconnect();
                    NativeMethods.mqtt_client_destroy(_handle);
                    _handle = IntPtr.Zero;
                }
            }
            catch { }

            if (_gcHandle.IsAllocated)
                _gcHandle.Free();

            _disposed = true;
            GC.SuppressFinalize(this);
        }

        ~NanoMQTTClient()
        {
            Dispose();
        }
    }
}
FileName
using System;
using System.Runtime.InteropServices;

namespace NanoMQTT
{
    internal static class NativeMethods
    {
        private const string DllName = "WinNanoSDK.dll";

        // 委托定义
        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void ConnectCallback(int reason, IntPtr userData);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void DisconnectCallback(int reason, IntPtr userData);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void MessageCallback(
            [MarshalAs(UnmanagedType.LPStr)] string topic,
            IntPtr payload,
            uint payloadLen,
            byte qos,
            IntPtr userData);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void PublishCallback(uint msgId, int result, IntPtr userData);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
        public delegate void SubscribeCallback(IntPtr reasonCodes, uint count, IntPtr userData);

        // 导入函数
        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern IntPtr mqtt_client_create();

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern void mqtt_client_destroy(IntPtr handle);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern int mqtt_client_connect(
            IntPtr handle,
            [MarshalAs(UnmanagedType.LPStr)] string url,
            [MarshalAs(UnmanagedType.LPStr)] string clientId,
            [MarshalAs(UnmanagedType.LPStr)] string username,
            [MarshalAs(UnmanagedType.LPStr)] string password,
            ushort keepAlive,
            bool cleanSession,
            byte protocolVersion);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern int mqtt_client_disconnect(IntPtr handle, int timeoutMs);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern int mqtt_client_publish(
            IntPtr handle,
            [MarshalAs(UnmanagedType.LPStr)] string topic,
            byte[] payload,
            uint payloadLen,
            byte qos,
            bool retain);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern int mqtt_client_subscribe(
            IntPtr handle,
            [MarshalAs(UnmanagedType.LPStr)] string topic,
            byte qos);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern int mqtt_client_unsubscribe(
            IntPtr handle,
            [MarshalAs(UnmanagedType.LPStr)] string topic);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern void mqtt_client_set_will(
            IntPtr handle,
            [MarshalAs(UnmanagedType.LPStr)] string topic,
            [MarshalAs(UnmanagedType.LPStr)] string message,
            byte qos,
            bool retain);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern void mqtt_client_set_connect_callback(
            IntPtr handle, ConnectCallback cb, IntPtr userData);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern void mqtt_client_set_disconnect_callback(
            IntPtr handle, DisconnectCallback cb, IntPtr userData);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern void mqtt_client_set_message_callback(
            IntPtr handle, MessageCallback cb, IntPtr userData);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern void mqtt_client_set_publish_callback(
            IntPtr handle, PublishCallback cb, IntPtr userData);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        public static extern void mqtt_client_set_subscribe_callback(
            IntPtr handle, SubscribeCallback cb, IntPtr userData);

        [DllImport(DllName, CallingConvention = CallingConvention.Cdecl , CharSet =CharSet.Unicode)]
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static extern string mqtt_client_last_error(IntPtr handle);
    }
}
NativeMethods

image

 

posted @ 2026-08-10 18:20  !>Mon<!  阅读(36)  评论(0)    收藏  举报