1.什么是MQTT协议

MQTT 协议的全称是 Message Queuing Telemetry Transport,翻译为消息队列传输探测,它是 ISO 标准下的一种基于发布 - 订阅模式的消息协议,它是基于 TCP/IP 协议簇的,它是为了改善网络设备硬件的性能和网络的性能来设计的。MQTT 一般多用于 IoT 即物联网上,广泛应用于工业级别的应用场景,比如汽车、制造、石油、天然气等。

2.MQTT 协议优缺点

优点

缺点

3. MQTT client 和MQTT server 理解

1. MQTT client : 向server 订阅对应的主题消息内容,并获取到订阅的相关主题消息或者发布相关的主题消息给server。

2. MQTT server :主要用于与多个客户端保持连接,并处理client (客户端)的发布和订阅等逻辑。一般很少直接从server (服务端)发布消息给客户端(如果要发布消息,可以使用 mqttServer.Publish(appMsg); 直接发布消息给client),多数情况下server 都是转发client 已订阅主题消息给对应的client,在系统中起到一个中介的作用。

 4.MQTT 协议与TCP/IP 协议的联系

实操部分
/*以下操作在.net6环境下。实操参考自  https://www.cnblogs.com/weskynet/p/16441219.html ,对mqtt感兴趣的同学可以去学习*/
创建.net core webApi 和 console 项目

下载nuget包

均安装 MQTTnet 最新版即可

Mqtt技术分享_客户端

创建服务端

创建一个类 继承 IHostedService和IDisposable并实现,我这里的类名是MqttHostService

Mqtt技术分享_客户端_02

在创建一个类,实现服务端发布方法

Mqtt技术分享_MQTT_03

public static void PublishData(MqttServer mqttServer, string data)
{
    var message = new MqttApplicationMessage
    {
        Topic = "topic_01",
        Payload = Encoding.Default.GetBytes(data),
        QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
        Retain = true //服务端是否保留消息,true 为保留,如果有新的订阅者连接,就会立马收到消息
    };
    mqttServer.InjectApplicationMessage(new InjectedMqttApplicationMessage(message)//发布消息给所有订阅topic_01 的客户端
    {
        SenderClientId = "Server_01"
    }).GetAwaiter().GetResult();

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

来到服务端Program 注册服务,代码如下:

Mqtt技术分享_MQTT_04

builder.Services.AddHostedService<MqttHostService>();
builder.Services.AddSingleton<MqttServer>(c =>
{
    MqttServerOptionsBuilder optionsBuilder = new MqttServerOptionsBuilder();
    optionsBuilder.WithDefaultEndpoint();
    optionsBuilder.WithDefaultEndpointPort(10086);//设置服务端 端口号
    optionsBuilder.WithConnectionBacklog(1000);//最大连接数
    MqttServerOptions options = optionsBuilder.Build();
    return new MqttFactory().CreateMqttServer(options);
});
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

这时回到创建的第一个类 创建类的构造函数 代码如下:

private readonly MqttServer _mqttServer;
public MqttHostService(MqttServer mqttServer)
{
    _mqttServer = mqttServer;
}
   

public void Dispose()
{
    throw new NotImplementedException();
}

public Task StartAsync(CancellationToken cancellationToken)
{
    throw new NotImplementedException();
}

public Task StopAsync(CancellationToken cancellationToken)
{
    throw new NotImplementedException();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.

接着创建事件 如下:

Mqtt技术分享_服务端_05

/// <summary>
/// 客户端连接时触发
/// </summary>
/// <param name="args"></param>
/// <returns></returns>
private Task _mqttServer_ClientConnectedAsync(ClientConnectedEventArgs args)
{
    Console.WriteLine($"客户端ID=【{args.ClientId}】已连接,用户名=【{args.UserName}】,地址=【{args.Endpoint}】");
    return Task.CompletedTask;
}

/// <summary>
/// 客户端订阅主题事件
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private Task _mqttServer_ClientSubscribedTopicAsync(ClientSubscribedTopicEventArgs arg)
{
    Console.WriteLine($"ClientSubscribedTopicAsync:客户端ID=【{arg.ClientId}】订阅的主题=【{arg.TopicFilter}】");
    return Task.CompletedTask;
}

/// <summary>
/// 关闭后事件
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private Task _mqttServer_StoppedAsync(EventArgs arg)
{
    Console.WriteLine($"StoppedAsync:MQTT服务已关闭……");
    return Task.CompletedTask;
}

/// <summary>
/// 用户名和密码验证有关
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private Task _mqttServer_ValidationgConnectionAsync(ValidatingConnectionEventArgs arg)
{
    arg.ReasonCode = MqttConnectReasonCode.Success;
    if ((arg.UserName ?? string.Empty) != "admin" || (arg.Password ?? string.Empty) != "123456")
    {
        arg.ReasonCode = MqttConnectReasonCode.Banned;
        Console.WriteLine($"ValidatingConnectionAsync:客户端=【{arg.ClientId}】用户名或密码验证错误");
    }
    return Task.CompletedTask;
}

/// <summary>
/// 启动后事件
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private Task _mqttServer_StartedAsync(EventArgs arg)
{
    Console.WriteLine($"StaredAsync:MQTT服务已启动……");
    return Task.CompletedTask;
}

/// <summary>
/// 客户端取消订阅事件
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private Task _mqttServer_ClientUnsubscribedTopicAsync(ClientUnsubscribedTopicEventArgs arg)
{
    Console.WriteLine($"ClientUnsubscribedTopicAsync:客户端ID=【{arg.ClientId}】已取消订阅的主题=【{arg.TopicFilter}】");

    return Task.CompletedTask;
}

/// <summary>
/// 应用消息不再被消费
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private Task _mqttServer_ApplicationNotConsumedAsync(ApplicationMessageNotConsumedEventArgs arg)
{
    Console.WriteLine($"ApplicationNotConsumedAsync:发送端ID=【{arg.SenderId}】Topic主题=【{arg.ApplicationMessage.Topic}】消息=【{Encoding.UTF8.GetString(arg.ApplicationMessage.Payload)}】qos登记=【{arg.ApplicationMessage.QualityOfServiceLevel}】");
    return Task.CompletedTask;
}
/// <summary>
/// 停止后事件
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StopAsync(CancellationToken cancellationToken)
{
    _mqttServer.StoppedAsync += _mqttServer_StoppedAsync;
    return Task.CompletedTask;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.

这样我们服务端就创建好了,我们来解释一下:

来看Program

Mqtt技术分享_MQTT_06

第一句话是注册一个直译 “主持服务” 的对象。对象类型为我们第一个创建的类。Nuget包下载后大多都需要在Program中完成注册使用服务,有时需要规定服务类型。

第二句话是添加一个单例对象,类型为MqttNet包提供的类型 叫 MqttServer

我们在其中使用lambda表达式给要使用本类型的对象提供默认服务端、端口号、最大连接数等并最终创建,返回。

来看MqttHostService 类,我们创建的第一个类,主要看开始方法

Mqtt技术分享_MQTT_07

/// <summary>
///  开始方法
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken)
{
    _mqttServer.ClientConnectedAsync += _mqttServer_ClientConnectedAsync;//客户端连接事件
    _mqttServer.ClientSubscribedTopicAsync += _mqttServer_ClientSubscribedTopicAsync;
    _mqttServer.ClientUnsubscribedTopicAsync += _mqttServer_ClientUnsubscribedTopicAsync;
    _mqttServer.StartedAsync += _mqttServer_StartedAsync;
    _mqttServer.ValidatingConnectionAsync += _mqttServer_ValidationgConnectionAsync;
    _mqttServer.ApplicationMessageNotConsumedAsync += _mqttServer_ApplicationNotConsumedAsync;
    _mqttServer.StartAsync();
    return Task.CompletedTask;

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

在开始方法中用 _mqttServer 字段 中服务于客户端连接的属性添加委托,添加委托用+=;

让我们运行一下。

运行成功后服务端控制台打印如下:

Mqtt技术分享_服务端_08

红框标注的是我们开启Mqtt服务后执行的事件,因为还没有客户端来连接,所以客户端连接事件未触发并未打印文字。

客户端创建(我选择的是控制台应用.Net8)

我这里创建了一个类 类名 ClientOne

Mqtt技术分享_客户端_09

类中内容如下:

public static IMqttClient _mqttClient;
public void MqttClientStart()
{
    var optionsBuilder = new MqttClientOptionsBuilder()
    .WithTcpServer("10.31.52.6", 10086)//要访问的mqtt服务端ip和端口号
    .WithCredentials("admin", "123456")
    .WithClientId("testclient01")//设置客户端id
    .WithCleanSession()
    .WithTls(new MqttClientOptionsBuilderTlsParameters
    {
        UseTls = false //是否使用tls加密
    });
    var clientOptions = optionsBuilder.Build();
    _mqttClient = new MqttFactory().CreateMqttClient();
    _mqttClient.ConnectedAsync += _mqttClient_ConnectedAsync; ;//客户端连接成功事件
    _mqttClient.DisconnectedAsync += _mqttClient_DisconnectedAsync;//客户端连接关闭事件
    _mqttClient.ApplicationMessageReceivedAsync += _mqttClient_ApplicationMessageReceivedAsync;//收到消息事件
    _mqttClient.ConnectAsync(clientOptions);
}
/// <summary>
/// 收到消息
/// </summary>
/// <param name="arg"></param>
/// <returns></returns>
private Task _mqttClient_ApplicationMessageReceivedAsync(MqttApplicationMessageReceivedEventArgs arg)
{
    Console.WriteLine($"【{arg.ClientId}】消息已收到。topis主题=【{arg.ApplicationMessage.Topic}】消息=【{Encoding.UTF8.GetString(arg.ApplicationMessage.Payload)}】 qos等级【{arg.ApplicationMessage.QualityOfServiceLevel}】");
    return Task.CompletedTask;
}
private Task _mqttClient_ConnectedAsync(MqttClientConnectedEventArgs arg)
{
    Console.WriteLine("客户端已连接服务");

    //订阅消息主题
    //MqttQualityOfServiceLevel:(Qos):最多一次,接收者不确认收到消息,并且消息不被发送者存储和重新发送提供底层TCP协议相关的保证
    _mqttClient.SubscribeAsync("topic_01", MqttQualityOfServiceLevel.AtLeastOnce);
    _mqttClient.UnsubscribeAsync("topic_01");//取消订阅
    _mqttClient.SubscribeAsync("topic_01", MqttQualityOfServiceLevel.AtLeastOnce);//恢复订阅
    return Task.CompletedTask;
}

private Task _mqttClient_DisconnectedAsync(MqttClientDisconnectedEventArgs arg)
{
    Console.WriteLine("客户端连接已关闭!");
    return Task.CompletedTask;
}
public void Publish(string data)
{
    var message = new MqttApplicationMessage
    {
        Topic = "topic_01",
        Payload = Encoding.Default.GetBytes(data),
        QualityOfServiceLevel = MqttQualityOfServiceLevel.AtLeastOnce,
        Retain = true,
    };
    _mqttClient.PublishAsync(message);
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

来看Console中program ,代码如下:

Mqtt技术分享_服务端_10

MqttClientConsole.ClientOne clientOne = new MqttClientConsole.ClientOne();

clientOne.MqttClientStart();
Console.ReadLine();
clientOne.Publish("publish message by server");
MqttClientConsole.ClientTow clientTow = new MqttClientConsole.ClientTow();
clientTow.MqttClientStart();
Console.ReadLine();
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
让我们看一下这些代码的意思
客户端连接服务端方法

Mqtt技术分享_客户端_11


发布方法

Mqtt技术分享_MQTT_12

订阅主题

Mqtt技术分享_服务端_13

Program中 执行的方法

Mqtt技术分享_客户端_14

执行一下:

服务端/客户端

Mqtt技术分享_客户端_15

键入后发布消息:服务端与客户端连接成功!

Mqtt技术分享_MQTT_16

再复制ClientOne

修改里面的

Mqtt技术分享_服务端_17

Mqtt技术分享_客户端_18

运行之后clientTow是接收不到主题消息的。