适用于 C 的 Azure IoT 设备 SDK

Azure IoT 设备 SDK 是一个库集,旨在简化从 Azure IoT 中心服务发送和接收消息的过程。 有各种不同的 SDK,每个 SDK 都以特定的平台为目标,而本文说明的是适用于 C 语言的 Azure IoT 设备 SDK

注意

嵌入式 C SDK 是支持自带网络 (BYON) 方法的受限制设备的替代项。 IoT 开发人员可以自由选择使用 MQTT 客户端、TLS 和套接字来创建设备解决方案。 详细了解嵌入式 C SDK

注意

本文中提到的某些功能(例如云到设备消息传递、设备孪生、设备管理)仅在 IoT 中心的标准层中提供。 有关 IoT 中心基本层和标准/免费层的详细信息,请参阅选择适合你的解决方案的 IoT 中心层

适用于 C 语言的 Azure IoT 设备 SDK 以 ANSI C (C99) 编写,以获得最大可移植性。 此功能使得这些库很适合在多个平台和设备上运行,尤其是在以将磁盘和内存占用量降到最低作为优先考虑的情况下。

SDK 已在许多平台上进行了测试(有关详细信息,请参阅 Azure IoT 认证设备目录)。 尽管本文包含的是在 Windows 平台上运行的示例代码演示,但本文所述的代码在各种支持的平台上都完全相同。

本文介绍适用于 C 语言的 Azure IoT 设备 SDK 的体系结构,将演示如何初始化设备库,将数据发送到 IoT 中心,以及从 IoT 中心接收消息。 本文中的信息应足以让你开始使用 SDK,但同时也提供了有关库的其他信息的链接。

SDK 体系结构

可在 GitHub 存储库中找到适用于 C 语言的 Azure IoT 设备 SDK,还可在 C API 参考中查看 API 的详细信息。

在此存储库的主分支中可找到最新版本的库:

存储库主分支的屏幕截图

  • 此 SDK 的核心实现可在 iothub_client 文件夹中找到,此文件夹包含 SDK 的最低 API 层的实现:IoTHubClient 库。 此 IoTHubClient 库包含实现原始消息传送的 API,即将消息发送到 IoT 中心以及从 IoT 中心接收消息。 使用此库时,需要负责实现消息序列化,但与 IoT 中心通信的其他细节则由系统处理。

  • IoTHubClient 库依赖于其他开放源代码库:

    • Azure C 共享实用程序库,其常用功能用于很多 Azure 相关的 C SDK 中所需的基本任务(如字符串、列表操作和 IO 等)。

    • Azure uAMQP 库,此库是针对资源约束设备的 AMQP 客户端实现的优化。

    • Azure uMQTT 库,它是实现 MQTT 协议并针对资源约束设备进行了优化的通用型库。

查看示例代码可以更方便地了解这些库的用法。 以下部分演练 SDK 中包含的几个示例应用程序。 此演练应可让你轻松了解 SDK 体系结构层的各种功能以及 API 工作原理的简介。

运行示例之前

在面向 C 的 Azure IoT 设备 SDK 中运行示例之前,必须在 Azure 订阅中创建 IoT 中心服务的实例。 然后完成以下任务:

  • 准备开发环境
  • 获取设备凭据。

准备开发环境

为常用平台提供了包(例如适用于 Windows 的 NuGet 包或者适用于 Debian 和 Ubuntu 的 apt_get),示例将使用这些包(如果适用)。 在某些情况下,需要为设备编译 SDK,或者在设备上编译 SDK。 如果需要编译 SDK,请参阅 GitHub 存储库中的准备开发环境

若要获取示例应用程序代码,请从 GitHub 下载 SDK 的副本。 从 GitHub 存储库的主分支获取源的副本。

获取设备凭据

获取示例源代码后,下一步是获取一组设备凭据。 要使设备能够访问 IoT 中心,必须先将该设备添加到 IoT 中心标识注册表。 添加设备时,需要获取一组所需的设备凭据,以便设备能够连接到 IoT 中心。 下一部分所述示例应用程序的预期凭据格式为设备连接字符串

有几个开源工具可帮助你管理 IoT 中心。

本教程使用图形设备资源管理器工具。 如果在 VS Code 中进行开发,可以使用适用于 VS Code 的 Azure IoT 工具 。 如果喜欢使用 CLI 工具,也可以使用适用于 Azure CLI 2.0 的 IoT 扩展 工具。

设备资源管理器工具使用 Azure IoT 服务库在 IoT 中心执行各种功能(包括添加设备)。 若使用设备资源管理器工具添加设备,会获得设备的连接字符串。 需要此连接字符串才能运行示例应用程序。

如果不熟悉设备资源管理器工具,请参阅以下过程,了解如何使用该工具来添加设备和获取设备连接字符串。

  1. 若要安装设备资源管理器工具,请参阅如何对 IoT 中心设备使用设备资源管理器

  2. 运行该程序时,可以看到以下界面:

    Device Explorer 孪生屏幕截图

  3. 在第一个字段中输入用户的 IoT 中心连接字符串,并单击“更新”。 此步骤配置该工具,以便与 IoT 中心通信。

可以在“IoT 中心服务”>“设置”>“共享访问策略”>“iothubowner”下找到连接字符串

  1. 配置 IoT 中心连接字符串后,请单击“管理”选项卡:

    Device Explorer 孪生/管理屏幕截图

可在此选项卡中管理已注册到 IoT 中心的设备。

  1. 单击“创建”按钮创建设备。 会显示一个已预先填充一组密钥(主密钥和辅助密钥)的对话框。 输入“设备 ID”,并单击“创建”。

    “创建设备”屏幕截图

  2. 创建设备后,“设备”列表会更新,其中包含所有已注册的设备(包括刚刚创建的设备)。 如果在新设备上单击右键,会看到此菜单:

    Device Explorer 孪生右键单击结果

  3. 如果选择“复制所选设备的连接字符串”,会将设备连接字符串复制到剪贴板。 请保留设备连接字符串的副本。 在运行后续部分中所述的示例应用程序时,将要用到它。

完成上述步骤后,可以开始运行一些代码。 大多数示例的主源文件顶部都有一个常量,可让你输入连接字符串。 例如,iothub_client_samples_iothub_convenience_sample 应用程序中的相应行如下所示。

static const char* connectionString = "[device connection string]";

使用 IoTHubClient 库

azure-iot-sdk-c 存储库的 iothub_client 文件夹中有一个 samples 文件夹,其中包含名为 iothub_client_sample_mqtt 的应用程序。

Windows 版本的 iothub_client_samples_iothub_convenience_sample 应用程序包含以下 Visual Studio 解决方案:

Visual Studio解决方案资源管理器

注意

如果 Visual Studio 要求你将项目重新定位到最新版本,请接受提示。

此解决方案只包含一个项目。 此解决方案中安装了四个 NuGet 包:

  • Microsoft.Azure.C.SharedUtility
  • Microsoft.Azure.IoTHub.MqttTransport
  • Microsoft.Azure.IoTHub.IoTHubClient
  • Microsoft.Azure.umqtt

在使用 SDK 时始终需要 Microsoft.Azure.C.SharedUtility 包。 本示例使用 MQTT 协议,因此,必须包括 Microsoft.Azure.umqtt 和 Microsoft.Azure.IoTHub.MqttTransport 包(AMQP 和 HTTPS 有对应的包)。 由于此示例使用 IoTHubClient 库,因此还必须在解决方案中包含 Microsoft.Azure.IoTHub.IoTHubClient 包。

可以在 iothub_client_samples_iothub_convenience_sample 源文件中找到示例应用程序的实现。

以下步骤使用此示例应用程序来演示使用 IoTHubClient 库时所需的项目。

初始化库

注意

在开始使用库之前,可能需要执行一些特定于平台的初始化。 例如,如果打算在 Linux 上使用 AMQP,则必须初始化 OpenSSL 库。 GitHub 存储库中的示例会在客户端启动时调用实用工具函数 platform_init,并在退出之前调用 platform_deinit 函数。 这些函数在 platform.h 标头文件中声明。 应该在存储库中为目标平台检查这些函数定义,以确定是否需要在客户端中包含任何特定于平台的初始化代码。

只有在分配 IoT 中心客户端句柄之后,才可以开始使用库:

if ((iotHubClientHandle = 
  IoTHubClient_LL_CreateFromConnectionString(connectionString, MQTT_Protocol)) == NULL)
{
    (void)printf("ERROR: iotHubClientHandle is NULL!\r\n");
}
else
{
    ...

将设备资源管理器工具获取的设备连接字符串传递给此函数。 还需指定要使用的通信协议。 本示例使用 MQTT,但也可以选择 AMQP 和 HTTPS。

获取有效的 IOTHUB_CLIENT_HANDLE 后,可以开始调用 API 来与 IoT 中心相互发送和接收消息。

发送消息

示例应用程序将设置一个循环用于向 IoT 中心发送消息。 以下代码片段:

  • 创建消息。
  • 将属性添加到消息。
  • 发送消息。

首先创建一条消息:

size_t iterator = 0;
do
{
    if (iterator < MESSAGE_COUNT)
    {
        sprintf_s(msgText, sizeof(msgText), "{\"deviceId\":\"myFirstDevice\",\"windSpeed\":%.2f}", avgWindSpeed + (rand() % 4 + 2));
        if ((messages[iterator].messageHandle = IoTHubMessage_CreateFromByteArray((const unsigned char*)msgText, strlen(msgText))) == NULL)
        {
            (void)printf("ERROR: iotHubMessageHandle is NULL!\r\n");
        }
        else
        {
            messages[iterator].messageTrackingId = iterator;
            MAP_HANDLE propMap = IoTHubMessage_Properties(messages[iterator].messageHandle);
            (void)sprintf_s(propText, sizeof(propText), "PropMsg_%zu", iterator);
            if (Map_AddOrUpdate(propMap, "PropName", propText) != MAP_OK)
            {
                (void)printf("ERROR: Map_AddOrUpdate Failed!\r\n");
            }

            if (IoTHubClient_LL_SendEventAsync(iotHubClientHandle, messages[iterator].messageHandle, SendConfirmationCallback, &messages[iterator]) != IOTHUB_CLIENT_OK)
            {
                (void)printf("ERROR: IoTHubClient_LL_SendEventAsync..........FAILED!\r\n");
            }
            else
            {
                (void)printf("IoTHubClient_LL_SendEventAsync accepted message [%d] for transmission to IoT Hub.\r\n", (int)iterator);
            }
        }
    }
    IoTHubClient_LL_DoWork(iotHubClientHandle);
    ThreadAPI_Sleep(1);

    iterator++;
} while (g_continueRunning);

每次发送消息时,指定发送数据时所调用的回调函数的引用。 在此示例中,回调函数名为 SendConfirmationCallback。 以下代码片段演示此回调函数:

static void SendConfirmationCallback(IOTHUB_CLIENT_CONFIRMATION_RESULT result, void* userContextCallback)
{
    EVENT_INSTANCE* eventInstance = (EVENT_INSTANCE*)userContextCallback;
    (void)printf("Confirmation[%d] received for message tracking id = %zu with result = %s\r\n", callbackCounter, eventInstance->messageTrackingId, MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONFIRMATION_RESULT, result));
    /* Some device specific action code goes here... */
    callbackCounter++;
    IoTHubMessage_Destroy(eventInstance->messageHandle);
}

处理完消息后,请注意对 IoTHubMessage_Destroy 函数的调用。 此函数释放创建消息时分配的资源。

接收消息

接收消息是一个异步操作。 首先,请注册当设备接收消息时所要调用的回调:

if (IoTHubClient_LL_SetMessageCallback(iotHubClientHandle, ReceiveMessageCallback, &receiveContext) != IOTHUB_CLIENT_OK)
{
    (void)printf("ERROR: IoTHubClient_LL_SetMessageCallback..........FAILED!\r\n");
}
else
{
    (void)printf("IoTHubClient_LL_SetMessageCallback...successful.\r\n");
    ...

最后一个参数是指向所需对象的 void 指针。 在本示例中,这是一个指向整数的指针,但也可以是指向更复杂数据结构的指针。 此参数使回调函数可与此函数的调用方以共享状态运行。

当设备接收消息时,将调用注册的回调函数。 此回调函数:

  • 从消息中检索消息 ID 和相关 ID。
  • 检索消息内容。
  • 从消息中检索任何自定义属性。
static IOTHUBMESSAGE_DISPOSITION_RESULT ReceiveMessageCallback(IOTHUB_MESSAGE_HANDLE message, void* userContextCallback)
{
    int* counter = (int*)userContextCallback;
    const char* buffer;
    size_t size;
    MAP_HANDLE mapProperties;
    const char* messageId;
    const char* correlationId;

    // Message properties
    if ((messageId = IoTHubMessage_GetMessageId(message)) == NULL)
    {
        messageId = "<null>";
    }

    if ((correlationId = IoTHubMessage_GetCorrelationId(message)) == NULL)
    {
        correlationId = "<null>";
    }

    // Message content
    if (IoTHubMessage_GetByteArray(message, (const unsigned char**)&buffer, &size) != IOTHUB_MESSAGE_OK)
    {
        (void)printf("unable to retrieve the message data\r\n");
    }
    else
    {
        (void)printf("Received Message [%d]\r\n Message ID: %s\r\n Correlation ID: %s\r\n Data: <<<%.*s>>> & Size=%d\r\n", *counter, messageId, correlationId, (int)size, buffer, (int)size);
        // If we receive the work 'quit' then we stop running
        if (size == (strlen("quit") * sizeof(char)) && memcmp(buffer, "quit", size) == 0)
        {
            g_continueRunning = false;
        }
    }

    // Retrieve properties from the message
    mapProperties = IoTHubMessage_Properties(message);
    if (mapProperties != NULL)
    {
        const char*const* keys;
        const char*const* values;
        size_t propertyCount = 0;
        if (Map_GetInternals(mapProperties, &keys, &values, &propertyCount) == MAP_OK)
        {
            if (propertyCount > 0)
            {
                size_t index;

                printf(" Message Properties:\r\n");
                for (index = 0; index < propertyCount; index++)
                {
                    (void)printf("\tKey: %s Value: %s\r\n", keys[index], values[index]);
                }
                (void)printf("\r\n");
            }
        }
    }

    /* Some device specific action code goes here... */
    (*counter)++;
    return IOTHUBMESSAGE_ACCEPTED;
}

使用 IoTHubMessage_GetByteArray 函数来检索消息(在本示例中是一个字符串)。

取消初始化库

完成发送事件和接收消息后,可以取消初始化 IoT 库。 为此,请发出以下函数调用:

IoTHubClient_LL_Destroy(iotHubClientHandle);

此调用释放 IoTHubClient_CreateFromConnectionString 函数以前分配的资源。

可以看到,使用 IoTHubClient 库可以轻松发送和接收消息。 该库处理与 IoT 中心通信的详细信息,包括要使用哪个协议(从开发人员的视角来看,这是一个简单的配置选项)。

后续步骤

本文介绍了有关使用适用于 C 语言的 Azure IoT 设备 SDK 中的库的基本知识。其中针对 SDK 中包含的组件及其体系结构,以及如何开始使用 Windows 示例等进行了详细说明。 下一篇文章通过讲解有关 IoTHubClient 库的详细信息来继续介绍该 SDK。

若要详细了解如何针对 IoT 中心进行开发,请参阅 Azure IoT SDK

若要进一步探索 IoT 中心的功能,请参阅: