简体中文

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

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

[开发指南] P2PTunnelAPIs APP端(Agent)开发手册

Version: 适配SDK全版本 | 适用场景: APP外网访问内网TCP/IP服务 | 核心特性: P2P穿透、端口映射、多设备连接、版本兼容

一、导语

TUTK 提供的 P2PTunnel 服务,类似于 VPN 服务。P2PTunnel 服务启动后,将通过 TUTK 的私有协议,将上层传入的数据转发到对端,而且不需要知道对端的IP。
P2PTunnel 模块,可以内嵌至厂商的 APP 程序内,也可以独立做成一个模块。对于一些基于 TCP/IP 的标准或者私有服务,比如 HTTP、SSH、FTP、Telnet、RTSP,APP 端只需要简单几行代码,就可以完成接入,实现外网访问内网设备。二、核心工作原理
P2PTunnel Agent(APP端)的核心作用是:通过 TUTK 服务器与内网设备的 P2PTunnel Server 建立 P2P 隧道,将 APP 本地端口与设备端服务端口映射,APP 访问本地映射端口即可穿透至内网设备的目标服务,无需关注设备真实IP。
2.1 P2PTunnel 模块工作示意图(APP端)

基于P2PTunnelAPIs和TCPIP协议开发NAS-device-1.png

图 1:APP端与内网设备的隧道穿透及端口映射逻辑
2.2 P2PTunnel Agent 使用流程图

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

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

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

以下为 P2PTunnel Agent(APP端)的核心开发流程,包含初始化、P2P连线、端口映射、资源释放等关键步骤,适配 SDK 全版本,支持多设备连接场景。
3.1 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 为状态码。
3.2 建立 P2P 隧道连接(必选)

调用扩展连接接口

通过设备 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。

实现客户端认证回调函数

回调函数用于向 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 传递设备专属信息,实现多设备差异化认证(不同设备可使用不同账号密码)。
3.3 端口映射(核心步骤)

步骤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 访问对应本地端口即可定位到目标设备。
3.4 解除端口映射(必选,避免端口占用)

单个映射解除

不需要访问设备服务时,需及时解除端口映射,释放本地端口资源。
// 通过映射索引解除单个端口映射(mapIndex为映射成功时返回的索引)
P2PTunnelAgent_StopPortMapping(mapIndex);
printf("已解除映射索引%d对应的端口映射\n", mapIndex);

多个映射批量解除

多设备场景下,可通过索引数组批量解除多个端口映射,简化操作。
// 批量解除多个端口映射(示例:解除3个设备的映射)
int mapIndexArray[3] = {deviceMapIndexes[0], deviceMapIndexes[1], deviceMapIndexes[2]};

// 调用批量解除接口(参数:映射索引数组,数组长度)
P2PTunnelAgent_StopPortMapping_byIndexArray(mapIndexArray, 3);
printf("已批量解除3个端口映射\n");

注意

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

3.5 断开 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),确保数据完整性。
3.6 Agent 反初始化(必选)

步骤1、释放 Agent 资源

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

四、FAQ(常见问题)

如果本地端口被占用了,该如何处理?
换一个未被占用的本地端口即可。建议 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 流。

五、相关资源

以下为 TUTK P2P SDK 相关开发文档,便于扩展学习和实操:

即刻开启您的物联网之旅

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

+86 755 27702549

7×24小时服务热线

法律声明 隐私权条款

关注“TUTK”

TUTK服务尽在掌握

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

TUTK服务尽在掌握

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

返回顶部