本文承接上一篇,繼續探討基於 GB/T 28181 標準的 SIP 協議組件開發,重點講解如何使用 eXosip2 庫實現 UAC(用戶代理客戶端)的註冊、刷新註冊及註銷流程。上一篇已介紹註冊的理論基礎與 Demo 程序框架,本節將深入剖析核心 API 調用、事件處理機制及完整代碼實現,並通過實測驗證流程正確性。
使用 eXosip2 前,必須先初始化協議棧環境並綁定監聽地址。初始化失敗將導致後續操作無法進行。
#include <eXosip2/eXosip.h>
int result = OSIP_SUCCESS;
// 初始化庫
if (OSIP_SUCCESS != (result = eXosip_init())) {
printf("eXosip_init failure.\n");
return 1;
}
cout << "eXosip_init success." << endl;
// 監聽 UDP 端口
if (OSIP_SUCCESS != eXosip_listen_addr(IPPROTO_UDP, NULL, UACPORTINT, AF_INET, 0)) {
printf("eXosip_listen_addr failure.\n");
eXosip_quit();
return 1;
}
初始化完成後,即可發送 SIP 消息並等待接收事件。
主循環中調用 eXosip_event_wait 獲取事件,並使用 eXosip_default_action 自動處理 401/407 鑑權回應,這是實現註冊重試的關鍵。
eXosip_event_t* osipEventPtr = NULL;
while (true) {
osipEventPtr = ::eXosip_event_wait(0, 200);
eXosip_lock();
eXosip_default_action(osipEventPtr); // 自動處理鑑權
eXosip_unlock();
if (NULL == osipEventPtr) continue;
// 根據事件類型分發處理...
eXosip_event_free(osipEventPtr);
osipEventPtr = NULL;
}
註:實際生產環境建議採用線程池併發處理事件,避免長耗時操作阻塞主循環。
從事件中提取請求/響應報文,解析頭部字段(如 Expires)以獲取業務參數。
osip_header_t* header = NULL;
osip_message_header_get_byname(request, "expires", 0, &header);
if (NULL != header && NULL != header->hvalue) {
// 解析 expires 值
}
定義了 CSipFromToHeader 和 CContractHeader 類,封裝 From/To/Contact 頭部的生成邏輯,確保格式符合 SIP 標準。
SendRegister該函數根據 iType 參數(0:初始註冊, 1:刷新, 2:註銷)構建並發送 REGISTER 請求。關鍵點:
eXosip_register_build_initial_register 獲取註冊 ID;eXosip_register_build_register 復用該 ID;expires=0 並添加 Logout-Reason 頭部;eXosip_add_authentication_info 預置憑證,鑑權由庫自動完成。int SendRegister(int& registerId, CSipFromToHeader &from, CSipFromToHeader &to,
CContractHeader &contact, const string& userName, const string& pwd,
const int expires, int iType) {
// ... 參數打印省略 ...
static osip_message_t *regMsg = 0;
int ret;
::eXosip_add_authentication_info(userName.c_str(), userName.c_str(), pwd.c_str(), "MD5", NULL);
eXosip_lock();
if (0 == registerId) {
registerId = ::eXosip_register_build_initial_register(
from.GetFormatHeader().c_str(), to.GetFormatHeader().c_str(),
contact.GetContractFormatHeader().c_str(), expires, ®Msg);
if (registerId <= 0) return -1;
} else {
ret = ::eXosip_register_build_register(registerId, expires, ®Msg);
if (ret != OSIP_SUCCESS) return ret;
if (expires == 0) {
// 修改 Contact 頭部並添加註銷原因
osip_contact_t *contact = NULL;
char tmp[128];
osip_message_get_contact(regMsg, 0, &contact);
sprintf(tmp, "<sip:%s@%s:%s>;expires=0",
contact->url->username, contact->url->host, contact->url->port);
osip_list_remove(®Msg->contacts, 0);
osip_message_set_contact(regMsg, tmp);
osip_message_set_header(regMsg, "Logout-Reason", "logout");
}
}
ret = ::eXosip_register_send_register(registerId, regMsg);
if (ret != OSIP_SUCCESS) registerId = 0;
eXosip_unlock();
return ret;
}
Register()、RefreshRegister()、UnRegister() 三個函數分別對應用戶操作,內部調用 SendRegister 並維護狀態變量 iCurrentStatus 和 iHandle。
將事件分發到獨立線程處理,主循環僅負責接收與分發,提高併發能力。示例中處理 EXOSIP_REGISTRATION_SUCCESS 和 EXOSIP_REGISTRATION_FAILURE 事件,打印狀態碼與處理結果。

觀察到流程:發送 REGISTER → 收到 401 Unauthorized → 庫自動發送帶憑證的 REGISTER → 收到 200 OK。

直接收到 200 OK,無鑑權挑戰。

發送 expires=0 的 REGISTER,收到 200 OK,Contact 頭部顯示 expires=0。

本文完整展示了基於 eXosip2 實現 SIP 註冊全生命周期的方法,關鍵在於正確使用 eXosip_default_action 讓庫自動處理摘要鑑權,並妥善管理註冊 ID 與狀態機。代碼結構清晰,可直接作為 GB/T 28181 設備接入開發的參考基礎。