【GB28181】 SIP信令服务器

ops/2025/3/4 1:58:39/

概述

本文仅总结关于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/ops/162925.html

相关文章

【人工智能】数据挖掘与应用题库(201-300)

1、在LetNet5网络中,卷积核的大小是? 答案:5*5 2、LeNet5网络参数的数量约为? 答案:6万 3、AlexNet与LeNet5相比,使用了哪些机制来改进模型的训练过程? 答案: 数据增广Dropout抑制过拟合ReLU激活函数CUDA加速神经网络训练4、VGGNet使用的卷积核的大小是? 答案:…

Conda 全面使用指南:从基础操作到高级优化

一、Conda 简介 Conda 是一个开源的包、依赖项和环境管理系统,可在 Windows、macOS 和 Linux 上运行。它最初是为 Python 程序创建的,但可以打包和分发任何语言的软件。Conda 有 Anaconda 和 Miniconda 两种发行版,Anaconda 包含大量常用的数…

手机跑大模型不是梦!Deepseek部署全攻略

引言 在人工智能飞速发展的当下,大语言模型已成为推动各领域创新的关键力量。Deepseek 作为其中的佼佼者,以其强大的语言理解与生成能力,吸引了众多开发者与用户的目光。通常,我们在电脑上部署和使用 Deepseek,享受其带…

Android-创建mipmap-anydpi-v26的Logo

利用 Android Studio 自动创建 创建新项目:打开 Android Studio,点击 “Start a new Android Studio project” 创建新项目。在创建项目的过程中,当设置Target SDK Version为 26 或更高版本时,Android Studio 会在项目的res目录下…

网络原理----TCP/IP(3)

核心机制七----延时应答 默认情况下,接收方都是在收到数据报的第一时间,就返回ack,但是可以通过延时返回ack的方式来提高效率,理论上不是100%提高效率,但还是有一定帮助的。 因为如果接收数据的主机⽴刻返回ACK应答,…

阿里云的 ECS(Elastic Compute Service)实例

阿里云的 ECS(Elastic Compute Service)实例 是一种高可扩展、灵活的计算服务,允许用户在云上运行虚拟机。通过ECS,用户可以在阿里云的云基础设施上启动、配置和管理虚拟服务器(实例),这些实例具…

Leetcode-最大矩形(单调栈)

一、题目描述 给定一个仅包含 0 和 1 、大小为 rows x cols 的二维二进制矩阵,找出只包含 1 的最大矩形,并返回其面积。 输入:matrix [["1","0","1","0","0"],["1","0&…

TypeScript 类型声明

在 TypeScript 开发中简化类型声明,可以通过以下 7 种实用技巧 显著提升效率: 一、善用类型推断(30% 场景免声明) // ❌ 冗余写法 const user: { name: string; age: number } { name: Jack, age: 25 };// ✅ 自动推断&#xff…