OpenSIPS 3.1 开发手册(一)

https://www.opensips.org/Documentation/Development-Manual

目录

1. 引言

2. 整体架构

3. 内存管理

3.1  私有内存(PKG) 

3.2  共享内存(SHM) 

3.3  内存分配器

4.  解析SIP 消息

4.1  通用头域解析器

4.2  特定头域解析

4.3  解析SIP URI

4.4  解析SDP消息体


1. 引言

        本文将重点介绍OpenSIPS的通用架构,以及OpenSIPS为构建新模块/特性而暴露的所有重要组件及API。

        这份教程无意介绍 Linux / C编程技术。具备以下知识是理解本教程的先决条件:

  • Linux C 编程
  • Linux多进程编程
  • 基本网络编程概念
  • 基本SIP知识


        这份教程标的是OpenSIPS 3.1 LTS版本。

2. 整体架构

TBD \\

3. 内存管理

        OpenSIPS 有自己的内存分配器。相比系统内存分配器,它具有以下几点重要的优势:

  • 硬限制OpenSIPS使用内存的能力
  • 比系统内存分配器更具性能优势
  • 可使用多个分配器,每个分配器拥有自己的使用场景(一个标准分配器;一个内存调试分配器--帮助调试内存泄漏及内存损坏;还有一个高性能分配器--专门为多核系统上加载高并发任务而构建的)。

        此外,OpenSIPS 是一个多进程应用,在许多场景中都会使用共享内存。OpenSIPS 分配器隐藏了共享内存的实现细节,并提供了一套非常简单的API,一套非常类似系统分配器的API。

        OpenSIPS 启动时,所有自定义分配器将从操作系统申请配置的最大内存,然后在OpenSIPS后继处理消息时,在内部管理内存。从开发视角看,有两种不同类型的内存:一种是关联单个进程上下文的内存(私有内存);另一种是在所有OpenSIPS进程间共享的内存 (共享内存)。

3.1  私有内存(PKG) 

        私有内存仅作用于单个的OpenSIPS进程。由于当前进程之外对它不可见,那么管理这类内存时不需要锁机制,因此,分配私有内存比共享内存更高效。


  常见用例:在fork OpenSIPS进程之前,开发人员将一些静态变量存储在主进程的私有内存中。 Fork后,每个子进程将具有自己的私有内存块的克隆(相同的内存地址指针!)。



mem/mem.h 提供了所有私有内存相关的接口函数

/*
Parameters :
      size - size in bytes of the request private memory
Returns :
      the actual allocated buffer, or NULL is case of error
*/
void *pkg_malloc(unsigned int size);

/*
Parameters :
      buf - the buffer to be freed
*/
void pkg_free(void *buf)

/*
Parameters :
      buf - buffer that we want to reallocate
      size - the new desired buffer size
Returns :
        the new buffer address if reallocation is successful, or NULL in case of error.

Note that pkg_realloc(NULL,size) is equivalent to pkg_malloc(size)
*/
void *pkg_realloc(void *buf, unsigned int size);

3.2  共享内存(SHM) 

        所有OpenSIPS 进程都可以访问同一块共享内存。因此,通常来讲,为了保证一致性,所有共享内存的写操作都需要某种同步机制保护。

        此外,由于所有OpenSIPS进程都可以申请共享内存,那么,为了保证一致性,共享内存内部分配时有必要上锁。因此,作为一条通用的指导原则,建议把一些有意义的信息合并到一个共享内存块中,以避免造成太多的共享内存碎片(分配许多小内存块导致)。

mem/shm_mem.h 里声明了所有共享内存相关的接口函数:

/*
Parameters :
      size - size in bytes of the request shared memory
Returns :
      the actual allocated buffer, or NULL is case of error
*/
void *shm_malloc(unsigned int size);

/*
Parameters :
      buf - the buffer to be freed
*/
void shm_free(void *buf)

/*
Parameters :
      buf - buffer that we want to reallocate
      size - the new desired buffer size
Returns :
        the new buffer address if reallocation is successful, or NULL in case of error.

Note that shm_realloc(NULL,size) is equivalent to shm_malloc(size)
*/
void *shm_realloc(void *buf, unsigned int size);

3.3  内存分配器

        要了解各种分配器的优劣,请务必阅读一下这一篇博客:https://blog.opensips.org/2019/03/21/containing-memory-related-issues-in-an-easy-way-with-opensips-3-0/

4.  解析SIP 消息

        OpenSIPS 的SIP消息解析器是一个惰性解析器,具有极高的性能。其行为规范如下:

  • 收到消息时,仅解析关键的头域(比如说:最上层的Via 头域)
  • 对于已知头域类型,开发人员想要提取某个头域首次出现的值时,不需要解析整个消息,所需头域类型首次被标识时,立刻停止解析。

此外,需要重点关注以下两种解析类型:

  • 识别解析:标识头域边界,然后把头域解析为头域名和头域体两部分(比如说:解析'To : [email protected];tag=123\r\n' 头域,首先标识,判断头域存在,然后解析出头域体'[email protected];tag=123\r\n')
  • 深度解析:对指定的头域类型,解析识别头域内容的具体信息(比如说,解析上面实例中的to-tag,等等)

        从某种意义来说,所有解析都是有状态执行的,OpenSIPS知道哪个头域已经解析过,并在内部持有一个解析掩码。因此,一旦 To 头域被解析过一次之后,所有后续的To 头域解析尝试将立刻返回。
        解析器的实现通常存储于parser/ 文件夹中。

4.1  通用头域解析器

        通用SIP头域解析器接口声明在parser/msg_parser.h里。要使用的函数是:

/*
Parameters :
      msg : the SIP message that needs to be parsed – see parser/msg_parser.h for details on the struct sip_msg structure
      flags : bitmask of header types that need to be parsed
      next : specifies whether the parser should explicitly force the parsing of new headers from the provided bitmask, even though those header types were already previously found. Can be useful when trying to find a second occurrence of a header ( in the case that header can appear multiple times in a SIP message – eg. Route )

Returns :
      0 in case of error, -1 in case of error ( either header was not found or some other error occurred ).
*/
int parse_headers(struct sip_msg* msg, hdr_flags_t flags, int next);


用法实例:

if (parse_headers(req, HDR_CALLID_F|HDR_TO_F|HDR_FROM_F, 0) < 0 || !req->callid || !req->to || !req->from) {
      LM_ERR("bad request or missing CALLID/TO/FROM hdr\n");
      return -1;
}


HDR_EOH_F ,如果想要解析当前SIP消息中的所有头域,可以用它。


   parse_headers()函数根本不会复制SIP头域, sip_msg 结构体中的钩子将直接由SIP消息buffer中的指针直接填充。


        返回时,如果成功,函数将在sip_msg结构体中填充各自的钩子描述头域信息。其中hdr_field* 结构体分配的是pkg内存,SIP消息处理完毕后,将会自动释放。

        解析成功后,开发者可以这样访问头域名和头域体:
 

LM_INFO("The callid header name is %.*s and the callid header body is %.*s\n",
    req->callid->name.len, 
    req->callid->name.s,
    req->callid->bodylen.  req->callid->body.s);

4.2  特定头域解析

        对于解析特定头域及提取头域相关信息来说, parser/ 目录下包含了所有已知的实现。文件命名约定是: parser/parse_X.h 声明头域X的解析接口。

        比如说,以下是解析To头域的实现,接口声明在parser/parse_to.h 文件中:

int parse_to_header( struct sip_msg *msg)
{      
      struct to_body* to_b;

      if ( !msg->to && ( parse_headers(msg,HDR_TO_F,0)==-1 || !msg->to)) {
            LM_ERR("bad msg or missing To header\n");
            goto error;
      }

      /* maybe the header is already parsed! */
      if (msg->to->parsed)
            return 0;

      /* bad luck! :-( - we have to parse it */
      /* first, get some memory */
      to_b = pkg_malloc(sizeof(struct to_body));
      if (to_b == 0) {
            LM_ERR("out of pkg_memory\n");
            goto error;
      }

      /* now parse it!! */
      memset(to_b, 0, sizeof(struct to_body));
      parse_to(msg->to->body.s,msg->to->body.s+msg->to->body.len+1,to_b);
      if (to_b->error == PARSE_ERROR) {
            LM_ERR("bad to header\n");
            pkg_free(to_b);
            goto error;
      }

      msg->to->parsed = to_b;

      return 0;
error:
      return -1;
}

        注意:hdr_field结构体里的void *parsed元素将包含特定解析器结构,它分配一段私有内存,这段内存在SIP消息处理结束后自动释放。


        在parse_to_header()函数成功返回之后,开发者可以通过以下方式访问TO头域内容:

LM_INFO("The To header tag value is %.*s\n", get_to(msg)->tag_value.len, get_to(msg)->tag_value.s);

4.3  解析SIP URI

        OpenSIPS解析器还实现了解析SIP URI的功能。 
parser/parse_uri.h :

/*
Parameters :
      buf - the string which contains our SIP URI
      len - length of the SIP URI buffer
      uri - structure which will be populated by the function in case of success.
      See full struct sip_uri members in parser/msg_parser.h
Returns :
      0 in case of success, negative value in case of error parsing the URI
*/
int parse_uri(char *buf, int len, struct sip_uri* uri);


        parse_uri()函数没有分配任何内存,它只是以入参buf的引用填充sip_uri 结构体。

        以下是解析To URI的一个示例:
     

      /* make sure TO header is parsed before this */
      struct to_body *tb = get_to(msg);
      if (parse_uri(tb->uri.s, tb->uri.len , &tb->parsed_uri)<0) {
            LM_ERR("failed to parse To uri\n");
            return -1;
      }

      LM_INFO(“TO URI user is %.*s and TO URI domain is %.*s\n”,
      tb->parsed_uri.user.len, tb->parsed_uri.user.s,
      tb->parsed_uri.domain.len, tb->parsed_uri.domain.s);

4.4  解析SDP消息体

        OpenSIPS提供了操作SIP消息体的函数。
parser/msg_parser.h 

/*
Parameters :
      msg - the SIP message to fetch the body for
      body - output param, which will hold the body pointer inside the SIP message and the body length, or {NULL,0} in case of no body present
Returns :
      0 in case of success, or -1 in the case of parsing errors ( the function needs to internally parse all the headers in order to detect the body length ).
*/
int get_body(struct sip_msg *msg, str *body)


        parser/sdp/sdp.h 里提供了解析SDP消息体及提取各种会话信息的接口:

/*
Parameters :
      _m - the SIP message to have it's SDP parsed
Returns :
      0 in case of success, negative in case of error
*/
int parse_sdp(struct sip_msg* _m);

        这个函数内部填充_m->sdp ,sdp_info 结构的更多细节,请参考 parser/sdp/sdp.h 。

猜你喜欢

转载自blog.csdn.net/yetyongjin/article/details/106537814
3.1