二、核心開發(fā)步驟(Server端)
以下為 P2PTunnel Server(服務端)的核心開發(fā)流程,包含授權激活、初始化、服務配置、啟動運行、狀態(tài)檢測、資源釋放等關鍵步驟,適配 SDK 全版本,支持多訪客連接場景。
(一)Server 初始化(必選)
1. 設置 SDK 許可證密鑰
啟動 Server 前需先通過 TUTK 提供的許可證密鑰激活 SDK,否則后續(xù)接口調用會失敗,密鑰需向 TUTK 官方申請。
// 1. 配置 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 需妥善保管,避免泄露;服務啟動時僅需調用一次。
2. 初始化 P2PTunnel Server 模塊
根據 SDK 版本選擇對應的初始化接口,新版本支持局域網直連模式(提升傳輸速度,需 APP 端配合開啟),僅需在服務啟動時調用一次。
// 定義最大允許的訪客連接數(自定義,如8個連接)
#define MAX_CONNECTION 8
// 2. 初始化 P2PTunnelServer(適配不同 SDK 版本)
#if _USE_SDK_VERSION_BELOW_4_3_5_0_
// 舊版本:僅指定最大允許連接數
ret = P2PTunnelServerInitialize(MAX_CONNECTION);
if (ret != TUNNEL_ER_NoERROR) {
printf("P2PTunnelServerInitialize() error[%d]!\n", ret);
return -1;
}
#else
// 新版本:支持局域網直連(第二個參數填1開啟)
// 優(yōu)勢:局域網內傳輸速度大幅提升;注意:需客戶端同步開啟,且局域網內數據不加密
ret = P2PTunnelServerInitialize2(MAX_CONNECTION, 1);
if (ret != TUNNEL_ER_NoERROR) {
printf("P2PTunnelServerInitialize2() error[%d]!\n", ret);
return -1;
}
#endif
說明:多次調用初始化接口可能導致資源沖突,若需重啟服務,需先執(zhí)行反初始化操作。
3. 注冊連接狀態(tài)回調函數
注冊回調函數以監(jiān)聽訪客連接狀態(tài)變更(如接入、退出、異常斷開),便于服務端進行連接管理和日志記錄。
// 3. 注冊連接狀態(tài)回調(監(jiān)聽訪客接入/退出等狀態(tài)變更)
P2PTunnelServer_GetStatus(TunnelStatusCB, (void *)args);
說明:TunnelStatusCB 需按 SDK 定義的函數原型實現,示例:void TunnelStatusCB(int SID, int status, void* pArg),其中 SID 為會話ID,status 為狀態(tài)碼。
(二)端口白名單配置(可選,提升安全性)
通過注冊端口驗證回調,限制僅允許指定端口的服務通過 P2PTunnel 轉發(fā),拒絕非法端口訪問,降低安全風險。
1. 實現端口驗證回調函數
// 定義允許放行的服務端口
#define WEB_SERVICE_PORT 80 // HTTP 服務
#define SSH_SERVICE_PORT 22 // SSH 服務
#define TELNET_SERVICE_PORT 23 // Telnet 服務
// 端口驗證回調函數
int TunnelPortVerifyCB(uint16_t nServicePort, const void *pArg){
int ret = 0;
switch (nServicePort) {
case WEB_SERVICE_PORT:
case SSH_SERVICE_PORT:
case TELNET_SERVICE_PORT:
printf("[%s] 端口[%d] 允許放行\(zhòng)n", __func__, nServicePort);
break;
default:
printf("[%s] 端口[%d] 拒絕訪問\n", __func__, nServicePort);
ret = -1;
break;
}
return ret;
}
2. 注冊端口驗證回調
// 注冊端口驗證回調
int ret = P2PTunnelServer_Register_Port_Verify(TunnelPortVerifyCB, NULL);
if (ret != TUNNEL_ER_NoERROR) {
printf("P2PTunnelServer_Register_Port_Verify() error[%d]!\n", ret);
return -1;
}
說明:回調函數中返回 0 表示放行,返回 -1 表示拒絕,可根據業(yè)務需求擴展端口白名單列表。
(三)啟動 Tunnel Server(必選)
啟動服務時需傳入設備唯一標識(gUid)、賬密驗證回調、連接狀態(tài)信息回調及自定義參數,啟動成功后設備即可通過公網接收訪客連接。
// 啟動 P2PTunnel Server
// 參數說明:設備UID、賬密驗證回調、連接信息回調、自定義參數
ret = P2PTunnelServer_Start_Ex(gUid, TunnelServerAuthentication, TunnelSessionInfoExCB, args);
if (ret != TUNNEL_ER_NoERROR) {
printf("P2PTunnelServer_Start_Ex() error[%d]!\n", ret);
return -1;
} else {
printf("P2PTunnelServer 啟動成功,可通過公網訪問\n");
}
重要提醒
gUid 為設備唯一標識,需確保平臺唯一,否則會導致連接沖突。
(四)認證與狀態(tài)回調實現(必選)
1. 賬密驗證回調函數
用于驗證訪客登錄賬號的合法性:SDK 接收訪客傳入的賬號后,通過該回調獲取對應密碼并進行比對,僅賬號匹配時才允許建立連接。
// 賬密驗證回調函數
void TunnelServerAuthentication(const char *cszAccount, char *cszPassword, uint32_t nPasswordMaxLength, const void *pArg) {
printf("[%s] 訪問賬號:%s,密碼緩沖區(qū)長度:%d\n", __func__, cszAccount, nPasswordMaxLength);
// 校驗賬號合法性(實際場景建議從配置文件或數據庫讀取賬號密碼)
if (strcmp(cszAccount, TUNNEL_USERNAME) == 0) {
// 賬號匹配:將預設密碼拷貝至 SDK 緩沖區(qū)進行比對
strcpy(cszPassword, TUNNEL_PASSWORD);
} else {
// 賬號不匹配:拒絕連接(不寫入密碼即可)
printf("未知賬號,拒絕連接\n");
}
}
關鍵說明:實際項目中不建議硬編碼賬號密碼,應從配置文件、數據庫或安全存儲中讀取,提升安全性。
2. 連接狀態(tài)信息回調函數
實時獲取訪客連接的詳細信息,包括連接模式、NAT 類型、P2PTunnel 版本、訪客 IP/端口、會話 ID 等,便于問題排查和狀態(tài)監(jiān)控。
// 連接狀態(tài)信息回調函數
void TunnelSessionInfoExCB(sP2PTunnelSessionInfoEx *sSessionInfo, const void *pArg) {
printf("[%s] 連接信息更新\n", __func__);
printf(" 連接模式 = %d,NAT 類型 = %d\n", sSessionInfo->nMode, sSessionInfo->nNatType);
printf(" P2PTunnel 版本 = %X,會話 ID = %d\n", (unsigned int)sSessionInfo->nVersion, sSessionInfo->nSID);
printf(" 訪客 IP:端口 = %s:%d\n", sSessionInfo->szRemoteIP, sSessionInfo->nRemotePort);
}
(五)數據傳輸(自動轉發(fā),無需額外開發(fā))
Tunnel Server 啟動后,即可正常部署 HTTP、FTP、RTSP、SSH 等標準服務。P2PTunnel 模塊會自動轉發(fā)對應端口的數據流,無需額外修改服務代碼(服務端按本地常規(guī)方式部署即可)。
說明:若已配置端口白名單,僅白名單中指定的端口會被轉發(fā),未配置則允許所有端口轉發(fā)(不推薦)。
(六)設備登錄狀態(tài)檢測(推薦)
設備成功登錄 P2P 服務器后,會通過心跳機制維持連接。為應對異常掉線場景,可通過 IOTC_Get_Login_Info 接口持續(xù)檢測登錄狀態(tài),確保服務可用性。
unsigned int login_state;
int login_fail_count = 0;
// 循環(huán)檢測登錄狀態(tài)(gProcessRun 為程序運行狀態(tài)標志)
while (gProcessRun) {
int ret = IOTC_Get_Login_Info(&login_state);
printf("IOTC_Get_Login_Info() 重試失敗次數 = %d,登錄狀態(tài) = %u\n", ret, login_state);
// 接口調用異常,退出檢測
if (ret < IOTC_ER_NoERROR) {
break;
}
// 重試失敗次數超過4次:設備已與 P2P 服務器失聯(lián)
if (ret > 4) {
// 無用戶訪問且網絡暢通時,執(zhí)行反初始化
if (is_no_user_connected() && is_network_available()) {
printf("設備已從 P2P 服務器掉線,執(zhí)行反初始化\n");
P2PTunnelServer_Stop();
P2PTunnelServerDeInitialize();
break;
}
}
// 5秒檢測一次
for (int sleep_count = 0; sleep_count < 5 && gProcessRun; sleep_count++) {
sleep(1);
}
}
說明:is_no_user_connected() 需自行實現(判斷當前是否有訪客連接),is_network_available() 可通過 ping 網關或公網地址實現網絡連通性檢測;login_state = 7 表示設備正常登錄并收到 P2P 服務器響應。
(七)Server 反初始化(必選)
用于停止 Tunnel 服務并釋放資源,通常在程序退出時調用一次;若中途需重啟服務,也需先執(zhí)行反初始化操作。
// 停止 Tunnel Server 服務
P2PTunnelServer_Stop();
// 釋放 Tunnel Server 資源
P2PTunnelServerDeInitialize();
printf("P2PTunnel Server 反初始化完成\n");
注意
反初始化前需確保所有訪客連接已斷開,否則可能導致資源釋放不徹底,建議在反初始化前調用連接檢測接口,等待所有連接斷開后再執(zhí)行。