简体中文

基于P2PTunnelAPIs和TCPIP协议开发NAS(或者摄像头)-APP端

P2PTunnelAPIs APP端(Agent)开发指南 | TUTK P2P SDK 开发手册

一、API调用流程图

基于P2PTunnelAPIs和TCPIP协议开发NAS-app-2.png

图 1:APP端 Agent 初始化、连线、映射、释放完整流程

二、核心开发步骤(APP端 Agent)

以下为 P2PTunnel Agent(APP端)的核心开发流程,包含初始化、P2P连线、端口映射、资源释放等关键步骤,适配 SDK 全版本,支持多设备连接场景。

(一)Agent 初始化(必选)
1. 设置 SDK 许可证密钥

启动 Agent 前需先通过 TUTK 提供的许可证密钥激活 SDK,否则后续接口调用会失败。

// 设置SDK许可证密钥(由 TUTK 官方提供) int ret = TUTK_SDK_Set_License_Key(sdk_license_key); if (ret != TUTK_ER_NoERROR) {    printf("TUTK_SDK_Set_License_Key() error[%d]\n", ret);    return -1; }
说明:sdk_license_key 需妥善保管,避免泄露;APP 启动时仅需调用一次。
2. 初始化 P2PTunnel Agent 模块

根据 SDK 版本选择对应的初始化接口,新版本支持局域网直连模式(提升传输速度,需设备端配合开启)。

// 定义最大支持的设备连接数(自定义,如8台设备) #define MAX_SERVER_CONNECT_NUM 8 // 初始化P2PTunnel模块(根据SDK版本选择接口) #if _USE_SDK_VERSION_BELOW_4_3_5_0_ // 旧版本初始化接口(SDK版本 < 4.3.5.0) // 参数:最大支持的设备连接数 ret = P2PTunnelAgentInitialize(MAX_SERVER_CONNECT_NUM); if (ret != TUTK_ER_NoERROR) {    printf("P2PTunnelAgentInitialize() error[%d]\n", ret);    return -1; } #else // 新版本初始化接口(SDK版本 >= 4.3.5.0) // 参数1:最大支持的设备连接数 // 参数2:1=开启局域网直连(提升速度,局域网内数据不加密);0=关闭 // 注意:设备端需同样设置为1才能启用直连模式,否则不生效 ret = P2PTunnelAgentInitialize2(MAX_SERVER_CONNECT_NUM, 1); if (ret != TUTK_ER_NoERROR) {    printf("P2PTunnelAgentInitialize2() error[%d]\n", ret);    return -1; } #endif
说明:初始化接口仅需在 APP 启动时调用一次,多次调用可能导致资源冲突。
3. 注册隧道状态回调函数

注册回调函数以监听 P2P 会话状态变更(如连接建立、断开、异常),便于 APP 同步UI状态。

// 注册P2P隧道状态回调函数 // 参数1:回调函数指针(需自定义实现,如TunnelStatusCB) // 参数2:自定义参数(传递给回调函数,如APP上下文信息) P2PTunnelAgent_GetStatus(TunnelStatusCB, (void*)args);
说明:TunnelStatusCB 需按 SDK 定义的函数原型实现,示例:void TunnelStatusCB(int SID, int status, void* pArg),其中 SID 为会话ID,status 为状态码。
(二)建立 P2P 隧道连接(必选)
1. 调用扩展连接接口

通过设备 UID 发起 P2P 连接,支持新版本 DTLS 加密协议,同时兼容旧版本设备(自动降级为旧接口)。

// 设备信息结构体(存储设备UID、认证信息等,自定义) typedef struct {    char uid[64];          // 设备唯一标识(由设备端提供)    char username[32];     // 设备认证用户名    char password[64];     // 设备认证密码    // 其他扩展字段... } sTunnelInfo; // 初始化设备信息(示例:从APP配置或用户输入获取) sTunnelInfo tunnelInfo = {    .uid = "DEVICE_UID_123456", // 目标设备UID    .username = "tutk_agent",   // 预设用户名    .password = "agent_pass123" // 预设密码 }; // 尝试通过新版本扩展接口建立P2P隧道连接 int SID = P2PTunnelAgent_Connect_Ex(    tunnelInfo.uid,                  // 目标设备UID    TunnelClientAuthentication,      // 客户端认证回调函数(填充账号密码)    (void*)&tunnelInfo               // 自定义参数(传递设备信息给回调) ); // 处理"远程不支持DTLS"错误(旧版本设备兼容逻辑) if (SID == TUNNEL_ER_REMOTE_NOT_SUPPORT_DTLS) {    printf("远程设备为旧版本SDK,不支持DTLS,切换至旧版本连接接口\n");    // 初始化旧版本认证数据结构(按旧协议要求)    sAuthData authData = {0};        // 自动初始化缓冲区为0    int nDeviceErr = 0;              // 接收设备端返回的错误码    // 填充旧版本默认认证信息(需与设备端旧版本认证逻辑匹配)    strncpy(authData.szUsername, "Tutk.com", sizeof(authData.szUsername) - 1);    strncpy(authData.szPassword, "P2P Platform", sizeof(authData.szPassword) - 1);    // 调用旧版本连接接口重试    SID = P2PTunnelAgent_Connect(        tunnelInfo.uid,              // 目标设备UID        (void*)&authData,            // 旧版本认证数据        sizeof(sAuthData),           // 认证数据长度        &nDeviceErr                  // 输出设备端错误码(便于排查)    );    // 打印旧版本连接结果    printf("旧版本接口连接结果 - UID: %s, 会话ID(SID): %d, 设备错误码: %d\n",           tunnelInfo.uid, SID, nDeviceErr); } // 处理最终连接结果 if (SID < 0) {    printf("P2P连接失败,错误码: %d(参考SDK文档错误码说明)\n", SID);    return -1; } else {    printf("P2P连接成功,会话ID(SID): %d(后续操作需使用该SID)\n", SID); }
说明:连接成功后返回的 SID 为会话唯一标识,后续端口映射、断开连接等操作需传入该 SID。
2. 实现客户端认证回调函数

回调函数用于向 SDK 填充设备认证的用户名和密码,支持按设备区分不同认证信息(核心!)。

/** * P2P隧道客户端认证回调函数 * @brief 向SDK填充设备认证的用户名和密码,支持多设备差异化认证 * @param cszAccount 输出参数:用户名缓冲区(SDK读取该缓冲区进行认证) * @param nAccountMaxLength cszAccount缓冲区最大长度(含字符串终止符'\0') * @param cszPassword 输出参数:密码缓冲区(SDK读取该缓冲区进行认证) * @param nPasswordMaxLength cszPassword缓冲区最大长度(含字符串终止符'\0') * @param pArg 自定义参数:传递的sTunnelInfo结构体指针(区分不同设备) */ // 全局默认认证信息(适用于无自定义信息的设备) #define DEFAULT_TUNNEL_USERNAME "default_user" #define DEFAULT_TUNNEL_PASSWORD "default_pass" void TunnelClientAuthentication(    char *cszAccount,    uint32_t nAccountMaxLength,    char *cszPassword,    uint32_t nPasswordMaxLength,    const void *pArg ) {    // 将自定义参数转换为设备信息结构体(区分不同设备)    sTunnelInfo *deviceInfo = (sTunnelInfo *)pArg;    // 校验参数有效性    if (cszAccount == NULL || cszPassword == NULL || nAccountMaxLength == 0 || nPasswordMaxLength == 0) {        printf("[%s] 无效参数:缓冲区为空或长度为0\n", __func__);        return;    }    // 按设备填充认证信息(核心差异化逻辑)    if (deviceInfo != NULL && strlen(deviceInfo->uid) > 0) {        // 有自定义设备信息:使用设备专属的用户名密码        printf("[%s] 设备认证 - UID: %s, 用户名: %s\n",               __func__, deviceInfo->uid, deviceInfo->username);                // 安全填充用户名(避免缓冲区溢出)        strncpy(cszAccount, deviceInfo->username, nAccountMaxLength - 1);        cszAccount[nAccountMaxLength - 1] = '\0';        // 安全填充密码(避免缓冲区溢出)        strncpy(cszPassword, deviceInfo->password, nPasswordMaxLength - 1);        cszPassword[nPasswordMaxLength - 1] = '\0';    } else {        // 无自定义设备信息:使用全局默认认证信息        printf("[%s] 无设备自定义信息,使用默认认证\n", __func__);        strncpy(cszAccount, DEFAULT_TUNNEL_USERNAME, nAccountMaxLength - 1);        cszAccount[nAccountMaxLength - 1] = '\0';        strncpy(cszPassword, DEFAULT_TUNNEL_PASSWORD, nPasswordMaxLength - 1);        cszPassword[nPasswordMaxLength - 1] = '\0';    } }
关键说明:通过 pArg 传递设备专属信息,实现多设备差异化认证(不同设备可使用不同账号密码)。
(三)端口映射(核心步骤)
1. 建立本地端口与设备端口的映射

将 APP 本地端口与设备端服务端口绑定,APP 访问本地端口即可穿透至设备端对应服务(如本地10001→设备22端口(SSH))。

/** * 建立P2P隧道端口映射 * @param SID 已建立的P2P会话ID(连接成功返回的SID) * @param local_port APP本地端口(自定义,需未被占用) * @param remote_port 设备端服务端口(如SSH=22、HTTP=80、RTSP=554) * @return 映射索引(>=0成功,<0失败) */ // 示例:映射本地10001端口 → 设备22端口(SSH服务) int local_port = 10001; int remote_port = 22; int mapIndex = P2PTunnelAgent_PortMapping(SID, local_port, remote_port); // 处理映射结果 if (mapIndex < 0) {    // 映射失败:输出详细信息(常见原因:本地端口被占用、SID无效)    printf("端口映射失败 - SID: %d, 本地端口: %d → 设备端口: %d, 错误码: %d\n",           SID, local_port, remote_port, mapIndex);        // 解决方案:本地端口被占用时,更换本地端口(如10002、10003)    local_port = 10002;    mapIndex = P2PTunnelAgent_PortMapping(SID, local_port, remote_port);    if (mapIndex >= 0) {        printf("更换本地端口后映射成功 - 本地端口: %d → 设备端口: %d, 映射索引: %d\n",               local_port, remote_port, mapIndex);    } } else {    // 映射成功:记录映射索引(后续解除映射需使用)    printf("端口映射成功 - SID: %d, 本地端口: %d → 设备端口: %d, 映射索引: %d\n",           SID, local_port, remote_port, mapIndex);        // 提示:APP可通过访问 127.0.0.1:%d 访问设备服务(如127.0.0.1:10001)    printf("访问方式:127.0.0.1:%d(等同于访问设备端 %d 端口)\n", local_port, remote_port); }

重要提醒

端口映射成功后,需妥善保存 mapIndex(映射索引),后续解除映射必须使用该索引,否则会导致本地端口长期占用。

2. 多设备端口映射示例

若 APP 需连接多个设备,通过不同本地端口区分,实现同时访问多个设备的同一服务端口。

// 多设备端口映射示例(3台设备,均访问80端口(HTTP服务)) sTunnelInfo multiDeviceInfo[3] = {    {.uid = "DEVICE_UID_001", .username = "user1", .password = "pass1"},    {.uid = "DEVICE_UID_002", .username = "user2", .password = "pass2"},    {.uid = "DEVICE_UID_003", .username = "user3", .password = "pass3"} }; int deviceSIDs[3] = {0};         // 存储3台设备的SID int deviceMapIndexes[3] = {0};   // 存储3台设备的映射索引 int localPorts[3] = {10001, 10002, 10003}; // 3个不同本地端口 int remotePort = 80;             // 设备端HTTP服务端口 // 循环建立多设备连接和映射 for (int i = 0; i < 3; i++) {    // 建立P2P连接(复用之前的连接逻辑)    deviceSIDs[i] = P2PTunnelAgent_Connect_Ex(        multiDeviceInfo[i].uid,        TunnelClientAuthentication,        (void*)&multiDeviceInfo[i]    );    if (deviceSIDs[i] < 0) {        printf("设备%d(UID:%s)连接失败\n", i+1, multiDeviceInfo[i].uid);        continue;    }    // 建立端口映射    deviceMapIndexes[i] = P2PTunnelAgent_PortMapping(deviceSIDs[i], localPorts[i], remotePort);    if (deviceMapIndexes[i] >= 0) {        printf("设备%d映射成功 - 访问 127.0.0.1:%d → 设备%d的80端口\n",               i+1, localPorts[i], i+1);    } }
说明:多设备场景下,通过“不同本地端口+不同SID+不同映射索引”实现区分,APP 访问对应本地端口即可定位到目标设备。
(四)解除端口映射(必选,避免端口占用)
1. 单个映射解除

不需要访问设备服务时,需及时解除端口映射,释放本地端口资源。

// 通过映射索引解除单个端口映射(mapIndex为映射成功时返回的索引) P2PTunnelAgent_StopPortMapping(mapIndex); printf("已解除映射索引%d对应的端口映射\n", mapIndex);
2. 多个映射批量解除

多设备场景下,可通过索引数组批量解除多个端口映射,简化操作。

// 批量解除多个端口映射(示例:解除3个设备的映射) int mapIndexArray[3] = {deviceMapIndexes[0], deviceMapIndexes[1], deviceMapIndexes[2]}; // 调用批量解除接口(参数:映射索引数组,数组长度) P2PTunnelAgent_StopPortMapping_byIndexArray(mapIndexArray, 3); printf("已批量解除3个端口映射\n");

注意

断开设备连接前,必须先解除所有端口映射,否则会导致本地端口无法释放,后续无法重复使用该端口。

(五)断开 P2P 连接(必选)
1. 优雅断开(推荐)

等待缓冲区数据发送完成后再断开连接,避免数据丢失(适用于文件传输、数据同步等场景)。

// 优雅断开连接(参数:会话ID SID) P2PTunnelAgent_Disconnect(SID); printf("已优雅断开SID=%d的P2P连接(等待数据发送完成)\n", SID);
2. 强制断开

不等待数据发送,直接断开连接(适用于紧急退出、网络异常等场景)。

// 强制断开连接(参数:会话ID SID) P2PTunnelAgent_Abort(SID); printf("已强制断开SID=%d的P2P连接(不等待数据发送)\n", SID);
说明:两种方式任选其一,建议优先使用优雅断开(P2PTunnelAgent_Disconnect),确保数据完整性。
(六)Agent 反初始化(必选)
1. 释放 Agent 资源

APP 退出时,调用反初始化接口释放 P2PTunnel Agent 模块的所有资源,避免内存泄漏。

// Agent 反初始化(APP退出时仅需调用一次) P2PTunnelAgentDeInitialize(); printf("P2PTunnel Agent 反初始化完成\n");
说明:反初始化前需确保所有端口映射已解除、所有 P2P 连接已断开,否则可能导致资源释放不彻底。

三、常见问题

问:如果本地端口被占用了,该如何处理?

答:换一个未被占用的本地端口即可。建议 APP 内置端口检测逻辑(如尝试10001→10002→10003...),自动选择可用端口。


问:如果 Agent 需要连接多个设备,该如何区分不同的设备?

答:通过不同的本地端口区分。例如:3台设备均提供 HTTP 服务(80端口),APP 可使用 10001、10002、10003 分别映射这3台设备的80端口,访问对应本地端口即可定位到目标设备。同时,通过 SID(会话ID)和映射索引分别管理不同设备的连接和映射。


问:P2PTunnelAPIs 可以使用 IOTCAPIs 的 API 吗?

答:可以,调用 IOTCAPIs 接口时,传入 P2P 会话的 SID(连接成功返回的会话ID)即可,数据会通过 P2P 隧道传输。


问:P2PTunnelAPIs 可以与其他模块(AV、RDT)一起使用吗?

答:可以,但集成复杂度较高,容易出现资源冲突或兼容性问题,通常不建议混合使用。若需同时实现音视频传输和隧道功能,建议优先使用 P2PTunnel 承载音视频 TCP 流。

即刻开启您的物联网之旅

联系解决方案专家
Kalay App
资讯安全白皮书
全球专利布局
解决方案
新闻动态
公司动态
行业资讯
媒体报道
永续发展
经营者的话
社会参与
环境永续
公司治理

+86 755 27702549

7×24小时服务热线

法律声明 隐私权条款

关注“TUTK”

TUTK服务尽在掌握

© 2022 物联智慧科技(深圳)有限公司版权所有粤ICP备14023641号
在线咨询
扫一扫

TUTK服务尽在掌握

全国免费服务热线
+86 755 27702549

返回顶部