【GB28181】 SIP信令服务器

概述

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

主要逻辑梳理

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

框架分析

事件循环的基本框架

cpp 复制代码
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) 分发处理
    • 最后释放该事件即可

事件处理逻辑

事件处理逻辑的总体框架

cpp 复制代码
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;
}

新呼叫

打印消息到日志上

代码实现

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

// 写入日志函数实现
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信息输入到到日志中

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

呼叫释放事件

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

新的消息请求事件

  • 判断新消息的类型
    • 如果是注册消息,则调用注册逻辑
    • 如果是即时消息,那么解析该消息,然后回复200OK
cpp 复制代码
case EXOSIP_MESSAGE_NEW://23
    LOGI("EXOSIP_MESSAGE_NEW type=%d",evtp->type); // 代码行 121

    if (MSG_IS_REGISTER(evtp->request)) { // 代码行 123
        this->response_register(evtp); // 代码行 124
    }
    else if (MSG_IS_MESSAGE(evtp->request)) { // 代码行 126
        this->response_message(evtp);  // 代码行 127
    }
    else if(strncmp(evtp->request->sip_method, "BYE", 3) != 0){ // 代码行 129
        LOGE("unknown1"); // 代码行 130
    }
    else{
        LOGE("unknown2"); // 代码行 132
    }
    break;

注册消息处理

  • 客户端发送一个注册请求,其中会包含授权信息(Authorization头部)
  • 服务器提取请求中的认证信息
  • 服务器计算认证信息是否正确
  • 如果结果一致,那么此时就可以创建一个新的Client对象,然后发送200响应
  • 如果结果不一致,那么就返回401 Unauthorized响应
cpp 复制代码
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@3402000000

        osip_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防止重放攻击的随机值
cpp 复制代码
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 请求的应答事件

仅输出到日志或者客户端

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

MESSAGE 请求发送失败事件

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

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

  • 记录日志的同时,发送响应信息
cpp 复制代码
case EXOSIP_CALL_ANSWERED:// 7
    LOGI("EXOSIP_CALL_ANSWERED type=%d: The server receives an invite (SDP) confirmation reply from the client", evtp->type); // 代码行 151
    this->dump_request(evtp);  // 代码行 152
    this->dump_response(evtp); // 代码行 153
    this->response_invite_ack(evtp); // 代码行 155
    break;

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信令服务器

相关推荐
SelectDB1 天前
Litefuse 开源并推出单进程轻量模式,25 秒就能跑起来的 Agent 可观测与评估平台
运维·后端·自动化运维
zzzzzz3103 天前
9K Star 炸裂开源!这个 C 语言写的代码知识图谱,把 Linux 内核索引压缩到了 3 分钟
linux·服务器·sql
XIAOHEZIcode3 天前
Linux系统鼠标偏移常见原因以及修复方案
linux·运维·游戏
用户0328472220703 天前
如何搭建本地yum源(上)
运维
大树886 天前
金刚石散热越强,管路越先见顶
大数据·运维·服务器·人工智能·ai
摇滚侠6 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
霸道流氓气质6 天前
领域驱动设计(DDD)在 Spring Boot 微服务中的实践指南
运维·spring boot·微服务
小宇宙Zz6 天前
Maven依赖冲突
java·服务器·maven
Inhand陈工6 天前
基于台达PLC与映翰通IG502的智慧水产养殖精准投喂与远程运维解决方案
运维·人工智能·物联网·阿里云·信息与通信
酣大智6 天前
ARP代理--工作原理
运维·网络·arp·arp代理