如果您在生产设备时还未明确设备需要连接的服务器站点,那么您的设备可以通过bootstrap服务器获取物联网控制台指定设备应该连接的国家或地区的站点,然后建立连接。

背景说明

引导服务

如上图流程所示,使用C-SDK中的Bootstrap功能,可查询到设备应该连接的阿里云站点信息。

注意
  • Bootstrap服务目前仅通过HTTPS方式提供。
  • 建立HTTPS连接时,TLS层的安全连接类型必须为证书方式,也即AIOT_SYSDEP_NETWORK_CRED_SVRCERT_CA

API列表

以下是完整的Bootstrap部分API列表及简要说明 (详见components/bootstrap/aiot_bootstrap_api.h)。

接口名 说明
aiot_bootstrap_init 初始化bootstrap实例并设置默认参数。
aiot_bootstrap_setopt 配置bootstrap实例, 详见Bootstrap选项配置说明
aiot_bootstrap_deinit 释放bootstrap实例句柄的资源。
aiot_bootstrap_send_request 向bootstrap服务器发送请求。
aiot_bootstrap_recv 从bootstrap服务器接收应答。

例程讲解

现对照demos/bootstrap_basic_demo.c例程, 分步骤讲解如何使用API。

引导开发流程

这个例程演示了用SDK配置Bootstrap会话实例的参数, 并发起请求和接收应答, 之后

  • 如果接收应答失败了, 销毁实例, 回收资源, 结束程序退出。
  • 如果接收应答成功, 在demo_bootstrap_recv_handler()的应答处理回调函数中, 演示解析获取服务端应答的内容

需要用户关注或修改的部分, 已用 TODO 在注释中标明。

  1. 设置设备证书

    例程使用的设备证书是公用的, 所以应用于实际业务时, 请替换如下的TODO部分, 传入用户自己真实的设备证书。

    /* TODO: 替换为自己设备的producKey和deviceName */
        char *product_key       = "a13FNXXXXXX";
        char *device_name       = "bootstrap_basic_demo";
  2. 进入程序入口, 给SDK配置全局的底层依赖和日志回调

    底层依赖描述了硬件平台的资源使用方式, 比如怎样获取时钟, 分配内存等, 日志回调是用户的函数, SDK有log输出的时候会进入这个函数。

    int main(int argc, char *argv[])
    {
        ...
        ...
        /* 配置SDK的底层依赖 */
        aiot_sysdep_set_portfile(&g_aiot_sysdep_portfile);
        /* 配置SDK的日志输出 */
        aiot_state_set_logcb(demo_state_logcb);
  3. 给Bootstrap会话配置参数

    这些连接参数包括如何建立TLS连接, 服务器地址在哪, 连接后的数据及事件回调函数是什么等等。

    /* 创建1个Bootstrap会话实例并内部初始化默认参数 */
        bootstrap_handle =  aiot_bootstrap_init();
        /* 配置Bootstrap会话实例
         *
         * 配置项详细说明可参考: http://gaic.alicdn.com/ztms/linkkit/html/aiot__bootstrap__api_8h.html#a9cc177610fa0842f8287ed4fa438d56d
         *
         */
        aiot_bootstrap_setopt(bootstrap_handle, AIOT_BOOTSTRAPOPT_HOST, (void *)host);
        aiot_bootstrap_setopt(bootstrap_handle, AIOT_BOOTSTRAPOPT_PORT, (void *)&port);
        aiot_bootstrap_setopt(bootstrap_handle, AIOT_BOOTSTRAPOPT_PRODUCT_KEY, (void *)product_key);
        aiot_bootstrap_setopt(bootstrap_handle, AIOT_BOOTSTRAPOPT_DEVICE_NAME, (void *)device_name);
        aiot_bootstrap_setopt(bootstrap_handle, AIOT_BOOTSTRAPOPT_NETWORK_CRED, (void *)&cred);
        aiot_bootstrap_setopt(bootstrap_handle, AIOT_BOOTSTRAPOPT_RECV_HANDLER, (void *)demo_bootstrap_recv_handler);
        aiot_bootstrap_setopt(bootstrap_handle, AIOT_BOOTSTRAPOPT_USERDATA, (void *)&demo_info);
        ...
        ...
  4. 向服务器发送服务请求

    使用aiot_bootstrap_send_request()接口, 以上面设置的参数, 向Bootstrap服务器发起服务请求。

    /* 发送Bootstrap服务请求 */
        res = aiot_bootstrap_send_request(bootstrap_handle);
        if (res < STATE_SUCCESS) {
            printf("aiot_bootstrap_send_request failed, res: -0x%04X\n", -res);
            return -1;
        }

    以下日志就对应被发送的服务请求。

    [1580994365.099][LK-040D] < HTTP/1.1 200 OK
    [1580994365.099][LK-040D] < Server: Tengine
    [1580994365.099][LK-040D] < Date: Thu, 06 Feb 2020 13:06:05 GMT
    [1580994365.099][LK-040D] < Content-Type: text/xml
    [1580994365.099][LK-040D] < Content-Length: 129
    [1580994365.099][LK-040D] < Connection: keep-alive
    [1580994365.099][LK-040D] <
    [LK-040E] < 7B 22 63 6F 64 65 22 3A  32 30 30 2C 22 64 61 74 | {"code":200,"dat
    [LK-040E] < 61 22 3A 7B 22 72 65 73  6F 75 72 63 65 73 22 3A | a":{"resources":
    [LK-040E] < 7B 22 6D 71 74 74 22 3A  7B 22 68 6F 73 74 22 3A | {"mqtt":{"host":
    [LK-040E] < 22 70 75 62 6C 69 63 2E  69 6F 74 2D 61 73 2D 6D | "public.iot-as-m
    [LK-040E] < 71 74 74 2E 63 6E 2D 73  68 61 6E 67 68 61 69 2E | qtt.cn-shanghai.
    [LK-040E] < 61 6C 69 79 75 6E 63 73  2E 63 6F 6D 22 2C 22 70 | aliyuncs.com","p
    [LK-040E] < 6F 72 74 22 3A 31 38 38  33 7D 7D 7D 2C 22 6D 65 | ort":1883}}},"me
    [LK-040E] < 73 73 61 67 65 22 3A 22  73 75 63 63 65 73 73 22 | ssage":"success"
    [LK-040E] < 7D                                               | }
  5. 请求发送成功后, 尝试接收服务应答

    使用aiot_bootstrap_recv()接口, 以上面设置的参数, 从Bootstrap服务器接收服务应答。

    /* 接收Bootstrap服务应答 */
        res = aiot_bootstrap_recv(bootstrap_handle);
        if (res < STATE_SUCCESS) {
            printf("aiot_bootstrap_recv failed, res: -0x%04X\n", -res);
            return -1;
        }

    以下日志对应的是被成功接收到的服务应答。

    [1580829319.044][LK-040D] < HTTP/1.1 200 OK
    [1580829319.044][LK-040D] < Server: Tengine
    [1580829319.044][LK-040D] < Date: Tue, 04 Feb 2020 15:15:19 GMT
    [1580829319.044][LK-040D] < Content-Type: text/xml
    [1580829319.044][LK-040D] < Content-Length: 132
    [1580829319.044][LK-040D] < Connection: keep-alive
    [1580829319.044][LK-040D] <
    [LK-040E] < 7B 22 63 6F 64 65 22 3A  32 30 30 2C 22 64 61 74 | {"code":200,"dat
    [LK-040E] < 61 22 3A 7B 22 72 65 73  6F 75 72 63 65 73 22 3A | a":{"resources":
    [LK-040E] < 7B 22 6D 71 74 74 22 3A  7B 22 68 6F 73 74 22 3A | {"mqtt":{"host":
    [LK-040E] < 22 70 75 62 6C 69 63 2E  69 6F 74 2D 61 73 2D 6D | "public.iot-as-m
    [LK-040E] < 71 74 74 2E 61 70 2D 73  6F 75 74 68 65 61 73 74 | qtt.ap-southeast
    [LK-040E] < 2D 31 2E 61 6C 69 79 75  6E 63 73 2E 63 6F 6D 22 | -1.aliyuncs.com"
    [LK-040E] < 2C 22 70 6F 72 74 22 3A  31 38 38 33 7D 7D 7D 2C | ,"port":1883}}},
    [LK-040E] < 22 6D 65 73 73 61 67 65  22 3A 22 73 75 63 63 65 | "message":"succe
    [LK-040E] < 73 73 22 7D                                      | ss"}
  6. 应答接收成功后, 在回调函数中解析应答内容

    之前已经用AIOT_BOOTSTRAPOPT_RECV_HANDLER选项设置了, SDK收到应答时应进入的数据处理函数是demo_bootstrap_recv_handler()

    /* Bootstrap接收消息处理回调, 当SDK从服务器收到Bootstrap消息时被调用 */
    void demo_bootstrap_recv_handler(void *handle, const aiot_bootstrap_recv_t *packet, void *userdata)
    {
        demo_info_t *demo_info = (demo_info_t *)userdata;
        switch (packet->type) {
            case AIOT_BOOTSTRAPRECV_STATUS_CODE: {
                demo_info->code = packet->data.status_code.code;
            }
            break;
            case AIOT_BOOTSTRAPRECV_CONNECTION_INFO: {
                demo_info->host = malloc(strlen(packet->data.connection_info.host) + 1);
                if (demo_info->host != NULL) {
                    memset(demo_info->host, 0, strlen(packet->data.connection_info.host) + 1);
                    /* TODO: 回调中需要将packet指向的空间内容复制保存好, 因为回调返回后, 这些空间就会被SDK释放 */
                    memcpy(demo_info->host, packet->data.connection_info.host, strlen(packet->data.connection_info.host));
                    demo_info->port = packet->data.connection_info.port;
                }
            }
            break;
            default: {
            }
            break;
        }
    }

    Bootstrap服务应答已经被储存在回调函数的入参packet中, 在回调中需要将它的内容复制到demo_info, 因为回调返回后, SDK就要将相关的内存空间释放掉了。

  7. 回到主流程, 打印服务应答的消息内容

    aiot_bootstrap_recv()接口返回后(其中经过了触发并执行demo_bootstrap_recv_handler()), 服务器应答内容已被保存, 将它们打印出来。

    /* 把服务应答中的MQTT域名地址和端口号打印出来 */
        if (demo_info.host != NULL) {
            printf("host: %s, port: %d\n", demo_info.host, demo_info.port);
            /* TODO: 可以保存host和port的内容, 后续用于建立MQTT连接 */
            free(demo_info.host);
        }

    实际的业务使用中,应答内容自然不只是被打印出来,对其中的MQTT服务器域名地址和端口,一般会被保存,然后传给SDK的MQTT接口, 用于建立MQTT长连接。

  8. 销毁会话实例, 释放资源退出

    Bootstrap的主要功能:获取MQTT连接信息(域名+端口)已经演示完毕,流程到这里就清理资源,正常退出了。

    /* 销毁Bootstrap会话实例 */
        aiot_bootstrap_deinit(&bootstrap_handle);