SIP系列六:SIP实战(基于eXosip实现UAC、UAS)

        我的音视频/流媒体开源项目(github)

        SIP系列目录

目录

一、SIP开源库介绍

二、安装eXosip

三、API介绍

3.1、osip

3.2、eXosip

四、UAC、UAS示例


 

一、SIP开源库介绍

        介绍几个常见的基于C/C++实现的SIP开源库:

        eXosip:C语言实现,eXosip基于是osip扩展的,eXosip对osip进行了二次封装。eXosip是一个较轻量级的SIP协议栈,专注于SIP协议的基础功能,适合需要SIP通信(如呼叫、注册、消息等)但不需要复杂多媒体功能的应用。eXosip的设计简洁,主要提供SIP消息的处理和事务管理,适合快速开发基于SIP的应用。

        PJSIP:C语言实现,PJSIP是一个全功能的多媒体通信库,不仅实现了SIP协议,还包括RTP、STUN、TURN、ICE等协议,适合需要音频、视频、即时消息等多种通信功能的项目。

        eXosip2和PJSIP对比入下表所示:

特点 PJSIP eXosip
功能 完整的SIP、RTP、STUN、TURN、ICE、多媒体支持。 主要支持SIP协议,不包括多媒体功能。
性能 高性能,适合复杂多媒体应用。 轻量级,适合简单SIP应用。
易用性 相对复杂,功能丰富,适合大规模应用。 简单易用,适合快速实现SIP通信功能。
适用场景 音视频通话、软电话、视频会议、大规模商业应用。 SIP客户端、服务器、代理、嵌入式系统。

        本篇文章将基于eXosip实现UAC、UAS进行SIP通信。

        osip具有以下特点:

  • 不带传输层:osip 本身不包含传输层的功能,用户需要自行管理 socket 和 协议栈 的细节。开发者需要手动管理网络层的套接字,决定如何处理SIP消息的发送和接收。
  • 无SSL支持:不支持SSL/TLS加密。
  • 无线程支持:osip 不包含内置的线程管理,用户需要自行决定如何处理并发和多线程问题。
  • 回调机制:osip 提供了灵活的回调机制,开发者需要注册一系列回调函数来处理SIP消息的各种事件(如收到请求、响应、状态变化等)。这些回调函数允许开发者自定义处理流程。
  • 适用于需要完全控制SIP协议的底层实现,特别是那些需要高度定制SIP协议栈和传输层的开发者。

        eXosip具有以下特点:

  • 带传输层:eXosip 在其封装中集成了 传输层管理,自动处理套接字(socket)和协议栈。用户无需自己管理网络层的细节,eXosip 提供了透明的网络层管理。
  • 有SSL支持:eXosip 自带 SSL/TLS支持。
  • 内置线程支持:eXosip 默认提供了一个会话状态机的运行线程 eXosip_thread,它自动管理SIP会话的生命周期和状态。
  • 封装回调机制:eXosip 已经封装好了回调机制,开发者无需手动注册每个回调函数,而是通过 eXosip 的高层接口来自动处理SIP消息。
  • 自动状态机管理:eXosip 内置了会话状态机,并且为每个SIP会话提供了自动化的状态管理。开发者无需手动管理每个会话的状态机,eXosip 会自动处理状态转换。
  • 适用于快速开发SIP应用,尤其是那些不需要对SIP协议栈做太多定制的场景。

 

二、安装eXosip

        下载地址(eXosip和osip版本保持一致,版本是eXosip2和osip2,2x版本与1x版本API和变量名不一样,eXosip和osip源码第2版本命名规则为2-x.x.x):

osip:http://ftp.twaren.net/Unix/NonGNU/osip/
eXosip:http://download.savannah.gnu.org/releases/exosip/

        这里下载的是5.1.2

        安装osip:

wget http://ftp.twaren.net/Unix/NonGNU/osip/libosip2-5.1.2.tar.gz

tar -zxvf libosip2-5.1.2.tar.gz

cd libosip2-5.1.2

./configure

make 

make install

        安装eXosip:

wget http://download.savannah.gnu.org/releases/exosip/libexosip2-5.1.2.tar.gz

tar -zxvf libexosip2-5.1.2.tar.gz

cd libexosip2-5.1.2

./configure --disable-openssl # 启用openssl可能报错

make 

make install

三、API介绍

3.1、osip

        在使用eXosip实现UAC、UAS的时候,大部分osip的API都被eXosip隐藏了,我们只需要使用osip的API对SIP消息进行封装和解析。

        osip源码包包含两个库,分别是osip2和osipparser2。

        osip2 是 osip 协议栈的核心模块,负责 SIP 协议的实现,具体包括 SIP 消息的构建、解析、事务管理和会话管理等。
        osipparser2 是 osip 的消息解析器模块,主要负责 SIP 消息的解析和处理。它实现了 SIP 消息的格式解析和拆解,属于 osip 的低层模块。

        首先看一下osip中几个重要的结构体:

osip_t
osip_message_t
osip_dialog_t
osip_transaction_t

        osip_t:是一个全局变量,所有要使用Osip协议栈的事务处理能力的程序都要第一步就初始化它。它内部主要是定义了Osip 协议栈的四个主要事务链表、消息实际发送函数及状态机各状态事件下的回调函数等。

        osip_message_t:记录SIP消息,收到SIP消息解析后存在该结构中方便程序使用接收到的消息中的指定的字段,发送消息前为方便设置要发送的字段值,将要发送的内容存在该结构中等发送时转为字符串。

        osip_dialog_t:记录dialog,它标识了uac和uas的一对关系,并一直保持到会话(session)结束,一个完整的dialog主要包括from、to、call id、from tag、to tag等,其中call id、from tag、to tag在一个dialog成功建立后才完整,在SIP中From、To的tag,Call-id字段的值相同就表明这些消息是属于同一个Dialog的,成功建立一个dialog之后,两个SIP用户的后继逻辑均是使用这个dialog进行处理(如transaction事务处理)。

        osip_transaction_t:记录SIP中的一个事务,例如 invite-100-180-200-ack这是一个完整的事务,bye-200这也是一个完整的事务,SIP消息中Via的 branch的值相同表示属于一个事务的消息(事务是在Dialog中的,所以From、To的tag,Call-id值也是相同的),事务对于 UAC、UAS的终端类型不同及消息的不同,分为四类,对于invite事务,主叫uac中会关联一个ict事务,被叫uas会关联一个ist事务,而除了invite之外,主叫关联nict,被叫关联nist,osip靠有限状态机来实现的上述四种事务(osip_fsm_type_t中定义)。

        接下来看下简单的API调用

        1、创建 SIP 协议栈对象:

osip_t* osip = NULL;
osip_init(&osip);

        2、解析消息:

// osip各种消息(REGISTER、INVITE、ACK、BYE)都定义为osip_message_t
osip_message_t* sip_msg = NULL;
osip_message_init(&sip_msg);
osip_message_parse(sip_msg, sip_message_str, strlen(sip_message_str));

        3、获取 SIP 消息头信息:

osip_call_id_t* call_id = osip_message_get_call_id(sip_msg);
osip_from_t* from = osip_message_get_from(sip_msg);
osip_to_t* to = osip_message_get_to(sip_msg);
osip_via_t* via = osip_message_get_via(sip_msg, 0);
osip_cseq_t* cseq = osip_message_get_cseq(sip_msg);

        4、生成SIP消息:

osip_message_t* sip_msg = NULL;
osip_message_init(&sip_msg);
 
osip_call_id_t* call_id = NULL;
osip_call_id_init(&call_id);
osip_call_id_generate(call_id);
osip_message_set_call_id(sip_msg, call_id);
 
osip_from_t* from = NULL;
osip_from_init(&from);
osip_from_set_url(from, osip_uri_parse("sip:from@domain.com"));
osip_message_set_from(sip_msg, from);
 
osip_to_t* to = NULL;
osip_to_init(&to);
osip_to_set_url(to, osip_uri_parse("sip:to@domain.com"));
osip_message_set_to(sip_msg, to);
 
osip_cseq_t* cseq = NULL;
osip_cseq_init(&cseq);
osip_cseq_set_method(cseq, "INVITE");
osip_cseq_set_seq(cseq, 1);
osip_message_set_cseq(sip_msg, cseq);
 
osip_via_t* via = NULL;
osip_via_init(&via);
osip_via_set_host(via, "192.168.1.1");
osip_via_set_port(via, 5060);
osip_via_set_protocol(via, "UDP");
osip_message_add_via(sip_msg, via);
 
osip_message_to_str(sip_msg, &sip_message_str);

        5、销毁 SIP 协议栈对象:

osip_quit(osip);

3.2、eXosip

        关于osip,感兴趣的可以去看一下源码,这里重点介绍以下eXosip,首先看一下eXosip的源码结构:

底层连接管理:
        extl.c、extludp.c、extl_tcp.c、extl_dtls.c、extl_tls.c 是与网络连接有关的文件。实现了连接的建立,数据的接收以及发送等相关的接口。其中,extludp.c为使用UDP连接的实现,extl_tcp.c为使用TCP连接的实现。extl_dtls.c以及extl_tls.c都是使用安全socket 连接的实现。
部功能模块实现:
        jauth.c、jcall.c、jdialog.c、jevents.c、jnotify.c、jpublish.c、jreg.c、jrequest.c、iresponse.c、jsubscribe.c等文件实现了部对一些模块的管理,这些模块正如其文件名所表示的,jauth.c主要是认证,jcall.c则是通话等等。
上层API 封装实现:
        excall_api.c、exinsubsription_api.c、exmessag_api.c、exoptions_api.c、expublish_api.c、exrefer_api.c、exregister_api.c、exsubsribtion_api.c 这几个以api为后缀的文件,实现各个子模块的管理。应用程序可以调用这里提供的接口,方便的构造或者发送sip 消息。
其他:
        inet_ntop.c 实现ip 地址的点分十进制与十六进制表示之间的转换。
        jcallback.c实现一堆回调函数,这些回调函数就是用来注册到osip库的。我们使用eXosip库,就是避免直接使用osip库,因为一些工作eXosip已经帮我们做了,所以这样一来,可以简化上层的实现。
        udp.c文件主要用来对通过UDP连接接收到的消息进行分类处理。
        exutilis.c文件实现一些杂项的函数。有ip地址到字符串之间的转换,域名的解析等一些辅助的功能函数。
        exconf.c文件实现了eXosip初始化相关的接口,包括后台任务的实现。实际上是“configuration api”的实现。
        exosip.c文件实现了与exconf.c文件相似的功能。比如管道的使用,eXosip上事务的创建和查找,register和subscribe的更新,认证信息的处理等。

        eXosip详细介绍可参考国标28181:libexosip2协议栈原理-CSDN博客

        eXosip初始化流程:

#include "stdio.h"
#include <eXosip2/eXosip.h>
#include <iostream>
#include <rpc/types.h>

using namespace std;


int main(void)
{
    int listenport = 8888;
    //库处理结果
    int result = OSIP_SUCCESS;
    TRACE_INITIALIZE (6, stdout);
    eXosip_t *ctx = eXosip_malloc();
    result = eXosip_init(ctx);
    if (OSIP_SUCCESS != result)
    {
        printf("Can't initialize eXosip!\n");
        return 1;
    }
    printf("eXosip_init successfully!\n");
    //监听
    result = eXosip_listen_addr(ctx, IPPROTO_UDP,NULL,listenport,AF_INET,0);
    if (OSIP_SUCCESS != result){
        printf("eXosip_listen_addr failure.\n");
        return 1;
    }
    printf("eXosip_listen_addr successfully.\n");
    // 结束
    eXosip_quit(ctx);
    ctx = nullptr;

    return 0;
}

        eXosip数据接收流程:

//开启循环消息,实际应用中可以开启多线程同时接收信号
    eXosip_event_t* osipEventPtr = NULL;

    while (true)
    {
        // Wait the osip event.
        osipEventPtr = ::eXosip_event_wait(ctx, 0, 200);
        eXosip_lock();
        // 让库处理一些默认操作:处理401-客户端授权失败;407-代理服务器授失败;3xx-重定向
        eXosip_default_action(ctx, osipEventPtr);
        eXosip_unlock();
        // If get nothing osip event,then continue the loop.
        if (NULL == osipEventPtr)
        {
            continue;
        }
        // 事件处理
        switch (osipEventPtr->type)
        {
            case EXOSIP_REGISTRATION_NEW:
                ...
                break;
            case EXOSIP_MESSAGE_NEW:
                ...
                break;
            case XXXXX:
                ...
                break;
           case XXXXX:
                ...
                break;
            default:
                cout << "未处理消息 : " << osipEventPtr->type<<endl;
                break;
        }
        eXosip_event_free(osipEventPtr);
        osipEventPtr = NULL;
    }

        要发送数据时,根据消息类型,调用exosip对应模块的api接口函数来完。

        eXosip事件类型定义为eXosip_event_type_t,根据不同的消息类型进行不同的处理:

typedef enum eXosip_event_type {
  /* REGISTER related events */
  // 注册相关事件
  EXOSIP_REGISTRATION_SUCCESS, /**< user is successfully registred.  */
  EXOSIP_REGISTRATION_FAILURE, /**< user is not registred.           */

  /* INVITE related events within calls */
  // INVITE事务内的事件 invite-100-180-200-ack
  EXOSIP_CALL_INVITE,   /**< announce a new call                   */
  EXOSIP_CALL_REINVITE, /**< announce a new INVITE within call     */

  EXOSIP_CALL_NOANSWER,       /**< announce no answer within the timeout */
  EXOSIP_CALL_PROCEEDING,     /**< announce processing by a remote app   */
  EXOSIP_CALL_RINGING,        /**< announce ringback                     */
  EXOSIP_CALL_ANSWERED,       /**< announce start of call                */
  EXOSIP_CALL_REDIRECTED,     /**< announce a redirection                */
  EXOSIP_CALL_REQUESTFAILURE, /**< announce a request failure            */
  EXOSIP_CALL_SERVERFAILURE,  /**< announce a server failure             */
  EXOSIP_CALL_GLOBALFAILURE,  /**< announce a global failure             */
  EXOSIP_CALL_ACK,            /**< ACK received for 200ok to INVITE      */

  EXOSIP_CALL_CANCELLED, /**< announce that call has been cancelled */

  /* request related events within calls (except INVITE) */
  // INVITE事务外但是同一个dailog中的事件,例如BYE、INFO方法
  EXOSIP_CALL_MESSAGE_NEW,            /**< announce new incoming request. */
  EXOSIP_CALL_MESSAGE_PROCEEDING,     /**< announce a 1xx for request. */
  EXOSIP_CALL_MESSAGE_ANSWERED,       /**< announce a 200ok  */
  EXOSIP_CALL_MESSAGE_REDIRECTED,     /**< announce a failure. */
  EXOSIP_CALL_MESSAGE_REQUESTFAILURE, /**< announce a failure. */
  EXOSIP_CALL_MESSAGE_SERVERFAILURE,  /**< announce a failure. */
  EXOSIP_CALL_MESSAGE_GLOBALFAILURE,  /**< announce a failure. */

  EXOSIP_CALL_CLOSED, /**< a BYE was received for this call      */

  /* for both UAS & UAC events */
  EXOSIP_CALL_RELEASED, /**< call context is cleared.            */

  /* events received for request outside calls */
  // dialog之外的事件,不需要INVITE建立会话即可发送的消息,例如MESSAGE、REGISTER方法 
  EXOSIP_MESSAGE_NEW,            /**< announce new incoming request. */
  EXOSIP_MESSAGE_PROCEEDING,     /**< announce a 1xx for request. */
  EXOSIP_MESSAGE_ANSWERED,       /**< announce a 200ok  */
  EXOSIP_MESSAGE_REDIRECTED,     /**< announce a failure. */
  EXOSIP_MESSAGE_REQUESTFAILURE, /**< announce a failure. */
  EXOSIP_MESSAGE_SERVERFAILURE,  /**< announce a failure. */
  EXOSIP_MESSAGE_GLOBALFAILURE,  /**< announce a failure. */

  /* Presence and Instant Messaging */
  // SUBSCRIBE 和 NOTIFY相关的事件
  EXOSIP_SUBSCRIPTION_NOANSWER,       /**< announce no answer              */
  EXOSIP_SUBSCRIPTION_PROCEEDING,     /**< announce a 1xx                  */
  EXOSIP_SUBSCRIPTION_ANSWERED,       /**< announce a 200ok                */
  EXOSIP_SUBSCRIPTION_REDIRECTED,     /**< announce a redirection          */
  EXOSIP_SUBSCRIPTION_REQUESTFAILURE, /**< announce a request failure      */
  EXOSIP_SUBSCRIPTION_SERVERFAILURE,  /**< announce a server failure       */
  EXOSIP_SUBSCRIPTION_GLOBALFAILURE,  /**< announce a global failure       */
  EXOSIP_SUBSCRIPTION_NOTIFY,         /**< announce new NOTIFY request     */

  EXOSIP_IN_SUBSCRIPTION_NEW, /**< announce new incoming SUBSCRIBE/REFER.*/

  EXOSIP_NOTIFICATION_NOANSWER,       /**< announce no answer              */
  EXOSIP_NOTIFICATION_PROCEEDING,     /**< announce a 1xx                  */
  EXOSIP_NOTIFICATION_ANSWERED,       /**< announce a 200ok                */
  EXOSIP_NOTIFICATION_REDIRECTED,     /**< announce a redirection          */
  EXOSIP_NOTIFICATION_REQUESTFAILURE, /**< announce a request failure      */
  EXOSIP_NOTIFICATION_SERVERFAILURE,  /**< announce a server failure       */
  EXOSIP_NOTIFICATION_GLOBALFAILURE,  /**< announce a global failure       */

  EXOSIP_EVENT_COUNT /**< MAX number of events              */
} eXosip_event_type_t;

四、UAC、UAS示例

        uac.c

#include <eXosip2/eXosip.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

// gcc uac.c -o uac -losip2 -leXosip2 -lpthread -L/usr/local/lib -losipparser2
struct eXosip_t *context_eXosip;

int main(int argc, char *argv[])
{
    eXosip_event_t *je;
    osip_message_t *reg = NULL;
    osip_message_t *invite = NULL;
    osip_message_t *ack = NULL;
    osip_message_t *info = NULL;
    osip_message_t *message = NULL;

    int call_id, dialog_id;
    int i, flag;
    int flag1 = 1;
    int id;

    char *identity = "sip:140@127.0.0.1:15060";
    char *registerer = "sip:127.0.0.1:15061";
    char *source_call = "sip:140127.0.0.1:15060";
    char *dest_call = "sip:133@127.0.0.1:15061";

    char command;
    char tmp[4096];
    char localip[128];

    printf("r     register Server \r\n");
    printf("c     cancle register \r\n");
    printf("i     invite \r\n");
    printf("h     hold \r\n");
    printf("q     quit \r\n");
    printf("s         excute INFO \r\n");
    printf("m     excute MESSAGE \r\n");

    context_eXosip = eXosip_malloc();
    // 初始化
    i = eXosip_init(context_eXosip);
    if (i != 0)
    {
        printf("Couldn't initialize eXosip!\n");
        return -1;
    }
    else
    {
        printf("eXosip_init successfully!\n");
    }

    i = eXosip_listen_addr(context_eXosip, IPPROTO_UDP, NULL, 15060, AF_INET, 0);
    if (i != 0)
    {
        eXosip_quit(context_eXosip);
        fprintf(stderr, "Couldn't initialize transport layer!\n");
        return -1;
    }
    flag = 1;
    while (flag)
    {
        printf("please input the comand:\n");

        scanf("%c", &command);
        getchar();

        switch (command)
        {
        case 'r':
            printf("This modal isn't commpleted!\n");
            break;
        case 'i': /* INVITE */
            i = eXosip_call_build_initial_invite(context_eXosip, &invite, dest_call, source_call, NULL, "This si a call for a conversation");
            if (i != 0)
            {
                printf("Intial INVITE failed!\n");
                break;
            }
            // 符合SDP格式,其中属性a是自定义格式,也就是说可以存放自己的信息
            // 但是经测试,格式:v o t必不可少,原因未知,估计是协议栈在传输时需要检查的
            snprintf(tmp, 4096,
                     "v=0\r\n"
                     "o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
                     "t=1 10\r\n"
                     "a=username:rainfish\r\n"
                     "a=password:123\r\n");
            osip_message_set_body(invite, tmp, strlen(tmp));
            osip_message_set_content_type(invite, "application/sdp");

            eXosip_lock(context_eXosip);
            i = eXosip_call_send_initial_invite(context_eXosip, invite);
            eXosip_unlock(context_eXosip);
            flag1 = 1;
            while (flag1)
            {
                je = eXosip_event_wait(context_eXosip, 0, 200);

                if (je == NULL)
                {
                    printf("No response or the time is over!\n");
                    break;
                }

                switch (je->type)
                {
                case EXOSIP_CALL_INVITE:
                    printf("a new invite reveived!\n");
                    break;
                case EXOSIP_CALL_PROCEEDING:
                    printf("proceeding!\n");
                    break;
                case EXOSIP_CALL_RINGING:
                    printf("ringing!\n");
                    printf("call_id is %d, dialog_id is %d \n", je->cid, je->did);
                    break;
                case EXOSIP_CALL_ANSWERED:
                    printf("ok! connected!\n");
                    call_id = je->cid;
                    dialog_id = je->did;
                    printf("call_id is %d, dialog_id is %d \n", je->cid, je->did);

                    eXosip_call_build_ack(context_eXosip, je->did, &ack);
                    eXosip_call_send_ack(context_eXosip, je->did, ack);
                    flag1 = 0;
                    break;
                case EXOSIP_CALL_CLOSED:
                    printf("the other sid closed!\n");
                    break;
                case EXOSIP_CALL_ACK:
                    printf("ACK received!\n");
                    break;
                default:
                    printf("other response!\n");
                    break;
                }
                eXosip_event_free(je);
            }
            break;
        case 'h':
            printf("Holded !\n");

            eXosip_lock(context_eXosip);
            eXosip_call_terminate(context_eXosip, call_id, dialog_id);
            eXosip_unlock(context_eXosip);
            break;
        case 'c':
            printf("This modal isn't commpleted!\n");
            break;
        case 's':
            // 传输INFO方法
            eXosip_call_build_info(context_eXosip, dialog_id, &info);
            snprintf(tmp, 4096,
                     "hello,rainfish");
            osip_message_set_body(info, tmp, strlen(tmp));
            // 格式可以任意设定,text/plain代表文本信息
            osip_message_set_content_type(info, "text/plain");
            eXosip_call_send_request(context_eXosip, dialog_id, info);
            break;
        case 'm':
            // 传输MESSAGE方法,也就是即时消息,和INFO方法相比,MESSAGE不用建立连接,直接传输信息,而INFO必须在建立INVITE的基础上传输。
            printf("the mothed :MESSAGE\n");
            eXosip_message_build_request(context_eXosip, &message, "MESSAGE", dest_call, source_call, NULL);
            snprintf(tmp, 4096,
                     "hellor rainfish");
            osip_message_set_body(message, tmp, strlen(tmp));
            // 假设格式是xml
            osip_message_set_content_type(message, "text/xml");
            eXosip_message_send_request(context_eXosip, message);
            break;
        case 'q':
            eXosip_quit(context_eXosip);
            printf("Exit the setup!\n");
            flag = 0;
            break;
        }
    }
    return 0;
}

        uas.c

#include <eXosip2/eXosip.h>
#include <osip2/osip_mt.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>

// gcc uas.c -o uas -losip2 -leXosip2 -lpthread -L/usr/local/lib -losipparser2

struct eXosip_t *context_eXosip;

int main(int argc, char *argv[])
{
    eXosip_event_t *je = NULL;
    osip_message_t *ack = NULL;
    osip_message_t *invite = NULL;
    osip_message_t *answer = NULL;
    sdp_message_t *remote_sdp = NULL;

    int call_id, dialog_id;
    int i, j;
    int id;
    char *sour_call = "sip:133@127.0.0.1:15061";
    char *dest_call = "sip:140@192.168.0.140:15060";

    char command;
    char tmp[4096];
    char localip[128];

    int pos = 0;

    // 初始化sip
    context_eXosip = eXosip_malloc();
    i = eXosip_init(context_eXosip);
    if (i != 0)
    {
        printf("Can't initialize eXosip!\n");
        return -1;
    }
    else
    {
        printf("eXosip_init successfully!\n");
    }

    i = eXosip_listen_addr(context_eXosip, IPPROTO_UDP, NULL, 15061, AF_INET, 0);
    if (i != 0)
    {
        eXosip_quit(context_eXosip);
        fprintf(stderr, "eXosip_listen_addr error!\nCouldn't initialize transport layer!\n");
    }

    for (;;)
    {
        // 侦听是否有消息到来
        je = eXosip_event_wait(context_eXosip, 0, 50);

        // 处理401 407 3xx响应
        eXosip_lock(context_eXosip);
        eXosip_default_action(context_eXosip, je);
        eXosip_unlock(context_eXosip);

        if (je == NULL) // 没有接收到消息
            continue;
        // printf ("the cid is %s, did is %s/n", je->did, je->cid);
        switch (je->type)
        {
        case EXOSIP_MESSAGE_NEW: // 新的消息到来
            printf(" EXOSIP_MESSAGE_NEW!\n");
            if (MSG_IS_MESSAGE(je->request)) // 如果接受到的消息类型是MESSAGE
            {
                {
                    osip_body_t *body;
                    osip_message_get_body(je->request, 0, &body);
                    printf("I get the msg is: %s\n", body->body);
                    // printf ("the cid is %s, did is %s/n", je->did, je->cid);
                }
                // 按照规则,需要回复200 OK信息
                eXosip_message_build_answer(context_eXosip, je->tid, 200, &answer);
                eXosip_message_send_answer(context_eXosip, je->tid, 200, answer);
            }
            break;
        case EXOSIP_CALL_INVITE:
            // 得到接收到消息的具体信息
            printf("Received a INVITE msg from %s:%s, UserName is %s, password is %s\n", je->request->req_uri->host,
                   je->request->req_uri->port, je->request->req_uri->username, je->request->req_uri->password);
            // 得到消息体,认为该消息就是SDP格式.
            remote_sdp = eXosip_get_remote_sdp(context_eXosip, je->did);
            call_id = je->cid;
            dialog_id = je->did;

            eXosip_lock(context_eXosip);
            eXosip_call_send_answer(context_eXosip, je->tid, 180, NULL);
            i = eXosip_call_build_answer(context_eXosip, je->tid, 200, &answer);
            if (i != 0)
            {
                printf("This request msg is invalid!Cann't response!\n");
                eXosip_call_send_answer(context_eXosip, je->tid, 400, NULL);
            }
            else
            {
                snprintf(tmp, 4096,
                         "v=0\r\n"
                         "o=anonymous 0 0 IN IP4 0.0.0.0\r\n"
                         "t=1 10\r\n"
                         "a=username:rainfish\r\n"
                         "a=password:123\r\n");

                osip_message_set_body(answer, tmp, strlen(tmp));
                osip_message_set_content_type(answer, "application/sdp");

                eXosip_call_send_answer(context_eXosip, je->tid, 200, answer);
                printf("send 200 over!\n");
            }
            eXosip_unlock(context_eXosip);

            printf("the INFO is :\n");
            while (!osip_list_eol(&remote_sdp->a_attributes, pos))
            {
                sdp_attribute_t *at;

                at = (sdp_attribute_t *)osip_list_get(&remote_sdp->a_attributes, pos);
                printf("%s : %s\n", at->a_att_field, at->a_att_value);

                pos++;
            }
            break;
        case EXOSIP_CALL_ACK:
            printf("ACK recieved!\n");
            // printf ("the cid is %s, did is %s/n", je->did, je->cid);
            break;
        case EXOSIP_CALL_CLOSED:
            printf("the remote hold the session!\n");
            // 这个版本库会自动回复200ok,不需要再次回复
            // i = eXosip_call_build_answer(context_eXosip, je->tid, 200, &answer);
            // if (i != 0)
            // {
            //     printf("This request msg is invalid!Cann't response!\n");
            //     eXosip_call_send_answer(context_eXosip, je->tid, 400, NULL);
            // }
            // else
            // {
            //     eXosip_call_send_answer(context_eXosip, je->tid, 200, answer);
            //     printf("bye send 200 over!\n");
            // }
            break;

        case EXOSIP_CALL_MESSAGE_NEW:
            /*
            // request related events within calls (except INVITE)
                  EXOSIP_CALL_MESSAGE_NEW,          < announce new incoming request.
            // response received for request outside calls
                     EXOSIP_MESSAGE_NEW,          < announce new incoming request.
            EXOSIP_CALL_MESSAGE_NEW是一个dialog的新的消息到来,invite事务内的不算
            EXOSIP_MESSAGE_NEW而是表示不是呼叫内的消息到来。例如MESSAGE,MESSAGE不需要INVITE建立会话即可直接发送
            */
            printf(" EXOSIP_CALL_MESSAGE_NEW\n");
            if (MSG_IS_INFO(je->request)) // 如果传输的是INFO方法
            {
                eXosip_lock(context_eXosip);
                i = eXosip_call_build_answer(context_eXosip, je->tid, 200, &answer);
                if (i == 0)
                {
                    eXosip_call_send_answer(context_eXosip, je->tid, 200, answer);
                }
                eXosip_unlock(context_eXosip);
                {
                    osip_body_t *body;
                    osip_message_get_body(je->request, 0, &body);
                    printf("the body is %s\n", body->body);
                }
            }
            break;
        default:
            printf("Could not parse the msg!\n");
        }
    }
}

        代码里有些API调用需要加eXosip_lock和eXosip_unlock保证线程安全,但是官方文档中并没有说明哪些操作需要加锁,结合案例和源码,我猜测加锁的API满足几个条件:

        1、只有数据发送才可能需要加锁,因为发送数据会改变库的状态机,对于数据的构建和解析这种常规操作不需要加锁。

        2、不是所有的数据发送API都需要加锁,例如:eXosip_call_send_initial_invite需要加锁,但是eXosip_call_send_ack不需要加锁,进到源码看,上面uac、uas的代码需要加锁的都是API内部调用了_eXosip_update函数,没加锁的数据发送API内部没有调用_eXosip_update函数,但是有一个特例:eXosip_default_action和eXosip_automatic_action内部没有调用_eXosip_update函数函数,也需要加锁。

        关于eXosip_lock这部分我也不清楚,上面只是我的猜测,有懂的朋友,麻烦在评论区说明一下。

        执行以下两个编译命令:

gcc uac.c -o uac -losip2 -leXosip2 -lpthread -L/usr/local/lib -losipparser2

gcc uas.c -o uas -losip2 -leXosip2 -lpthread -L/usr/local/lib -losipparser2

        运行之前导入环境变量:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib

 

posted @   BreakingY  阅读(60)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示