【GB28181】 SIP信令服务器

news/2025/3/6 17:17:36/

概述

本文仅总结关于GB28181下的注册、心跳维持等与推流拉流相配合的SIP信令,主要基于eXosip库实现;其中搭建信令服务器参考了开源代码以及B站up北小菜,文章结尾有链接

主要逻辑梳理

  • 配置自身SIP服务器,同时配置自己想要访问的SIP服务器
  • 主事件循环逻辑
    • 设计一个循环,不断的接收请求的事件
    • 根据不同请求,进行不同的处理
  • 事件处理逻辑
    • REGISTER 请求处理
    • MESSAGE 请求处理
    • INVITE 请求处理
    • INVITE 响应处理
    • BYE 请求处理

框架分析

事件循环的基本框架

class SipServer {
public:void loop(); // 服务器主循环
private:int sip_event_handle(eXosip_event_t *evtp); // SIP 事件处理函数struct eXosip_t *mSipCtx; // eXosip 上下文bool mQuit; // 退出标志
};void SipServer::loop() {if (this->init_sip_server() != 0) {return;}while (!mQuit) {eXosip_event_t *evtp = eXosip_event_wait(mSipCtx, 0, 20); // 等待 SIP 事件if (!evtp) {eXosip_automatic_action(mSipCtx); // 处理超时osip_usleep(100000);continue;}eXosip_automatic_action(mSipCtx); // 执行 eXosip 自动操作this->sip_event_handle(evtp);     // 分发和处理事件eXosip_event_free(evtp);          // 释放事件内存}
}int SipServer::sip_event_handle(eXosip_event_t *evtp) {switch (evtp->type) {case EXOSIP_CALL_MESSAGE_NEW: // 14// 处理 EXOSIP_CALL_MESSAGE_NEW 事件LOGI("EXOSIP_CALL_MESSAGE_NEW type=%d", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_CLOSED: // 21// 处理 EXOSIP_CALL_CLOSED 事件LOGI("EXOSIP_CALL_CLOSED type=%d", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_RELEASED: // 22// 处理 EXOSIP_CALL_RELEASED 事件LOGI("EXOSIP_CALL_RELEASED type=%d", evtp->type);this->dump_request(evtp);this->dump_response(evtp);this->clearClientMap();break;case EXOSIP_MESSAGE_NEW: // 23// 处理 EXOSIP_MESSAGE_NEW 事件LOGI("EXOSIP_MESSAGE_NEW type=%d", evtp->type);if (MSG_IS_REGISTER(evtp->request)) {this->response_register(evtp);} else if (MSG_IS_MESSAGE(evtp->request)) {this->response_message(evtp);} else if (strncmp(evtp->request->sip_method, "BYE", 3) != 0) {LOGE("unknown1");} else {LOGE("unknown2");}break;case EXOSIP_MESSAGE_ANSWERED:// 处理 EXOSIP_MESSAGE_ANSWERED 事件this->dump_request(evtp);break;case EXOSIP_MESSAGE_REQUESTFAILURE:// 处理 EXOSIP_MESSAGE_REQUESTFAILURE 事件LOGI("EXOSIP_MESSAGE_REQUESTFAILURE type=%d: Receive feedback on sending failure after actively sending a message", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_INVITE:// 处理 EXOSIP_CALL_INVITE 事件LOGI("EXOSIP_CALL_INVITE type=%d: The server receives the Invite request actively sent by the client", evtp->type);break;case EXOSIP_CALL_PROCEEDING: // 5// 处理 EXOSIP_CALL_PROCEEDING 事件LOGI("EXOSIP_CALL_PROCEEDING type=%d: When the server receives the Invite (SDP) confirmation reply from the client", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_ANSWERED: // 7// 处理 EXOSIP_CALL_ANSWERED 事件LOGI("EXOSIP_CALL_ANSWERED type=%d: The server receives an invite (SDP) confirmation reply from the client", evtp->type);this->dump_request(evtp);this->dump_response(evtp);this->response_invite_ack(evtp);break;case EXOSIP_CALL_SERVERFAILURE:// 处理 EXOSIP_CALL_SERVERFAILURE 事件LOGI("EXOSIP_CALL_SERVERFAILURE type=%d", evtp->type);break;case EXOSIP_IN_SUBSCRIPTION_NEW:// 处理 EXOSIP_IN_SUBSCRIPTION_NEW 事件LOGI("EXOSIP_IN_SUBSCRIPTION_NEW type=%d", evtp->type);break;default:// 处理未知事件LOGI("type=%d unknown", evtp->type);break;}return 0;
}

基本逻辑分析

该处仅仅说明事件循环,在事件处理逻辑对于事件的处理逻辑再进行详细说明

  • 事件循环(loop)
    • 负责初始化服务器 (init_sip_server())
    • 在一个 while 循环中不断等待事件
    • 如果超时没有事件,执行 eXosip_automatic_action() 和休眠
    • 如果接收到了事件evtp,那么就调用 sip_event_handle(evtp) 分发处理
    • 最后释放该事件即可

事件处理逻辑

 事件处理逻辑的总体框架

int SipServer::sip_event_handle(eXosip_event_t *evtp) {switch (evtp->type) {case EXOSIP_CALL_MESSAGE_NEW: // 14// 处理 EXOSIP_CALL_MESSAGE_NEW 事件LOGI("EXOSIP_CALL_MESSAGE_NEW type=%d", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_CLOSED: // 21// 处理 EXOSIP_CALL_CLOSED 事件LOGI("EXOSIP_CALL_CLOSED type=%d", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_RELEASED: // 22// 处理 EXOSIP_CALL_RELEASED 事件LOGI("EXOSIP_CALL_RELEASED type=%d", evtp->type);this->dump_request(evtp);this->dump_response(evtp);this->clearClientMap();break;case EXOSIP_MESSAGE_NEW: // 23// 处理 EXOSIP_MESSAGE_NEW 事件LOGI("EXOSIP_MESSAGE_NEW type=%d", evtp->type);if (MSG_IS_REGISTER(evtp->request)) {this->response_register(evtp);} else if (MSG_IS_MESSAGE(evtp->request)) {this->response_message(evtp);} else if (strncmp(evtp->request->sip_method, "BYE", 3) != 0) {LOGE("unknown1");} else {LOGE("unknown2");}break;case EXOSIP_MESSAGE_ANSWERED:// 处理 EXOSIP_MESSAGE_ANSWERED 事件this->dump_request(evtp);break;case EXOSIP_MESSAGE_REQUESTFAILURE:// 处理 EXOSIP_MESSAGE_REQUESTFAILURE 事件LOGI("EXOSIP_MESSAGE_REQUESTFAILURE type=%d: Receive feedback on sending failure after actively sending a message", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_INVITE:// 处理 EXOSIP_CALL_INVITE 事件LOGI("EXOSIP_CALL_INVITE type=%d: The server receives the Invite request actively sent by the client", evtp->type);break;case EXOSIP_CALL_PROCEEDING: // 5// 处理 EXOSIP_CALL_PROCEEDING 事件LOGI("EXOSIP_CALL_PROCEEDING type=%d: When the server receives the Invite (SDP) confirmation reply from the client", evtp->type);this->dump_request(evtp);this->dump_response(evtp);break;case EXOSIP_CALL_ANSWERED: // 7// 处理 EXOSIP_CALL_ANSWERED 事件LOGI("EXOSIP_CALL_ANSWERED type=%d: The server receives an invite (SDP) confirmation reply from the client", evtp->type);this->dump_request(evtp);this->dump_response(evtp);this->response_invite_ack(evtp);break;case EXOSIP_CALL_SERVERFAILURE:// 处理 EXOSIP_CALL_SERVERFAILURE 事件LOGI("EXOSIP_CALL_SERVERFAILURE type=%d", evtp->type);break;case EXOSIP_IN_SUBSCRIPTION_NEW:// 处理 EXOSIP_IN_SUBSCRIPTION_NEW 事件LOGI("EXOSIP_IN_SUBSCRIPTION_NEW type=%d", evtp->type);break;default:// 处理未知事件LOGI("type=%d unknown", evtp->type);break;}return 0;
}

新呼叫

打印消息到日志上

代码实现

case EXOSIP_CALL_CLOSED://21LOGI("EXOSIP_CALL_CLOSED type=%d",evtp->type); // 代码行 109this->dump_request(evtp);  // 代码行 110this->dump_response(evtp); // 代码行 111break;// 写入日志函数实现
void SipServer::dump_request(eXosip_event_t *evtp) {char *s;size_t len;osip_message_to_str(evtp->request, &s, &len);LOGI("\nprint request start\ntype=%d\n%s\nprint request end\n",evtp->type,s);
}
void SipServer::dump_response(eXosip_event_t *evtp) {char *s;size_t len;osip_message_to_str(evtp->response, &s, &len);LOGI("\nprint response start\ntype=%d\n%s\nprint response end\n",evtp->type,s);
}

呼叫关闭

实现同新呼叫,仅仅是将sip信息输入到到日志中

case EXOSIP_CALL_CLOSED://21LOGI("EXOSIP_CALL_CLOSED type=%d",evtp->type); // 代码行 109this->dump_request(evtp);  // 代码行 110this->dump_response(evtp); // 代码行 111break;

呼叫释放事件

  • 将消息内容记录到日志
  • 调用 clearClientMap() 函数,清空客户端列表 mClientMap

新的消息请求事件

  • 判断新消息的类型
    • 如果是注册消息,则调用注册逻辑
    • 如果是即时消息,那么解析该消息,然后回复200OK
case EXOSIP_MESSAGE_NEW://23LOGI("EXOSIP_MESSAGE_NEW type=%d",evtp->type); // 代码行 121if (MSG_IS_REGISTER(evtp->request)) { // 代码行 123this->response_register(evtp); // 代码行 124}else if (MSG_IS_MESSAGE(evtp->request)) { // 代码行 126this->response_message(evtp);  // 代码行 127}else if(strncmp(evtp->request->sip_method, "BYE", 3) != 0){ // 代码行 129LOGE("unknown1"); // 代码行 130}else{LOGE("unknown2"); // 代码行 132}break;

注册消息处理

  • 客户端发送一个注册请求,其中会包含授权信息(Authorization头部)
  • 服务器提取请求中的认证信息
  • 服务器计算认证信息是否正确
  • 如果结果一致,那么此时就可以创建一个新的Client对象,然后发送200响应
  • 如果结果不一致,那么就返回401 Unauthorized响应
void SipServer::response_register(eXosip_event_t *evtp) {// 1. 获取授权信息osip_authorization_t * auth = nullptr;osip_message_get_authorization(evtp->request, 0, &auth);// 2. 获取SIP协议中身份认证信息if(auth && auth->username){char *method = NULL, // REGISTER*algorithm = NULL, // MD5*username = NULL,// 340200000013200000024*realm = NULL, // sip服务器传给客户端,客户端携带并提交上来的sip服务域*nonce = NULL, //sip服务器传给客户端,客户端携带并提交上来的nonce*nonce_count = NULL,*uri = NULL; // sip:34020000002000000001@3402000000osip_contact_t *contact = nullptr;osip_message_get_contact (evtp->request, 0, &contact);method = evtp->request->sip_method;char calc_response[HASHHEXLEN];HASHHEX HA1, HA2 = "", Response;#define SIP_STRDUP(field) if (auth->field) (field) = osip_strdup_without_quote(auth->field)SIP_STRDUP(algorithm);SIP_STRDUP(username);SIP_STRDUP(realm);SIP_STRDUP(nonce);SIP_STRDUP(nonce_count);SIP_STRDUP(uri);DigestCalcHA1(algorithm, username, realm, mInfo->getSipPass(), nonce, nonce_count, HA1);DigestCalcResponse(HA1, nonce, nonce_count, auth->cnonce, auth->message_qop, 0, method, uri, HA2, Response);HASHHEX temp_HA1;HASHHEX temp_response;DigestCalcHA1("REGISTER", username, mInfo->getSipRealm(), mInfo->getSipPass(), mInfo->getNonce(), NULL, temp_HA1);DigestCalcResponse(temp_HA1, mInfo->getNonce(), NULL, NULL, NULL, 0, method, uri, NULL, temp_response);memcpy(calc_response, temp_response, HASHHEXLEN);Client *client = new Client(strdup(contact->url->host),atoi(contact->url->port),strdup(username));if (!memcmp(calc_response, Response, HASHHEXLEN)) {this->response_message_answer(evtp,200);LOGI("Camera registration succee,ip=%s,port=%d,device=%s",client->getIp(),client->getPort(),client->getDevice());mClientMap.insert(std::make_pair(client->getDevice(),client));this->request_invite(client->getDevice(),client->getIp(),client->getPort());} else {this->response_message_answer(evtp,401);LOGI("Camera registration error, p=%s,port=%d,device=%s",client->getIp(),client->getPort(),client->getDevice());delete client;}osip_free(algorithm);osip_free(username);osip_free(realm);osip_free(nonce);osip_free(nonce_count);osip_free(uri);} else {response_register_401unauthorized(evtp);}}

未授权响应处理逻辑(也就是401响应的处理逻辑)

  •  401响应的生成
    • osip_www_authenticate_set_auth_type设置认证类型为Digest
    • osip_www_authenticate_set_realm设置服务域名
    • osip_www_authenticate_set_nonce设置为none防止重放攻击的随机值
void SipServer::response_register_401unauthorized(eXosip_event_t *evtp) {char *dest = nullptr;osip_message_t * reg = nullptr;osip_www_authenticate_t * header = nullptr;osip_www_authenticate_init(&header);osip_www_authenticate_set_auth_type (header, osip_strdup("Digest"));osip_www_authenticate_set_realm(header,osip_enquote(mInfo->getSipRealm()));osip_www_authenticate_set_nonce(header,osip_enquote(mInfo->getNonce()));osip_www_authenticate_to_str(header, &dest);int ret = eXosip_message_build_answer (mSipCtx, evtp->tid, 401, &reg);if ( ret == 0 && reg != nullptr ) {osip_message_set_www_authenticate(reg, dest);osip_message_set_content_type(reg, "Application/MANSCDP+xml");eXosip_lock(mSipCtx);eXosip_message_send_answer (mSipCtx, evtp->tid,401, reg);eXosip_unlock(mSipCtx);LOGI("response_register_401unauthorized success");}else {LOGI("response_register_401unauthorized error");}osip_www_authenticate_free(header);osip_free(dest);}

MESSAGE 请求的应答事件

仅输出到日志或者客户端

case EXOSIP_MESSAGE_ANSWERED:this->dump_request(evtp);  // 代码行 135break;

MESSAGE 请求发送失败事件

case EXOSIP_MESSAGE_REQUESTFAILURE:LOGI("EXOSIP_MESSAGE_REQUESTFAILURE type=%d: Receive feedback on sending failure after actively sending a message", evtp->type); // 代码行 138this->dump_request(evtp);  // 代码行 139this->dump_response(evtp); // 代码行 140break;

呼叫应答事件 (2xx 响应)

  • 记录日志的同时,发送响应信息
case EXOSIP_CALL_ANSWERED:// 7LOGI("EXOSIP_CALL_ANSWERED type=%d: The server receives an invite (SDP) confirmation reply from the client", evtp->type); // 代码行 151this->dump_request(evtp);  // 代码行 152this->dump_response(evtp); // 代码行 153this->response_invite_ack(evtp); // 代码行 155break;void SipServer::response_invite_ack(eXosip_event_t *evtp){osip_message_t* msg = nullptr;int ret = eXosip_call_build_ack(mSipCtx, evtp->did, &msg);if (!ret && msg) {eXosip_call_send_ack(mSipCtx, evtp->did, msg);} else {LOGE("eXosip_call_send_ack error=%d", ret);}}

参考资料:BXC_SipServer: C++开发的国标GB28181流媒体Sip信令服务器


http://www.ppmy.cn/news/1577120.html

相关文章

6.从零开始学会Vue--{{路由}}

1.路由简介 Vue 作为一个渐进式框架,在使用 vue-router 之后可以是程序变为一个 SPA 的单页面应用, 它的本质是使用了 js 的 History API: Vue Router 利用浏览器的 History API(如 pushState 和 replaceState),实现…

自学微信小程序的第八天

DAY8 1、使用动画API即可完成动画效果的制作,先通过wx.createAnimation()方法获取Animation实例,然后调用Animation实例的方法实现动画效果。 表40:wx.createAnimation()方法的常用选项 选项 类型 说明 duration number 动画持续时间,单位为毫秒,默认值为400毫秒 timing…

【计算机视觉】手势识别

手势识别是计算机视觉领域中的重要方向,通过对摄像机采集的手部相关的图像序列进行分析处理,进而识别其中的手势,手势被识别后用户就可以通过手势来控制设备或者与设备交互。完整的手势识别一般有手的检测和姿态估计、手部跟踪和手势识别等。…

HTTP四次挥手是什么?

四次挥手,这是TCP协议用来关闭连接的过程。四次挥手是确保两个主机之间能够安全、可靠地关闭连接的重要机制。我会用简单易懂的方式来讲解,帮助你理解它的原理和过程。 1. 什么是四次挥手? 定义 四次挥手是TCP协议用来关闭连接的过程。它通…

泵吸式激光可燃气体监测仪:快速精准守护燃气管网安全

在城市化进程加速的今天,燃气泄漏、地下管网老化等问题时刻威胁着城市安全。如何实现精准、高效的可燃气体监测,守护“城市生命线”,成为新型基础设施建设的核心课题。泵吸式激光可燃气体监测仪,以创新科技赋能安全监测&#xff0…

C# Unity 面向对象补全计划 之 索引器与迭代器

本文仅作学习笔记与交流,不作任何商业用途,作者能力有限,如有不足还请斧正 本篇有部分内容出自唐老狮,唐老师网站指路:全部 - 游习堂 - 唐老狮创立的游戏开发在线学习平台 - Powered By EduSoho 目录 1.索引器 2.迭代器 1.索引器 我的理解 索…

ElasticSearch 在半导体工厂中的智能应用与 AI 联动

您是否曾想过如何将 ElasticSearch 这一强大的搜索和分析引擎应用于半导体工厂的智能生产中?本文将为您揭示 ElasticSearch 在半导体行业的应用场景、与 AI 和向量数据库的结合方式,以及与开源 AI(如 DeepSeek)的联动方法&#xf…

突破传统:用Polars解锁ICU医疗数据分析新范式

一、ICU数据革命的临界点 在重症监护室(ICU),每秒都在产生关乎生死的关键数据:从持续监测的生命体征到高频更新的实验室指标,从呼吸机参数到血管活性药物剂量,现代ICU每天产生的数据量级已突破TB级别。传统…