从代码学习如何设计一个健壮的设备应用
分类: Azure物联网 ◆ 标签: #Azure #IoT Hub # #入门 #指南 ◆ 发布于: 2023-06-13 22:31:50

我们上一章学习了设备SDK
连接状态,以及连接状态改变和原因,我们也用一个简单的应用观察和学习了设备SDK
在不同的情况下的不同表现。微软发布的Azure IoT Hub .Net Device SDK
包含了一个很不错的例子,这个例子里演示了如何创建一个健壮的设备应用。我们本节来学习这个例子。
注意
本章提及的原始代码可以参考:https://github.com/Azure/azure-iot-sdk-csharp/tree/main/iothub/device/samples/how to guides/DeviceReconnectionSample
需要注意的是这部分代码里有这样几个文件:
ExceptionHelper.cs
: 主要是用于判定当SDK
中发生异常时,该异常是网络异常还是安全异常。CustomRetryPolicy.cs
: 自定义重试规则,这个重试规则根据异常决定是否会重试,该类实现了接口IRetryPolicy
DeviceReconnectionSample.cs
: 该文件演示了如何定义一个健壮的设备应用,基本考虑到了各个方面的应用设计技巧。
ExceptionHelper.cs
文件浏览
类ExceptionHelper
首先定义了一个设备SDK
中的异常:
private static readonly HashSet<Type> s_networkExceptions = new() { typeof(IOException), typeof(SocketException), typeof(ClosedChannelException), typeof(TimeoutException), typeof(OperationCanceledException), typeof(HttpRequestException), typeof(WebException), typeof(WebSocketException), };
同时还通过如下代码判断运行的平台是否是Windows
平台。
private static readonly bool s_isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
还定义了一个非常值得研究的Unwind
方法,这个方法中应用了yeild return
以及递归,结合这两种方法帮助判断传入的异常是网络异常还是安全异常。
private static IEnumerable<Exception> Unwind(Exception exception, bool unwindAggregate = false) { while (exception != null) { yield return exception; if (!unwindAggregate) { exception = exception.InnerException; continue; } if (exception is AggregateException aggEx && aggEx.InnerExceptions != null) { foreach (Exception ex in aggEx.InnerExceptions) { foreach (Exception innerEx in Unwind(ex, true)) { yield return innerEx; } } } exception = exception.InnerException; } }
CustomRetryPolicy.cs
类CustomRetryPolicy
实现了接口IRetryPolicy
, 该接口必须实现一个方法:ShouldRetry
, 在这个方法里,使用辅助类ExceptionHelper
判断什么样的异常可以重试,这包括:IotHubException
, 以及网络异常,并且判定IotHubException
是否瞬时异常,这几种情况可以进入重试。
详细的代码,请查看原始代码。
DeviceReconnectionSample.cs
类DeviceReconnectionSample
详细的展示了如何跟踪设备连接状态的变化,以及是否需要重试。
定义了方法:ShouldClientBeInitialized
判断是否需要重新初始化客户端:
private bool ShouldClientBeInitialized(ConnectionStatus connectionStatus) { return (connectionStatus == ConnectionStatus.Disconnected || connectionStatus == ConnectionStatus.Disabled) && _deviceConnectionStrings.Any(); }
从上述代码可以看到当连接状态为Disconnected
以及Disabled
时,需要重新构建客户端实例。也需要明白的时,当我们使用方法创建了DeviceCient
的实例,并不代表着,该客户端已经通过底层的Transport
建立了连接,仅仅当有方法调用到deviceClient.OpenAsync()
的时候,才真正建立和服务端的网络连接。当连接的状态变为Disconnected
以及Disabled
之后,代表底层的Transport
的网络连接已经全部关停,对象资源也已经释放,这个时候我们要做的是同时释放DeviceClient
的实例,并重新创建一个实例。
代码中定义了一个监控连接状态变化的Handler
:private async void ConnectionStatusChangeHandlerAsync(ConnectionStatus status, ConnectionStatusChangeReason reason)
在这个方法里,针对不同的状态采取了不同的措施:
ConnectionStatus.Connected
: 表明设备连接成功了。最佳实践是只要设备连接成功后,立即全部拉取设备的Twin
然后有针对性的处理。ConnectionStatus.Disconnected_Retrying
和ConnectionStatus.Disabled
: 分别提示日志就好了,这里面需要注意的是,针对Disabled
是可以重试的。ConnectionStatus.Disconnected
: 当时Disconnected
状态时,代码设计了检查Reason
: 如果原因是ConnectionStatusChangeReason.Bad_Credential
, 则重新初始化客户端,因为在代码中传入了多个连接字符串,例如在使用对称密钥连接时,同时传入Primary
和Secondly
密钥。如果是ConnectionStatusChangeReason.Retry_Expired
和ConnectionStatusChangeReason.Communication_Error
则直接重新创建客户端。
其中重要的设计包括:
- 在类中使用关键字
volatile
定义DeviceClient
和s_connectionStatus
- 同时使用了多个连接字符串,当某个连接字符串失效时,启用备用连接字符串。
最重要的几个方法:
InitializeAndSetupClientAsync
ConnectionStatusChangeHandlerAsync
ShouldClientBeInitialized
还有一个技巧是: await Task.WhenAll(SendMessagesAsync(s_appCancellation.Token), ReceiveMessagesAsync(s_appCancellation.Token));
设备连接最佳实践
当设备的连接状态更改为ConnectionStatus.Connected
时,第一时间使用设备客户端从Azure IoT Hub
取回设备的Twin
, 并根据Twin
中定义的属性配置设备相关的参数。
当设备的连接状态变为ConnectionStatus.Connected
, 第一件要做的事就是从Azure IoT Hub
取回完整的Twin
, 并根据当时的值设定设备上相关的值:
case ConnectionStatus.Connected: _logger.LogDebug("### The DeviceClient is CONNECTED; all operations will be carried out as normal."); // Call GetTwinAndDetectChangesAsync() to retrieve twin values from the server once the connection status changes into Connected. // This can get back "lost" twin updates in a device reconnection from status like Disconnected_Retrying or Disconnected. // // Howevever, considering how a fleet of devices connected to a hub may behave together, one must consider the implication of performing // work on a device (e.g., get twin) when it comes online. If all the devices go offline and then come online at the same time (for example, // during a servicing event) it could introduce increased latency or even throttling responses. // For more information, see https://docs.microsoft.com/azure/iot-hub/iot-hub-devguide-quotas-throttling#traffic-shaping. await GetTwinAndDetectChangesAsync(s_appCancellation.Token); _logger.LogDebug("The client has retrieved twin values after the connection status changes into CONNECTED."); break;
更为详细的说明,大家可以参考一下源代码。
下一节我们依据这个例子改进代码,以便设备应用更加健壮。