根据《GBT 28181-2016 公共安全视频监控联网系统信息传输、交换、控制技术要求》9.6.1状态信息报送章节描述:
- 当源设备(包括网关、SIP 设备、SIP 客户端或联网系统) 发现工作异常时, 应立即向本 SIP 监控域的SIP服务器发送状态信息;
- 无异常时, 应定时向本SIP 监控域的SIP 服务器发送状态信息。 SIP 设备宜在状态信息中携带故障子设备描述信息。
通过周期性的状态信息报送,实现注册服务器与源设备之间的状态检测即心跳机制。
- 心跳发送方、 接收方需统一配置“心跳间隔”参数, 按照“心跳间隔”定时发送心跳消息, 默认心跳间隔60 s。 心跳发送方、 接收方需统一配置“心跳超时次数”参数, 心跳消息连续超时达到“心跳超时次数”。则认为对方下线, 默认心跳超时次数3 次。
- 心跳接收方在心跳发送方上线状态下检测到心跳消息连续超时达到商定次数则认为心跳发送方离线; 心跳发送方在心跳接收方上线状态下检测到心跳消息响应消息连续超时达到商定次数则认为心跳接收方离线。
命令流程
- 源设备向SIP 服务器发送设备状态信息报送命令。 设备状态信息报送命令采用 Message 方法携带;
- SIP 服务器收到命令后返回200 OK。
状态信息报送需要携带的信息如下:
实现
抓包分析
[设备>>服务端]
IPC设备应定时向所有与之联网的联网单元发送心跳报文消息。抓包如下
MESSAGE sip:44010200492000000001@192.168.0.28:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.0.60:57933;branch=z9hG4bK62860d8c
From: <sip:34020000001110000001@4401020049>
To: <sip:34020000001110000001@4401020049>
Call-ID: 000001EB000026E9@192.168.0.60
CSeq: 5 MESSAGE
Max-Forwards: 70
User-Agent: HTSIP UA/2.1
Content-Type: Application/MANSCDP+xml
Content-Length: 152
<?xml version="1.0"?>
<Notify>
<CmdType>Keepalive</CmdType>
<SN>4</SN>
<DeviceID>34020000001110000001</DeviceID>
<Status>OK</Status>
</Notify>
服务端>>客户端
服务端回应如下:
SIP/2.0 200 OK
Via: SIP/2.0/UDP 192.168.0.60:57933;branch=z9hG4bK62860d8c
From: <sip:34020000001110000001@4401020049>
To: <sip:34020000001110000001@4401020049>;tag=1025301396
Call-ID: 000001EB000026E9@192.168.0.60
CSeq: 5 MESSAGE
User-Agent: videosvr 1.0
Content-Length: 0
实现
服务端代码
int CSipUAEvtProc::ProceMessage(const eXosip_event_t* pSipEvt)
{
osip_body_t *pBody = NULL;
osip_message_get_body(pSipEvt->request, 0, &pBody);
.......
osip_message_t *pOrgSipMsg = pSipEvt->request;
if(eType == E_PT_KEEP_ALIVE_NOTIFY)
{
//处理饱和消息
CSExtRegister* pExtRegister = CSMgr::GetInstance()->FindAExtRegister(pOrgSipMsg->from);
if (pExtRegister != NULL)
{
DBGPrint(M_SipUA, BREAK_LEVEL, "%s: keepalive from<%s> BranchID<%s>!", __FUNCTION__, pOrgSipMsg->from->url->username, GetBranchID(pOrgSipMsg) );
//通过配置文件读取message 饱和时间
pExtRegister->ResetExpiryTimeForKeepAlive(DEFAULT_KEEPALIVE_EXPIRES);
CSBase::SendMessageAnswer(pSipEvt->tid, SIP_OK); //first return 200ok
.....
}
}
设备端代码
int GB28181Keeplive(GB28181Param_t *pGB28181Param)
{
char from[128] = {0,};
char proxy[128] = {0,};
char xml_body[1024] = {0,};
osip_message_t *rqt_msg = NULL;
// sip还未注册,那么不保活
if (!g_SipState.sipRegStatus || !pGB28181Param)
{
return -1;
}
snprintf(from, sizeof(from), "sip:%s@%s:%s",
pGB28181Param->userParam.devSipID,
pGB28181Param->userParam.devSipIP,
pGB28181Param->userParam.devSipPort);
snprintf(proxy, sizeof(proxy), "sip:%s@%s:%s",
pGB28181Param->userParam.sipServerID,
pGB28181Param->userParam.sipServerIP,
pGB28181Param->userParam.sipServerPort);
/* 构建"MESSAGE"请求 */
if (eXosip_message_build_request(&rqt_msg, "MESSAGE", proxy, from, NULL)!=OSIP_SUCCESS)
{
return -1;
}
snprintf(xml_body,
sizeof(xml_body),
"<?xml version=\"1.0\"?>\r\n"
"<Notify>\r\n"
"<CmdType>Keepalive</CmdType>\r\n"/*命令类型*/
"<SN>%d</SN>\r\n"/*命令序列号*/
"<DeviceID>%s</DeviceID>\r\n"/*设备编码*/
"<Status>OK</Status>\r\n"/*是否正常工作*/
"</Notify>\r\n",
GetSipSN(),
pGB28181Param->userParam.devSipID);
if (osip_message_set_content_type(rqt_msg, "Application/MANSCDP+xml")!=OSIP_SUCCESS)
{
osip_message_free(rqt_msg);
return -1;
}
if (osip_message_set_body(rqt_msg, xml_body, strlen(xml_body))!=OSIP_SUCCESS)
{
osip_message_free(rqt_msg);
return -1;
}
/* 发送消息 */
eXosip_lock();
eXosip_message_send_request(rqt_msg);
eXosip_unlock();
return 0;
}