国标GB/T28181视频流媒体网页无插件直播服务4G摄像头无插件直播国标GB28181协议注册、心跳和注销协议说明介绍
EasyGBS国标(GB28181)流媒体服务软件提供用户管理及Web可视化页面管理; 提供设备状态管理,可实时查看设备是否掉线等信息; 实时流媒体处理,PS(TS)转ES; 设备状态监测、云台控制、录像检索、回放; 提供RTSP、RTMP、HTTP-FLV、HLS等多种协议流输出; 对外提供服务器获取状态、信息,控制等HTTP API接口。
今天我们就来简单介绍下注册、心跳和注销这几个协议。注册和注销很好理解,就是Expires: 0的时候为注销,心跳也很简单就是一个简单的无应答message。
下面将注册的流程图简单摘出来一下:
根据流程图还是比较容易读懂,下面粘贴一下抓包信息:
REGISTER sip:34020000002000000001@192.168.1.81:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.93:5060;rport;branch=z9hG4bK-3d09000-23b5dfc-TFyCL5ME
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
To: <sip:64000000002000000001@192.168.1.93:5060>
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
CSeq: 5 REGISTER
Contact: <sip:64000000002000000001@192.168.1.93:5060>
Authorization: Digest username="34020000002000000001", realm="3402000000", nonce="bd2e4df9e3d9b280", uri="sip:64000000002000000001@192.168.1.93:5060", response="9c8411f2b96c5aef55eb136ba3f34655", algorithm=MD5
Max-Forwards: 70
User-Agent: iVMS 1.0
Expires: 200
Content-Length: 0
SIP/2.0 401 Unauthorized
To: <sip:64000000002000000001@192.168.1.93:5060>;tag=67239569_53173353_7ce4590f-587d-4d44-9690-96498367c675
Via: SIP/2.0/UDP 192.168.1.93:5060;rport=5060;branch=z9hG4bK-3d09000-23b5dfc-TFyCL5ME;received=192.168.1.93
CSeq: 5 REGISTER
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
WWW-Authenticate: Digest realm="3402000000",nonce="31ada55697307236"
Content-Length: 0
REGISTER sip:34020000002000000001@192.168.1.81:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.93:5060;rport;branch=z9hG4bK-3d09000-3068be5-lnl05y0C
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
To: <sip:64000000002000000001@192.168.1.93:5060>
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
CSeq: 6 REGISTER
Contact: <sip:64000000002000000001@192.168.1.93:5060>
Authorization: Digest username="34020000002000000001", realm="3402000000", nonce="31ada55697307236", uri="sip:64000000002000000001@192.168.1.93:5060", response="3526a5a5dd91a45e19d1e6a65704457f", algorithm=MD5
Max-Forwards: 70
User-Agent: iVMS 1.0
Expires: 200
Content-Length: 0
SIP/2.0 200 OK
To: <sip:64000000002000000001@192.168.1.93:5060>;tag=86156704_53173353_d7fb589f-4357-4fae-ae32-47c38d81fff1
Via: SIP/2.0/UDP 192.168.1.93:5060;rport=5060;branch=z9hG4bK-3d09000-3068be5-lnl05y0C;received=192.168.1.93
CSeq: 6 REGISTER
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
Contact: <sip:64000000002000000001@192.168.1.93:5060>
Expires: 200
Date: 2017-05-19T18:09:17.033
Content-Length:
其中注册要注意的是,注册的时候有个回复时间格式需要进行上下级和设备之间校时功能:
心跳:
MESSAGE sip:34020000002000000001@192.168.1.81:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.93:5060;rport;branch=z9hG4bK-3d09000-2a2b21b-nddru7fy
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=UL61Qycy
To: <sip:34020000002000000001@192.168.1.81:5060>
Call-ID: 5be796e4-b5ab-592e-cc13-64f34ea57528@192.168.1.93
CSeq: 35 MESSAGE
Contact: <sip:192.168.1.93:5060>
Content-Type: Application/MANSCDP+xml
Max-Forwards: 70
User-Agent: iVMS 1.0
Content-Length: 151
<?xml version="1.0"?>
<Notify>
<CmdType>Keepalive</CmdType>
<SN>26</SN>
<DeviceID>64000000002000000001</DeviceID>
<Status>OK</Status>
</Notify>
SIP/2.0 200 OK
To: <sip:34020000002000000001@192.168.1.81:5060>;tag=69112542_53173353_89bb4bc2-7f74-42a8-a102-6b6490b6daa2
Via: SIP/2.0/UDP 192.168.1.93:5060;rport=5060;branch=z9hG4bK-3d09000-2a2b21b-nddru7fy;received=192.168.1.93
CSeq: 35 MESSAGE
Call-ID: 5be796e4-b5ab-592e-cc13-64f34ea57528@192.168.1.93
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=UL61Qycy
Content-Length: 0
注销:
REGISTER sip:34020000002000000001@192.168.1.81:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.93:5060;rport;branch=z9hG4bK-3d09000-23b5dfc-TFyCL5ME
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
To: <sip:64000000002000000001@192.168.1.93:5060>
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
CSeq: 5 REGISTER
Contact: <sip:64000000002000000001@192.168.1.93:5060>
Authorization: Digest username="34020000002000000001", realm="3402000000", nonce="bd2e4df9e3d9b280", uri="sip:64000000002000000001@192.168.1.93:5060", response="9c8411f2b96c5aef55eb136ba3f34655", algorithm=MD5
Max-Forwards: 70
User-Agent: iVMS 1.0
Expires: 0
Content-Length: 0
SIP/2.0 401 Unauthorized
To: <sip:64000000002000000001@192.168.1.93:5060>;tag=67239569_53173353_7ce4590f-587d-4d44-9690-96498367c675
Via: SIP/2.0/UDP 192.168.1.93:5060;rport=5060;branch=z9hG4bK-3d09000-23b5dfc-TFyCL5ME;received=192.168.1.93
CSeq: 5 REGISTER
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
WWW-Authenticate: Digest realm="3402000000",nonce="31ada55697307236"
Content-Length: 0
REGISTER sip:34020000002000000001@192.168.1.81:5060 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.93:5060;rport;branch=z9hG4bK-3d09000-3068be5-lnl05y0C
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
To: <sip:64000000002000000001@192.168.1.93:5060>
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
CSeq: 6 REGISTER
Contact: <sip:64000000002000000001@192.168.1.93:5060>
Authorization: Digest username="34020000002000000001", realm="3402000000", nonce="31ada55697307236", uri="sip:64000000002000000001@192.168.1.93:5060", response="3526a5a5dd91a45e19d1e6a65704457f", algorithm=MD5
Max-Forwards: 70
User-Agent: iVMS 1.0
Expires: 0
Content-Length: 0
SIP/2.0 200 OK
To: <sip:64000000002000000001@192.168.1.93:5060>;tag=86156704_53173353_d7fb589f-4357-4fae-ae32-47c38d81fff1
Via: SIP/2.0/UDP 192.168.1.93:5060;rport=5060;branch=z9hG4bK-3d09000-3068be5-lnl05y0C;received=192.168.1.93
CSeq: 6 REGISTER
Call-ID: b4359b71-eb2f-b6f4-2fce-8fd47e5888ff@192.168.1.93
From: <sip:64000000002000000001@192.168.1.93:5060>;tag=pck2i7pI
Contact: <sip:64000000002000000001@192.168.1.93:5060>
Expires: 0
Date: 2017-05-19T18:09:17.033
Content-Length: 0
下面粘贴一下部分注册的代码,由于使用c++写的代码,所有只能裁剪一部分出来:
#include <stdlib.h>
#include <stdio.h>
#include "eXosip2/eXosip.h"
#include "osipparser2/osip_uri.h"
#include "osipparser2/osip_message.h"
#include "eXosip2.h"
//初始化 监听端口
int Init(int listenport)
{
int iRet = eXosip_init();
if (iRet != OSIP_SUCCESS)
{
//DBGPrint(M_SipUA, ERROR_LEVEL, "eXosip2 fail!");
//return -1;
printf("eXosip2 fail! \n");
exit(1);
}
iRet = eXosip_listen_addr(IPPROTO_UDP, NULL, listenport, AF_INET, 0);
if (iRet != OSIP_SUCCESS)
{
eXosip_quit();
//DBGPrint(M_SipUA, ERROR_LEVEL, "eXosip2 could not initialize transport layer!");
//return -1;
DBGPrint(M_SipUA, BREAK_LEVEL, "eXosip2 could not initialize transport layer! \n");
exit(1);
}
DBGPrint(M_SipUA, BREAK_LEVEL, "%s: SipSvr Listen Port:%d Sucess!!!!", `ls_function` , listenport);
return 0;
}
/*******************************************************************************
* Function:
* Digest MD5 authorization validate process.
*******************************************************************************/
//MD5鉴权
bool MD5AuthValidate(char *pMethod, osip_authorization_t *pAuthEcho)
{
if (NULL == pAuthEcho)
{
DBGPrint(M_SipUA, ERROR_LEVEL, "pAuthField is null pointer!");
return false;
}
char *pAlgorithm = NULL;
char *pUsername = NULL;
char *pRealm = NULL;
char *pPasswd = NULL;
char *pNonce = NULL;
char *pNonceCount = NULL;
char *pCNonce = NULL;
char *pQop = NULL;
char *pCMethod = NULL;
char *pUri = NULL;
char sNullAlg[] = ""; //"MD5";
char sNullQop[] = ""; //"auth";
char SessionKey[MD5_SESSION_KEY_LEN+1] = {0};
char Response[MD5_RESPONSE_LEN] = {0};
char *pResponse2 = NULL;
if (NULL == pAuthEcho->algorithm)
pAlgorithm = unquote(sNullAlg);
else
pAlgorithm = unquote(pAuthEcho->algorithm);
pUsername = unquote(pAuthEcho->username);
pRealm = unquote(pAuthEcho->realm);
pPasswd = unquote(CSBase::m_SipRegPasswd);
pNonce = unquote(pAuthEcho->nonce);
pCNonce = unquote(pAuthEcho->cnonce);
if (NULL == pUsername)
{
DBGPrint(M_SipUA, ERROR_LEVEL, "%s Get username is null error!",__FUNCTION__);
return false;
}
if(strcmp(pUsername, CSBase::m_SipRegUserName) != 0)
{
// DBGPrint(M_SipUA, ERROR_LEVEL, "%s %s username error!",__FUNCTION__,pAuthEcho->username);
// return false;
}
//calculate session key
DigestCalcHA1(pAlgorithm, pUsername, pRealm, pPasswd, pNonce, pCNonce, SessionKey);
pNonceCount = unquote(pAuthEcho->nonce_count);
if (NULL == pAuthEcho->message_qop)
pQop = unquote(sNullQop);
else
pQop = unquote(pAuthEcho->message_qop);
pCMethod = unquote(pMethod);
pUri = unquote(pAuthEcho->uri);
//calculate response
DigestCalcResponse(SessionKey, pNonce, pNonceCount, pCNonce, pQop, pCMethod, pUri, (char*)"", Response);
pResponse2 = unquote(pAuthEcho->response);
if (strcmp(Response, pResponse2) != 0)
{
DBGPrint(M_SipUA, ERROR_LEVEL, "Authorization failed <%s, %s>!", Response, pResponse2);
return false;
}
return true;
}
//回复没有包体的响应
int SendRegisterAnswer(int Tid, int Code, bool bSetTime)
{
osip_message_t * pMsgAnswer = NULL;
int ret = eXosip_message_build_answer(Tid, Code,&pMsgAnswer);
if(ret == OSIP_SUCCESS && pMsgAnswer != NULL )
{
if (bSetTime == true)
{
osip_header_t* pHeader = NULL;
char TmpBuf[CLIP_BUFFER_SIZE+1];
//initialize Date header
int iRet = osip_header_init(&pHeader);
if (OSIP_SUCCESS == iRet)
{
time_t now;
struct timeval tv;
//Get current time
gettimeofday(&tv,NULL);
now = tv.tv_sec;
// time(&now);
//struct tm* pTmVal = localtime(&now);
struct tm STm = {0};
struct tm* pTmVal = localtime_r(&now, &STm); //使用线程安全函数
if (pTmVal != NULL)
{
//系统启用夏令时,需要减去一个小时。
if (1 == pTmVal->tm_isdst)
{
now -= 3600;
//pTmVal = localtime(&now);
pTmVal = localtime_r(&now, &STm); //使用线程安全函数
if (NULL == pTmVal)
return -1;
}
//2010-07-26 16:05:10
snprintf(TmpBuf, CLIP_BUFFER_SIZE, "%4d-%02d-%02dT%02d:%02d:%02d.%03d", pTmVal->tm_year+1900, pTmVal->tm_mon+1, pTmVal->tm_mday, pTmVal->tm_hour, \
pTmVal->tm_min, pTmVal->tm_sec,(int)(tv.tv_usec/1000));
//set Date field
osip_header_set_name(pHeader, osip_strdup("Date") );
osip_header_set_value(pHeader, osip_strdup(TmpBuf) );
osip_list_add(&pMsgAnswer->headers, pHeader, -1);
}
}
}
eXosip_message_send_answer(Tid, Code, pMsgAnswer);
}
else
{
DBGPrint(M_SipUA, ERROR_LEVEL, "%s:Build Message Answer Failed Tid<%d> Code<%d>!", `ls_function` , Tid, Code);
}
return 0;
}
//初始接收的注册信息
int ProceRegister(const eXosip_event_t* pSipEvt)
{
int iReturnCode = -1;
int iRet = -1;
osip_authorization_t *pWWWAuEcho = NULL;
iRet = osip_message_get_authorization(pSipEvt->request, 0, &pWWWAuEcho);
if (iRet != -1)
{
//鉴权, 鉴权成功回复200OK
if (MD5AuthValidate(pSipEvt->request->sip_method, pWWWAuEcho))
{
//鉴权成功,回复200成功
//添加用户信息
SendRegisterAnswer(pSipEvt->tid, SIP_OK, true);
}
else
{
//鉴权失败,回复403
SendRegisterAnswer(pSipEvt->tid, SIP_FORBIDDEN, false);
}
}
else
{
//回复401 , 待认证
osip_www_authenticate_t *pWWWAuth = NULL;
iRet = osip_www_authenticate_init(&pWWWAuth);
if (-1 == iRet)
{
DBGPrint(M_SipUA, ERROR_LEVEL, "Failed to init www-authenticate header");
return -1;
}
char RTag[RANDOM_TAG_LEN+1] = {0};
char Nonce[DIGEST_NONCE_LEN+1] = {0};
char TmpBuf[CLIP_BUFFER_SIZE/2+1] = {0};
SIPGenerateNonce(Nonce, 0, GenerateRandomTag(RTag) );
osip_www_authenticate_set_auth_type(pWWWAuth, osip_strdup("Digest") );
memcpy(TmpBuf, CSBase::m_SipSvrPubID, 10);
char realm[CLIP_BUFFER_SIZE];
snprintf(realm, CLIP_BUFFER_SIZE/2, "\"%s\"", TmpBuf);
osip_www_authenticate_set_realm(pWWWAuth, osip_strdup(realm) );
snprintf(TmpBuf, CLIP_BUFFER_SIZE/2, "\"%s\"", Nonce);
osip_www_authenticate_set_nonce(pWWWAuth, osip_strdup(TmpBuf) );
char *pDest = NULL;
osip_www_authenticate_to_str(pWWWAuth, &pDest);
osip_message_t * pSRegister = NULL;
iReturnCode = eXosip_message_build_answer(pSipEvt->tid,SIP_UNAUTHORIZED,&pSRegister);
if ( iReturnCode == 0 && pSRegister != NULL )
{
osip_message_set_www_authenticate(pSRegister,pDest);
osip_message_set_content_type(pSRegister,"Application/MANSCDP+xml");
eXosip_message_send_answer(pSipEvt->tid,SIP_UNAUTHORIZED,pSRegister);
}
osip_www_authenticate_free(pWWWAuth);
osip_free(pDest);
}
return 0;
}
int ProceXsipEvt(eXosip_event_t* pSipEvt)
{
//Check input parameter exception
if (NULL == pSipEvt)
{
DBGPrint(M_SipUA, ERROR_LEVEL, "%s: pSipEvt is null pointer!", `ls_function` );
return -1;
}
//Check eXosip event type
switch (pSipEvt->type)
{
case EXOSIP_MESSAGE_NEW:
if (MSG_IS_NOTIFY(pSipEvt->request))
{
//ProceNotify(pSipEvt);
}
else if(MSG_IS_MESSAGE(pSipEvt->request))
{
//ProceMessage(pSipEvt);
}
else if(MSG_IS_REGISTER(pSipEvt->request))
{
ProceRegister(pSipEvt);
}
break;
case EXOSIP_SUBSCRIPTION_ANSWERED:
case EXOSIP_SUBSCRIPTION_REQUESTFAILURE:
{
//ProcSubScribleAnswer(pSipEvt);
}
break;
case EXOSIP_SUBSCRIPTION_NOTIFY:
{
//ProceNotify(pSipEvt);
}
break;
case EXOSIP_CALL_PROCEEDING:
case EXOSIP_CALL_RINGING:
break;
case EXOSIP_CALL_ANSWERED:
{
eXosip_call_t* pJCall = NULL;
if (OSIP_SUCCESS == eXosip_call_find(pSipEvt->cid, &pJCall) )
{
//ProcCallAnswer(pSipEvt);
}
}
break;
case EXOSIP_CALL_NOANSWER:
case EXOSIP_CALL_REDIRECTED:
case EXOSIP_CALL_REQUESTFAILURE:
case EXOSIP_CALL_SERVERFAILURE:
case EXOSIP_CALL_GLOBALFAILURE:
case EXOSIP_CALL_TIMEOUT:
{
if (pSipEvt->request == NULL || pSipEvt->response == NULL)
{
DBGPrint(M_SipUA, ERROR_LEVEL, "%s: Recv Sip Type<%d:%s> Error!", `ls_function` , pSipEvt->type, GetRecvSipType(pSipEvt->type));
break;
}
DBGPrint(M_SipUA, ERROR_LEVEL, "%s: ******** receive device:<%s> error !response status code:<%d> username:<%s>!", `ls_function` , pSipEvt->request->to->url->username, pSipEvt->response->status_code, pSipEvt->request->req_uri->username );
eXosip_call_t* pJCall = NULL;
if (OSIP_SUCCESS == eXosip_call_find(pSipEvt->cid, &pJCall) )
{
//ProcCallAnswer(pSipEvt);
}
}
break;
case EXOSIP_CALL_MESSAGE_NEW:
if (MSG_IS_BYE(pSipEvt->request) )
{
DBGPrint(M_SipUA, ERROR_LEVEL, "%s: ******** imcoming BYE for DevAor<%s>!", `ls_function` , CSBase::URIToAOR(pSipEvt->request->from->url) );
}
break;
case EXOSIP_CALL_MESSAGE_ANSWERED:
//BYE response
break;
case EXOSIP_CALL_CLOSED:
break;
case EXOSIP_CALL_RELEASED:
break;
case EXOSIP_MESSAGE_REQUESTFAILURE:
break;
case EXOSIP_MESSAGE_ANSWERED:
break;
default:
DBGPrint(M_SipUA, ERROR_LEVEL, "%s: not handle <%d:%s> event type!", `ls_function` , pSipEvt->type, GetRecvSipType(pSipEvt->type));
return -1;
}
}
int main()
{
//初始化exosip协议栈端口
Init(5060);
//等待接收sip数据
while(1)
{
//---------------- eXosip running process ----------------//
eXosip_execute();
eXosip_event_t* pSipEvt = eXosip_event_wait(0, 50);
while (pSipEvt != NULL)
{
eXosip_lock();
ProceXsipEvt(pSipEvt);
eXosip_default_action(pSipEvt);
eXosip_unlock();
eXosip_event_free(pSipEvt);
pSipEvt = eXosip_event_wait(0, 50);
}
usleep(50000);
}
}
GB/T28181视频流媒体解决方案播放效果可见下图:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 提示词工程——AI应用必不可少的技术
· 字符编码:从基础到乱码解决
· 地球OL攻略 —— 某应届生求职总结