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

例子代码分析
Server的初始化:
// 设置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;
}
// 初始化 P2P 隧道服务器(根据 SDK 版本选择接口)
#if _USE_SDK_VERSION_BELOW_4_3_5_0_
// 旧版本初始化接口(SDK 版本 < 4.3.5.0)
// 参数:允许的最大访客连接数
ret = P2PTunnelServerInitialize(MAX_CONNECTION);
if (ret != TUNNEL_ER_NoERROR) {
printf("P2PTunnelServerInitialize() error[%d]!\n", ret);
return -1;
}
#else
// 新版本初始化接口(SDK 版本 >= 4.3.5.0)
// 参数1:允许的最大访客连接数
// 参数2:1=开启局域网直连(提升速度,但局域网内不加密)
// 注意:客户端需同样设置为1才能启用直连模式
ret = P2PTunnelServerInitialize2(MAX_CONNECTION, 1);
if (ret != TUNNEL_ER_NoERROR) {
// 修正:打印正确的函数名
printf("P2PTunnelServerInitialize2() error[%d]!\n", ret);
return -1;
}
#endif
// 注册连接状态回调函数
// 功能:当访客连接状态变更(如退出访问)时,通过 TunnelStatusCB 回调通知
P2PTunnelServer_GetStatus(TunnelStatusCB, (void *)args);Server的初始化,只需要在程序开始调用一次。
端口白名单回调注册[可选]:
/**
* P2P隧道端口验证回调函数
* @brief 控制哪些端口允许通过隧道进行访问,实现端口级别的访问控制
* @param nServicePort 待验证的服务端口号
* @param pArg 自定义参数(当前未使用,传NULL)
* @return 0=允许访问该端口,-1=拒绝访问该端口
*/
#define WEB_SERVICE_PORT 80 // Web服务端口
#define SSH_SERVICE_PORT 22 // SSH服务端口
#define TELNET_SERVICE_PORT 23 // Telnet服务端口
int TunnelPortVerifyCB(uint16_t nServicePort, const void *pArg) {
// 根据端口号判断是否允许访问
switch (nServicePort) {
case WEB_SERVICE_PORT:
case SSH_SERVICE_PORT:
case TELNET_SERVICE_PORT:
// 允许访问预设的服务端口
printf("[%s] 允许访问端口: %d\n", __func__, nServicePort);
return 0;
default:
// 拒绝访问未授权的端口
printf("[%s] 拒绝访问端口: %d\n", __func__, nServicePort);
return -1;
}
}
// 注册端口验证回调函数,实现端口访问控制
int ret = P2PTunnelServer_Register_Port_Verify(TunnelPortVerifyCB, NULL);
if (ret != TUNNEL_ER_NoERROR) {
printf("P2PTunnelServer_Register_Port_Verify() 注册失败,错误码: %d\n", ret);
return -1;
} else {
printf("P2PTunnelServer_Register_Port_Verify() 注册成功,已启用端口访问控制\n");
}启动Tunnel Server:
// 启动P2P隧道服务器(带扩展参数)
// 参数说明:
// - gUid:服务器唯一标识(设备UID)
// - TunnelServerAuthentication:客户端认证回调函数(验证连接合法性)
// - TunnelSessionInfoExCB:会话信息回调函数(获取/处理会话详情)
// - args:自定义参数(传递给回调函数)
int ret = P2PTunnelServer_Start_Ex(gUid, TunnelServerAuthentication, TunnelSessionInfoExCB, args);
// 处理启动结果
if (ret != TUNNEL_ER_NoERROR) {
// 启动失败:输出错误码便于排查
printf("P2PTunnelServer_Start_Ex() 启动失败,错误码: %d\n", ret);
return -1;
} else {
// 启动成功:提示服务器可通过互联网被访问
printf("P2PTunnelServer_Start_Ex() 启动成功,设备UID: %s,可通过互联网接收连接\n", gUid);
}启动server的参数里面,一个是验证账密的回调,一个是连接状态信息的回调。
账密验证验证:
/**
* P2P隧道服务器端认证回调函数
* @brief 验证客户端账号合法性,并返回对应密码供SDK验证
* @param cszAccount 客户端提供的账号
* @param cszPassword 输出参数:用于填充账号对应密码的缓冲区
* @param nPasswordMaxLength cszPassword缓冲区的最大长度(含终止符)
* @param pArg 自定义参数(由启动函数传入)
*/
void TunnelServerAuthentication(
const char *cszAccount,
char *cszPassword,
uint32_t nPasswordMaxLength,
const void *pArg
) {
// 打印认证信息(注意:生产环境建议移除明文账号日志)
printf("[%s] 客户端认证 - 账号: %s, 密码缓冲区大小: %d\n",
__func__, cszAccount ? cszAccount : "NULL", nPasswordMaxLength);
// 校验输入参数有效性
if (cszAccount == NULL || cszPassword == NULL || nPasswordMaxLength == 0) {
printf("[%s] 无效的认证参数,拒绝连接\n", __func__);
return;
}
// 验证账号合法性
if (strcmp(cszAccount, TUNNEL_USERNAME) == 0) {
// 账号匹配:安全填充密码(防止缓冲区溢出)
if (TUNNEL_PASSWORD != NULL) {
strncpy(cszPassword, TUNNEL_PASSWORD, nPasswordMaxLength - 1);
cszPassword[nPasswordMaxLength - 1] = '\0'; // 确保字符串终止
printf("[%s] 账号验证通过,返回密码供SDK校验\n", __func__);
} else {
printf("[%s] 错误:服务器密码未配置,拒绝连接\n", __func__);
}
} else {
// 账号不匹配:不填充密码,SDK会拒绝连接
printf("[%s] 未知账号 %s,拒绝连接\n", __func__, cszAccount);
}
}连接状态的回调:
/**
* P2P隧道会话信息扩展回调函数
* @brief 接收并处理会话详细信息(连接模式、NAT类型、版本等)
* @param sSessionInfo 会话信息结构体指针,包含连接的详细参数
* @param pArg 自定义参数(由启动函数传入,当前未使用)
*/
void TunnelSessionInfoExCB(sP2PTunnelSessionInfoEx *sSessionInfo, const void *pArg) {
// 校验输入参数有效性
if (sSessionInfo == NULL) {
printf("[%s] 警告:会话信息结构体为空\n", __func__);
return;
}
// 打印会话基本信息
printf("\n[%s] 会话信息更新:\n", __func__);
// 连接模式说明(根据实际枚举值补充含义)
const char* modeStr = "";
switch (sSessionInfo->nMode) {
case 0: modeStr = "直连模式(最优)"; break;
case 1: modeStr = "中继模式(通过服务器转发)"; break;
case 2: modeStr = "局域网模式"; break;
default: modeStr = "未知模式";
}
printf(" 连接模式:%d (%s)\n", sSessionInfo->nMode, modeStr);
// NAT类型说明(常见NAT类型参考)
printf(" NAT类型:%d\n", sSessionInfo->nNatType);
// 版本与会话ID
printf(" P2PTunnelAPIs版本:0x%X\n", (unsigned int)sSessionInfo->nVersion);
printf(" SessionID(SID):%d\n", sSessionInfo->nSID);
// 远程地址信息
printf(" 远程设备地址:%s:%d\n",
sSessionInfo->szRemoteIP ? sSessionInfo->szRemoteIP : "未知IP",
sSessionInfo->nRemotePort);
}数据传输:
Tunnel Server启动完成后,就可以开启其他服务了,比如说http-server, ftp-server,rtsp-server,ssh-server等等。此部分因是标准服务,不再赘述。
Server的反初始化:
P2PTunnelServer_Stop();P2PTunnelServerDeInitialize();
Server的反始化,通常只需要在程序结束时调用一次,中途如果需要重启服务,也需调用。
设备的登录状态检测:
正常情况下,设备向P2P服务器报到成功后,会维护与P2P服务器之间的心跳。但是不排除某些情况下,可能会从服务器掉线,所以可以通过IOTC_Get_LoginInfo持续检测当前的login状态。
/**
* 监控IOTC登录状态并处理异常
* @brief 定期检查登录状态,当重试失败次数过多且满足条件时执行登出操作
*/
unsigned int login_state; // 登录状态(7表示正常登录并收到服务器响应)
while (gProcessRun) {
// 获取登录信息:返回值为重试登录失败次数,输出参数为当前登录状态
int ret = IOTC_Get_Login_Info(&login_state);
printf("IOTC_Get_Login_Info() - 重试失败次数: %d, 登录状态: %u\n", ret, login_state);
// 处理接口调用错误
if (ret < IOTC_ER_NoERROR) {
printf("IOTC_Get_Login_Info() 调用失败,错误码: %d,退出监控循环\n", ret);
break;
}
// 登录状态解析(补充状态说明)
if (login_state == 7) {
printf("登录状态正常:已收到P2P服务器响应\n");
} else {
printf("登录状态异常:状态码=%u(非7表示未正常登录)\n", login_state);
}
// 处理重试失败次数过多的情况(ret>4表示长时间与服务器失联)
if (ret > 4) {
printf("警告:已连续%d次登录重试失败,与P2P服务器失联\n", ret);
// 检查是否满足登出条件:无用户访问且网络畅通
bool noUserAccess = checkNoUserAccess(); // 替换为实际的"无用户访问"检查函数
bool networkAvailable = checkNetworkAvailability(); // 替换为实际的网络畅通检查函数(如ping)
if (noUserAccess && networkAvailable) {
printf("满足登出条件(无用户访问且网络正常),执行P2P服务器登出\n");
// 重启P2P模块
// 其他清理操作...
break; // 退出监控循环
} else {
printf("不满足登出条件 - 有用户访问: %s, 网络畅通: %s\n",
noUserAccess ? "否" : "是",
networkAvailable ? "是" : "否");
}
}
// 休眠5秒(每次休眠1秒并检查是否需要退出,提升响应速度)
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}FAQ:
1、P2PTunnelAPIs可以使用IOTCAPIs的API吗?
可以,参数为SID。
2、P2PTunnelAPIs可以与其他模块(AV、RDT)一起使用吗?
可以,但是会比较复杂,通常不建议一起用。
其他文章:
概论:引导
API比较:API比较
局域网设备搜索:基于IOTCAPIs的局域网设备搜索
实验1: [实验]如何基于P2PTunnelAPIs+ssh搭建一个轻NAS?
实验2:[实验]如何基于P2PTunnelAPIs+http搭建一个流媒体服务器
