简体中文

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

星空无限传媒xk8027-高清全集免费看

导语

TUTK提供的P2PTunnel服务,类似于vpn服务。P2PTunnel服务启动后,将通过TUTK的私有协议,将上层传入的数据转发到对端,而且不需要知道对端的IP。
P2PTunnel模块,可以内嵌至厂商的程序内,也可以独立做成一个模块。对于一些基于TCP/IP的标准或者私有服务,比如http、ssh、ftp、telnet、rtsp,只需要简单几行代码,就可以完成接入。
P2PTunnel模块工作示意图
基于P2PTunnelAPIs和TCPIP协议开发NAS-app-1.png


P2PTunnelAgent使用流程图

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


例子代码分析

Agent的初始化:

// 设置SDK许可证密钥
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;
}

// 初始化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:开启局域网直连(提升速度,但局域网内不加密)
// 注意:设备端需同样设置为1才能启用直连模式
ret = P2PTunnelAgentInitialize2(MAX_SERVER_CONNECT_NUM, 1);
if (ret != TUTK_ER_NoERROR) {
    printf("P2PTunnelAgentInitialize2() error[%d]\n", ret); // 修正:打印正确的函数名
    return -1;
}
#endif

// 注册P2P隧道状态回调函数
// 会话状态变化会通过TunnelStatusCB回调通知
P2PTunnelAgent_GetStatus(TunnelStatusCB, (void*)args);

Agent的初始化,只需要在app一打开调用一次。


开始P2P连线:

// 尝试通过带扩展参数的接口建立P2P隧道连接
int ret = P2PTunnelAgent_Connect_Ex(
    info->uid, 
    TunnelClientAuthentication,  // 认证回调函数
    info                         // 自定义参数(传递给认证回调)
);

// 处理"远程不支持DTLS"错误(通常是因为对方使用旧版本SDK)
if (ret == TUNNEL_ER_REMOTE_NOT_SUPPORT_DTLS) {
    printf("Remote P2PTunnel is old version, try legacy P2PTunnelAgent_Connect()\n");

    // 初始化旧版本认证数据结构
    sAuthData authData = {
        .szUsername = {0},  // 用户名缓冲区(自动初始化为0)
        .szPassword = {0}   // 密码缓冲区(自动初始化为0)
    };
    int nErrFromDeviceCB = 0;  // 用于接收设备端返回的错误码

    // 填充默认认证信息(根据旧版本协议要求)
    strncpy(authData.szUsername, "Tutk.com", sizeof(authData.szUsername) - 1);
    strncpy(authData.szPassword, "P2P Platform", sizeof(authData.szPassword) - 1);

    // 使用旧版本接口重试连接
    ret = P2PTunnelAgent_Connect(
        info->uid,
        (void*)&authData,        // 认证数据
        sizeof(sAuthData),       // 认证数据大小
        &nErrFromDeviceCB        // 输出设备端错误码
    );

    // 打印旧版本连接结果(包含设备端返回的错误码)
    printf("P2PTunnelAgent_Connect UID[%s] ret[%d] device_err[%d]\n", 
           info->uid, ret, nErrFromDeviceCB);
}

P2PTunnelAgent_Connect_Ex 里面填入的参数分别为设备端的UID,密码回调函数,以及用户自定义消息,连线成功,将返回本次连接的会话ID(即SID):

/**
 * P2P隧道客户端认证回调函数
 * @param cszAccount 输出参数:用于填充用户名的缓冲区
 * @param nAccountMaxLength cszAccount缓冲区的最大长度(含终止符)
 * @param cszPassword 输出参数:用于填充密码的缓冲区
 * @param nPasswordMaxLength cszPassword缓冲区的最大长度(含终止符)
 * @param pArg 自定义参数:通常为sTunnelInfo结构体指针,用于区分不同设备
 */
void TunnelClientAuthentication(
    char *cszAccount, 
    uint32_t nAccountMaxLength, 
    char *cszPassword, 
    uint32_t nPasswordMaxLength, 
    const void *pArg
) {
    // 将自定义参数转换为设备信息结构体(用于区分不同设备的认证信息)
    sTunnelInfo *info = (sTunnelInfo *)pArg;
    if (info == NULL) {
        printf("[%s] 警告:pArg为空指针,使用默认认证信息\n", __func__);
    } else {
        printf("[%s] 设备认证 - UID=%s, 用户名缓冲区大小=%d, 密码缓冲区大小=%d\n",
               __func__, info->uid, nAccountMaxLength, nPasswordMaxLength);
    }

    // 安全填充用户名(防止缓冲区溢出)
    if (TUNNEL_USERNAME != NULL && nAccountMaxLength > 0) {
        strncpy(cszAccount, TUNNEL_USERNAME, nAccountMaxLength - 1);
        cszAccount[nAccountMaxLength - 1] = '\0';  // 确保字符串终止
    }

    // 安全填充密码(防止缓冲区溢出)
    if (TUNNEL_PASSWORD != NULL && nPasswordMaxLength > 0) {
        strncpy(cszPassword, TUNNEL_PASSWORD, nPasswordMaxLength - 1);
        cszPassword[nPasswordMaxLength - 1] = '\0';  // 确保字符串终止
    }
}

对于不同的设备,通常可能会有不同的密码,在回调里面,如果给不同的设备,传入不同的密码呢?
答案就是在 P2PTunnelAgent_Connect_Ex 里面传入设备的UID,然后在 TunnelClientAuthentication 里面根据用户自定义信息区分设备。


端口映射:

端口映射,即将本地的一个端口,与远程设备监听的端口做绑定,然后agent直接访问本地端口,来访问远程端口。

/**
 * 建立P2P隧道端口映射
 * @param SID 已建立的P2P会话ID
 * @param local_port 本地端口
 * @param remote_port 远程设备端口
 * @return 端口映射索引(>=0表示成功,<0表示失败)
 */
int nWebIndex = P2PTunnelAgent_PortMapping(SID, local_port, remote_port);

// 处理端口映射结果
if (nWebIndex < 0) {
    // 映射失败:输出详细错误信息
    printf("P2PTunnelAgent_PortMapping failed - SID: %d, 本地端口: %d -> 远程端口: %d, 错误码: %d\n",
           SID, local_port, remote_port, nWebIndex);
} else {
    // 映射成功:记录映射索引,便于后续管理(如关闭映射)
    printf("P2PTunnelAgent_PortMapping successful - SID: %d, 本地端口: %d -> 远程端口: %d, 映射索引: %d\n",
           SID, local_port, remote_port, nWebIndex);
    
    // 提示:后续如需关闭此映射,可调用 P2PTunnelAgent_PortMapping_Close(nWebIndex)
}

端口映射是非常重要的一步,这一步完成,可以让客户端以本地访问的方式去访问远程设备。
在端口映射成功后,客户端如何去访问远程设备呢?

具体过程可以参考:
如何访问sftp服务器: [实验]如何基于P2PTunnelAPIs+ssh搭建一个轻NAS?
如何访问http服务器:[实验]如何基于P2PTunnelAPIs+http搭建一个流媒体服务器

需要断开与设备的连线时,应该怎么做呢?


解除映射:

解除端口映射是非常重要以及需要谨慎的,一旦端口没有解除映射,后续端口一直会被占用,导致端口没法再次使用。

P2PTunnelAgent_StopPortMapping(nWebIndex); //解除端口映射

也可以使用P2PTunnelAgent_StopPortMapping_byIndexArray(index_array)对多个index进行解绑。


断开连线:

有两种方式,任选其一

P2PTunnelAgent_Disconnect(SID);//这种方式需要等数据送出,再断开连接。P2PTunnelAgent_Abort(SID);//这种方式不等数据送出,直接断开连接。

Agent的反初始化:

P2PTunnelAgentDeInitialize();

Agent的反始化,只需要在app退出时调用一次。


FAQ:

1、如果本地端口被占用了,该如何处理?
换个本地端口就行了。

2、如果agent需要连接多个设备,该如何区分不同的设备?
用不同的本地端口区分,比如有3个设备在监听80端口,agent可以分别用10001,10002,10003去映射这3个设备的80端口,然后分别访问10001,10002,10003来访问这3个设备。

3、P2PTunnelAPIs可以使用IOTCAPIs的API吗?
可以,参数为SID。

4、P2PTunnelAPIs可以与其他模块(AV、RDT)一起使用吗?
可以,但是会比较复杂,通常不建议一起用。

其他文章

概论:引导
API比较:API比较
局域网设备搜索:基于IOTCAPIs的局域网设备搜索

即刻开启您的物联网之旅

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

+86 755 27702549

7×24小时服务热线

法律声明 隐私权条款

关注“TUTK”

TUTK服务尽在掌握

© 2022 星空无限传媒xk8027版权所有粤ICP备14023641号
在线咨询
扫一扫

TUTK服务尽在掌握

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

返回顶部