Skip to main content

如何使用Azure IoT Hub Device SDK管理连接状态

分类:  Azure物联网 标签:  #Azure #IoT Hub # #入门 #指南 发布于: 2023-06-13 22:27:51

我们前面介绍了如何使用设备SDK连入Azure IoT Hub,我们这一篇来介绍一下如何识别设备的连接状态和管理连接。

我们先来介绍一下可能出现的连接状态:

  • 瞬时网络问题:一般出现瞬时网络问题,SDK会自动重试, 连接状态变为Disconnected_RetryingSDK显示的改变原因变为Communication_Error
  • 认证使用的对称密钥或者证书过期:这种情况下连接状态会变为DisconnectedSDK显示的原因为变为Bad_Credential, 需要注意这种情况下SDK不会重试。
  • 设备标识在Azure IoT Hub被删除或者被禁止,连接状态会变为DisconnectedSDK显示原因为DeviceNotFoundException。 这种情况SDK也不会自动重试。
  • 连接过程中由于认证成功之后生成的sas token过期,连接状态会变为Disconnected_RetryingSDK显示的原因会变为Communication_Error,这种情况下SDK会自动重试。

如上是我们列举的可能的例子,实际上你可以参考下表对于连接状态的改变做一个详细的了解:

连接状态改变原因连接由谁处理说明需要采取的动作
ConnectedConnection_OkSDKSDK和服务之间的连接已经建立客户端已经准备好使用
Disconnected_RetryingCommunication_ErrorSDK由于一些瞬时的问题(例如网络,瞬时连接丢失,sas token过期)等发生的时候,连接状态会改变Disconnected_Retrying, SDK会尝试进行重试,用户可以设计重试规则,当重试的最大次数达到了,才会抛出异常,并出错。当在这个状态时,不要dispose或者重新初始化设备客户端实例,所有其他的操作会被queue起来,等到重试成功或者失败继续进行
DisconnectedDevice_Disabled应用设备或者模块标识被删除或者被禁止Dispose设备SDK实例,报异常退出应用
DisconnectedBad_Credential应用认证使用的标识过期或者无效Dispose设备SDK实例,报异常,退出应用
DisconnectedCommunication_Error应用连接遇到无法修复的通讯错误Dispose设备SDK`,报异常,退出应用, 也可以考虑安排一个指数增长级别的重试,等候网络恢复。
DisconnectedRetry_Expired应用重试超过了最大次数Dispose设备SDK实例,报异常,退出应用
DisabledClient_Close应用客户端明确关闭连接如果应用想进一步做其他的操作,需要Dispose SDK实例,并重新初始化客户端

设备连接管理的最佳实践

大多数的物联网设备都运行在网络受限的环境下,所以连接问题是一个常态,为了更好的管理连接,建议使用Azure提供的设备SDK来进行连接的管理,重试等。

Azure SDK提供了两个方面特性供用户使用:

SDK监控的错误

连接失败可能发生在各个层面:

  1. 网络错误: Socket连接错误或者DNS解析错误。
  2. 协议级别的错误: 例如MQTT的sas token过期, AMQP的linked deattach等等。
  3. 应用错误。

SDK会检测上述三个层面的错误,但是针对操作系统级别或者硬件级别的错误,SDK是不管的。

SDK的重试机制

SDK通过如下的步骤进行重试:

  1. SDK侦测发生的错误,这些错误可能是来自网络,协议或者应用。
  2. SDK使用错误过滤甄别错误的类型,决定是否进行重试。
  3. 如果SDK检测到该错误是不可恢复的错误,SDK通知用户,并报错退出。
  4. 如果SDK侦测到该错误是可恢复错误,SDK会根据指定的重试机制,安排重试。SDK默认使用Exponential back-off with jitter重试规则。
  5. 重试达到了最大次数或者最长事件,SDK报错退出。

针对于连接的状态变化,SDK允许用户注册一个回调函数用于处理连接状态改变的机制。

SDK有三种重试机制类型:

  1. Exponential back-off with jitter
  2. Custom retry
  3. No Retry

连接管理Demo

我们上面讨论了很多理论,现在我们开始动手实现上述理论,基本原则是:

  • 注册一个回调函数(委托),监控连接状态的变化。
  • 根据错误的基本信息,对照表1, 确认那种错误是可恢复的,那种是不可恢复的。可恢复的安排重试。
  • 重试根据场景选择默认的Exponential back-off with jitter机制,还是自定义重试机制。

我先创建一个简单的例子,在这个例子我们测试一下连接的各种变化。

dotnet new console -o ConnectionChange
cd .\ConnectionChange\
dotnet add package  Microsoft.Extensions.Hosting
dotnet add package Microsoft.Azure.Devices.Client

本例中为了简便,我们使用Symmetric Key认证设备。请先使用Azure Portal创建好一个使用Symmetric Key认证的设备,并记录下连接字符串,同样使用Security Manager来管理密钥。

dotnet user-secrets init
dotnet user-secrets set "Device:ConnectString" "<你的设备连接字符串>"

然后使用VS Code打开项目目录,编辑Program.cs:

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Azure.Devices.Client;
using System.Text;
using System.Text.Json;


using IHost host = Host.CreateDefaultBuilder(args).Build();

var ConnectionString = host.Services.GetRequiredService<IConfiguration>().GetValue<string>("Device:ConnectString");

var deviceClient = DeviceClient.CreateFromConnectionString(ConnectionString, 
                                                        TransportType.Mqtt,
                                                        new ClientOptions {
                                                            SasTokenTimeToLive = TimeSpan.FromSeconds(5)
                                                        }
                                                        );
IRetryPolicy retryPolicy = new ExponentialBackoff(3, TimeSpan.FromMicroseconds(100),
  TimeSpan.FromSeconds(3), TimeSpan.FromMicroseconds(100));
deviceClient.SetRetryPolicy(retryPolicy);

deviceClient.SetConnectionStatusChangesHandler(ConnectionStatusChangeHandlerAsync);

using var cts = new CancellationTokenSource();
await SendDeviceToCloudMessagesAsync(deviceClient, cts.Token);

await host.RunAsync();

我们注意到这个代码里有三个部分:

var deviceClient = DeviceClient.CreateFromConnectionString(ConnectionString, 
                                                        TransportType.Mqtt,
                                                        new ClientOptions {
                                                            SasTokenTimeToLive = TimeSpan.FromSeconds(5)
                                                        }
                                                        );

需要注意的是我们给客户端创建时指定了一个ClientOptions, 这里指定认证成功之后的SAS Token多长时间过期,我们为了测试,这里仅仅给了5秒,默认情况是一个小时。

IRetryPolicy retryPolicy = new ExponentialBackoff(3, TimeSpan.FromMicroseconds(100),
  TimeSpan.FromSeconds(3), TimeSpan.FromMicroseconds(100));
deviceClient.SetRetryPolicy(retryPolicy);

这个部分设置当可以retry的时候,最多重试3次,最小的间隔是100毫秒,最大的简单是2秒,指数级增长。

deviceClient.SetConnectionStatusChangesHandler(ConnectionStatusChangeHandlerAsync);

用于设置连接状态变化的监控函数,该函数现阶段很简单:

void ConnectionStatusChangeHandlerAsync(ConnectionStatus status, ConnectionStatusChangeReason reason)
{
    Console.WriteLine($"Connection status changed: status={status}, reason={reason}");
}

就打印状态的变化方便我们观察。

最后我们定义方法:SendDeviceToCloudMessagesAsync, 用于发送遥测数据,这个方法的定义我就不贴了,大家可以去前面几章拷贝一下,或者本章的代码库里找一下。

注意
上述简单的例子代码库在:https://github.com/hylinux/azure-iot-hub-examples/tree/main/ConnectionChange

然后我们开始运行:

$env:DOTNET_ENVIRONMENT = "Development"
dotnet run

我们开始测试。

测试网络断开

测试时将计算机网络临时屏蔽:

  1. 临时屏蔽网络,看到重试消息之后,立即打开网络:



    从图上可以看到,红1表示连接建立,红2处表示网络断开,进行重试。红3网络恢复,连接继续。

  2. 临时网络断开,等候重试次数结束:



    从图上可以看到,红1连接建立,红2网络断开重试,红3自动重试次数达到最大,立即报异常退出。

测试Disabled设备

我们打开Azure Portal -> Device Management -> Device, 在设备列表里找到你的设备,打开设备,如下可以DisabledEnabled设备:


如图上所示,我们可以利用红1 Disabled设备,红2 Enable设备, 红3 重新生成连接字符串,红4 保存结果。

我们先运行应用,然后使用红1 Disabled设备,并保存: Save:


从运行结果我们可以看到,Disabled设备之后,SDK也会进入重试,并且在重试次数结束之后,你可以看到报的异常和之前都不一样的。

测试重新生成连接字符串

我们先将设备恢复,然后运行应用,然后使用Azure Portal上的Manage Keys重新生成字符串:


发现重新生成连接字符串之后,连接没有中断的情况下居然不影响。

但是如果重新运行应用:


就报异常了。

本篇介绍了如何监听连接状态变化的基本方法,以及如何设置重试的基本方法,我们下一次一起来学习一下由微软官方提供的一个重新连接的实例,这个实例的代码完全可以应用在产线上,非常值得认证的研究和学习。