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

例子代码分析
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的局域网设备搜索
