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

相关推荐
糯米汤圆~1 分钟前
LVS+Keepalived 高可用集群搭建
服务器·网络·lvs
m0_7482449610 分钟前
RustDesk搭建公网中继服务器远控内网机器(完整版)
运维·服务器
平生不喜凡桃李44 分钟前
浅谈Linux中的软件包管理器——基于ubuntu环境
linux·运维·ubuntu
我们的五年1 小时前
内网穿透:打破网络限制的利器
服务器·网络·生活
Channing Lewis1 小时前
如何用python将pdf转为text并提取其中的图片
服务器·python·pdf
m0_748251352 小时前
Mac安装配置使用nginx的一系列问题
运维·nginx·macos
cd小白2 小时前
IO进程 day05
linux·服务器·c语言·网络·io进程
web150854159352 小时前
Nginx 配置前端后端服务
运维·前端·nginx
A星空1233 小时前
Linux mount命令
linux·运维·服务器
丁卯4044 小时前
Linux(centOS) 命令提示符格式修改(PS1)
linux·运维·服务器·centos