Opensips 开发手册(一)OpenSIPS 3.1 Development Manual

本文参考文档:https://www.opensips.org/Documentation/Development-Manual

1.文本内容介绍

该文档的主题是OpenSIPS的一般架构以及OpenSIPS暴露出来用于构建新模块/功能的主要组件和API。它提供了一些前提条件和先决条件,以确保读者能够理解和利用这个教程。具体来说,这段话传达了以下信息:

文档的主题是OpenSIPS的一般架构和API。
该文档不旨在教授Linux/C编程,因此假定读者已经具备一定的Linux和C编程知识。
读者需要了解Linux下的多进程编程、基本的网络概念以及基本的SIP知识,以便理解文档中的内容。
文档中的教程或信息适用于OpenSIPS 3.1 LTS版本,因此可能会基于这个特定版本的OpenSIPS进行讲解和示范。
总之,这段话是为了介绍一个关于OpenSIPS的教程文档,强调了文档的主题、读者需要具备的知识和适用的OpenSIPS版本。

2.通用架构

待定

3.内存管理

接着讨论了OpenSIPS的内存管理。以下是对每个部分的解释:

1、OpenSIPS拥有自己的内存分配器。这带来了与系统内存分配器相比的一些重要优势:

  1. 能够硬限制OpenSIPS使用的内存量。
  2. 比系统内存分配器性能更好。提供多个不同用途的分配器(标准分配器、用于调试内存泄漏和内存破坏的内存调试分配器,以及专为多核系统上高度并行工作负载而构建的高性能分配器)。

2、由于OpenSIPS是一个多进程应用程序,因此在许多常见情况下需要使用共享内存。OpenSIPS的分配器还隐藏了共享内存的实现细节,并提供了一个非常简单的API,非常类似于系统分配器。

3、在OpenSIPS启动时,所有自定义分配器将从操作系统请求最大配置的OpenSIPS内存,然后在处理流量时开始内部管理这些内存。

从开发的角度来看,存在两种类型的内存:一种与单个进程的上下文相关(私有内存),另一种在所有OpenSIPS进程之间共享(共享内存)。

总的来说,这段文字解释了OpenSIPS如何管理内存以及它使用自己的内存分配器的好处,尤其是在多进程环境下,以及如何处理私有内存和共享内存。

3.1、Private (PKG) Memory

“Private (PKG) Memory” 指的是程序中的私有内存。在这个上下文中,私有内存是指程序可以独立分配和管理的内存区域,不与其他程序或进程共享。私有内存通常用于存储程序的数据和临时变量,这些数据和变量只对程序本身可见,其他程序无法访问或修改它们。

接着解释了私有内存和共享内存的不同,以及为什么在管理私有内存时不需要使用锁定机制,因此分配私有内存会更快。

  1. 私有内存是特定于单个OpenSIPS进程的: 私有内存是与一个独立的OpenSIPS进程相关联的内存。每个OpenSIPS进程都有自己的私有内存,其他进程无法直接访问或共享它。这意味着私有内存的数据只在当前进程内可见,对其他进程来说是不可见的。

  2. 无需锁定机制: 由于私有内存只在单个进程内部可见,不需要在管理这种内存时使用锁定机制。锁定机制通常用于多进程或多线程环境中,以确保多个进程或线程不会同时访问或修改共享数据,以防止竞态条件(race conditions)和数据不一致性问题。但在私有内存的情况下,因为数据只属于一个进程,所以不会有多个进程同时访问,因此不需要锁定机制。

  3. 分配私有内存更快: 由于私有内存不需要锁定机制,因此在分配私有内存时可以更快地完成。与共享内存相比,私有内存的分配和管理更加简单和高效。

综上所述,这段话强调了私有内存的局部性和隔离性,以及它不需要额外的锁定机制,从而提高了内存分配的速度和效率。这对于多进程环境中的性能优化是一个重要的概念。

注意:
有一种常见的用例,涉及在OpenSIPS进程分叉(forking)之前,开发人员将一些静态变量存储在主进程的私有内存中。在分叉之后,每个子进程将拥有自己的私有内存副本(相同的内存地址指针)。

具体来说:

  1. 常见用例: 这里描述的情况是一种普遍的情况或惯例,它发生在OpenSIPS应用程序中。

  2. 在分叉OpenSIPS进程之前: “before forking OpenSIPS processes” 意味着在将OpenSIPS进程分叉成多个子进程之前,即在多个子进程被创建之前的阶段。

  3. 开发人员存储静态变量: 开发人员将某些静态变量(通常是不会在运行时改变的变量)存储在主进程的私有内存中。私有内存是每个进程独有的,所以主进程的私有内存与子进程的私有内存是独立的。

  4. 在fork后,每个子进程有自己的私有内存副本: 当OpenSIPS应用程序进行fork时,主进程将被分成多个子进程。每个子进程都会获得主进程私有内存的一个独立拷贝,这意味着它们各自具有相同的静态变量,但是这些变量在不同的内存位置上(同样的内存地址指针)。

这种做法的好处是,静态变量在主进程中初始化,然后通过分叉子进程来共享这些变量的初始状态。这使得每个子进程能够独立操作这些变量,而不会相互干扰,因为它们拥有自己的私有内存副本。这对于多进程并行处理是很有用的,因为它确保了数据的隔离性和线程之间的独立性。

头文件 “mem/mem.h” 提供了所有与私有内存相关的函数的访问接口。也就是说,通过包含这个头文件,开发人员可以访问和使用与私有内存管理有关的函数,这些函数可能包括分配、释放、读取、写入等操作,用于管理程序中的私有内存。头文件的 “exposes” 意味着这些函数在头文件中被公开或暴露出来,可以在程序中使用。

/*
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 Shared (SHM) Memory

以下是Opensips文档中提到的对共享内存的几点描述:

  1. “Shared memory can be accessible from all OpenSIPS processes.”(共享内存可以被所有OpenSIPS进程访问。)

    • 这句话指出,共享内存在OpenSIPS应用程序中可以被所有进程访问,不仅仅是一个单独的进程。多个OpenSIPS进程可以访问相同的共享内存区域,以便在它们之间共享数据和信息。
  2. “Thus, generally speaking, all write access to a shared memory buffer should be guarded by some form of synchronization mechanism in order to ensure consistency.”(因此,一般来说,对共享内存缓冲区的所有写访问都应该受到某种同步机制的保护,以确保一致性。)

    • 这句话强调了一个原则,即在多个进程之间共享内存时,所有写入共享内存的操作应该受到同步机制的保护,以确保数据的一致性。这是因为并发写入可能导致数据损坏或不一致,因此需要使用互斥锁(mutex)或其他同步机制来协调访问。
  3. “Also, since allocations of chunks of shared memory can be requested from all the OpenSIPS processes, the shared memory allocator internally uses locking in order to ensure consistency.”(而且,由于所有OpenSIPS进程都可以请求共享内存块的分配,因此共享内存分配器在内部使用锁定机制以确保一致性。)

    • 这句话说明了共享内存分配的情况。因为多个进程都可以请求共享内存的分配,共享内存分配器在内部使用了锁定机制,以确保分配的一致性。这意味着在分配共享内存块时,会进行必要的同步操作,以避免竞态条件和数据不一致。
  4. “Due to this, as a general guideline, it is recommended to avoid big fragmentation of the shared memory ( by requesting many small chunks ), by merging as much information that is meaningfully tied together in a single shared memory chunk.”(因此,作为一般指导原则,建议避免共享内存的大碎片化(通过请求许多小块),而是将有意义地相关联的信息合并到单个共享内存块中。)

    • 这句话提供了一些建议。由于共享内存的锁定机制,建议尽量避免将共享内存分散成许多小块,而是将相关的信息合并到一个较大的共享内存块中。这有助于减少锁定冲突的可能性,因为较小的碎片化共享内存可能导致更频繁的锁定和解锁操作。因此,建议将有关联的数据合并在一起,以提高性能和数据一致性。

总之,这段话强调了在OpenSIPS应用程序中使用共享内存时的注意事项,包括同步操作的重要性以及减少共享内存碎片化的建议。这些措施有助于确保共享内存在多进程环境中能够稳定和高效地工作。

头文件 “mem/shm_mem.h” 包含了所有与共享内存相关的函数的接口或声明。也就是说,通过包含这个头文件,开发人员可以访问和使用与共享内存管理有关的函数,这些函数可能包括共享内存的分配、释放、读取、写入以及其他操作。头文件 “mem/shm_mem.h” 提供了这些函数的定义或声明,以便在程序中使用。这有助于开发人员在OpenSIPS应用程序中有效地管理和操作共享内存。

/*
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 Memory Allocators

“Memory Allocators”,是在讨论关于内存分配器的话题。然而,它没有提供具体的细节或信息,而是建议读者查阅一篇详尽的博客文章来了解每种内存分配器的优势和劣势。因此,这段话并没有提供内存分配器的具体内容,而是引导读者查看博客文章以获取更多相关信息。这篇博客文章可能会深入探讨不同的内存分配器类型、它们的用途、性能特点以及在特定情况下的最佳实践。如果你需要详细了解内存分配器的信息,建议阅读该博客文章。
https://blog.opensips.org/2019/03/21/containing-memory-related-issues-in-an-easy-way-with-opensips-3-0/

4. Parsing SIP Messages

第四段描述了:OpenSIPS中的SIP消息解析器的工作原理和性能特点。
主要有以下几点

  1. “The OpenSIPS SIP message parser is a lazy parser, which performs very well in terms of performance.”

    • OpenSIPS的SIP消息解析器是一种"lazy parser"(惰性解析器),在性能方面表现非常出色。
    • “Lazy parser” 意味着解析器只在需要时才执行必要的解析操作,而不是立即解析整个消息。这种方式可以提高性能。
  2. “When the message is received, only the critical headers are parsed (e.g. topmost Via header)”

    • 当接收到消息时,只解析关键的头部信息(例如,最顶层的Via头部)。
    • 这意味着解析器在初始接收消息时只解析一些关键头部,而不是整个消息。
  3. “For known header types, when the developer wants to extract the first occurrence of a header, they do not have to parse the entire message, but rather the parser will stop parsing when the first needed header type was identified.”

    • 对于已知的头部类型,当开发人员想要提取某个头部的第一个出现时,他们不需要解析整个消息,解析器会在找到第一个需要的头部类型时停止解析。
    • 这意味着解析器能够在识别到所需头部后立即停止解析,提高了解析的效率。
  4. “Also, it is important to note that there are two types of parsing:”

    • 此外,需要注意有两种类型的解析:
  5. “Parsing in the sense of identifying header boundaries and separating between the header name and header body (e.g. parsing the ‘To : vlad@opensips.org;tag=123\r\n’ header, and identifying that first, the header is present, and secondly that the body is ‘vlad@opensips.org;tag=123\r\n’)”

    • 一种解析是指识别头部边界并将头部名称与头部内容分开,例如,解析 ‘To : vlad@opensips.org;tag=123\r\n’ 头部时,首先识别头部是否存在,其次确定头部内容。
  6. “In-depth parsing of a specific header type, for identifying header specific information (in our example, extracting the to-tag in the To header, etc.).”

    • 另一种解析是深入解析特定类型的头部,以识别头部的具体信息,例如,在To头部中提取to-tag等。
  7. “All parsing is done in a stateful manner, in the sense that the OpenSIPS parser knows which header was parsed and internally keeps a parsing bitmask. Thus, once the To header was first parsed, any subsequent attempts of parsing the To header will return immediately.”

    • 所有的解析都以有状态的方式进行,也就是说OpenSIPS解析器知道哪个头部已经被解析,并在内部保持了一个解析位掩码(parsing bitmask)。因此,一旦首次解析了To头部,任何后续尝试解析To头部的操作都将立即返回。
  8. “Parser implementations are generally exported in the parser/ folder.”

    • 解析器的实现通常被导出到名为 “parser/” 的文件夹中。
    • 这句话提到解析器的具体实现存储在 “parser/” 文件夹中,可能指的是OpenSIPS的代码结构中的特定目录。
4.1 “Generic Header Parser” 通用的头部解析器

通用的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 ).
*/

"Parameters:"

"Parameters:"(参数)这一部分说明了函数的参数列表。
"msg: the SIP message that needs to be parsed – see parser/msg_parser.h for details on the struct sip_msg structure"

"msg:" 是函数的第一个参数,它表示需要被解析的SIP消息。
"the SIP message that needs to be parsed"(需要解析的SIP消息)指示函数需要一个SIP消息作为输入。
"see parser/msg_parser.h for details on the struct sip_msg structure"(请参阅 parser/msg_parser.h 以获取关于 struct sip_msg 结构的详细信息)是对参数的进一步说明,建议开发人员参考指定的头文件以了解 struct sip_msg 结构的详细信息。
"flags: bitmask of header types that need to be parsed"

"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)"

"next:" 是函数的第三个参数,它用于指定解析器是否应该明确地强制从提供的位掩码中解析新的头部,即使这些头部类型已经在之前找到过。
"specifies whether the parser should explicitly force the parsing of new headers"(指定解析器是否应该明确地强制解析新的头部)进一步解释了参数的作用。
"Can be useful when trying to find a second occurrence of a header"(在尝试查找头部的第二次出现时可能会有用)说明了在某些情况下,这个参数可能会有用,特别是当一个头部可以在SIP消息中多次出现时,例如 "Route" 头部。
"Returns:"

"Returns:"(返回值)这一部分说明了函数的返回值。
"0 in case of error, -1 in case of error (either header was not found or some other error occurred)."

"0 in case of error"(在发生错误时返回0)表示如果函数执行中出现错误,它将返回0"-1 in case of error (either header was not found or some other error occurred)"(在发生错误时返回-1(要么头部未找到,要么发生了其他错误))进一步解释了错误情况下的返回值。如果函数无法找到指定的头部或遇到其他错误,它将返回-1int 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 can be used in order to parse all the headers in the current SIP message.

注意:
The parse_headers() function will not duplicate SIP headers at all – the hooks in the struct sip_msg structure will be populated with pointers that point directly in the SIP message buffer.
这句话解释了 parse_headers() 函数的行为,特别是在解析SIP头部时如何处理头部数据。以下是对这句话的解释:

  1. “The parse_headers() function will not duplicate SIP headers at all –”

    • parse_headers() 函数在处理SIP头部时不会进行任何复制操作 -
  2. “the hooks in the struct sip_msg structure will be populated with pointers that point directly in the SIP message buffer.”

    • 结构体 struct sip_msg 中的 “hooks”(钩子或指针)将被填充(populated)为指向直接位于SIP消息缓冲区中的数据的指针。

这句话的核心意思是,当使用 parse_headers() 函数解析SIP消息头部时,函数不会创建头部的副本,而是将结构体 struct sip_msg 中的一些指针(“hooks”)设置为直接指向原始SIP消息缓冲区中的头部数据。这意味着解析后的头部数据将共享原始消息的内存,而不会复制到新的内存位置。

这种行为可以节省内存和提高性能,因为它避免了在内存中复制大量的头部数据。但需要注意,由于头部数据与原始消息共享内存,应谨慎处理,以确保在访问或修改头部数据时不会意外更改原始消息。

接着描述了成功解析SIP消息头部后的行为,特别是在结构体 sip_msg 中关于已知头部的 “hooks”(钩子)的填充,以及关于分配和释放内存的信息。以下是对这段话的解释:

  1. “Upon return, if successful, the function will populate the respective hooks in the sip_msg structure for the known headers.”

    • 在成功解析后,如果函数执行成功,它将填充 sip_msg 结构体中已知头部的相应 “hooks”(钩子)。
    • 这意味着在 sip_msg 结构体中的特定字段将被设置为指向已知头部数据的指针,以便开发人员可以访问这些头部数据。
  2. “The hdr_field* structures are allocated in pkg memory, and will automatically be freed when the SIP message processing has finished.”

    • hdr_field* 结构体(表示SIP头部的数据结构)是在 “pkg memory” 中分配的,当SIP消息处理完成时,它们将自动被释放。
    • 这意味着解析器在分配内存以存储头部数据时使用了 “pkg memory”,并且开发人员不需要手动释放这些内存。当SIP消息的处理完成后,相关内存将被自动释放,以避免内存泄漏。
  3. “After successful parsing, the developer can access the header name and body as such:”

    • 在成功解析后,开发人员可以按以下方式访问头部的名称和内容:

这段话强调了在成功解析SIP消息头部后,开发人员可以使用 sip_msg 结构体中的 “hooks” 来访问已知头部的名称和内容。这使开发人员能够方便地检索和操作解析后的头部数据,而无需手动处理内存管理。因此,它提供了一种便捷的方式来处理已解析的SIP头部信息。

After successful parsing, the developer can access the header name and body as such:

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 Specific Header Parsing

For parsing a specific header type and extracting the header type relevant information, the parser/ directory contains all implementations for known headers. The naming convention is that parser/parse_X.h will expose the parsing for the header named X.
For example, here is the implementation of the parsing of the To header, exposed by parser/parse_to.h :

这段文本解释了在解析特定类型的SIP头部并提取与该头部相关信息时应如何操作。它提到了在 “parser/” 目录中包含了用于已知头部的所有解析实现,并且介绍了头文件的命名约定。

具体来说:

  1. “For parsing a specific header type and extracting the header type relevant information…”

    • 这部分说明了要解析特定类型的SIP头部以及提取与该头部相关信息的目标。
  2. “…the parser/ directory contains all implementations for known headers.”

    • “…the parser/ directory contains all implementations for known headers.”(“parser/” 目录包含所有已知头部的实现。)这部分指出了解析和提取头部信息的代码实现位于名为 “parser/” 的目录中。
  3. “The naming convention is that parser/parse_X.h will expose the parsing for the header named X.”

    • “The naming convention is that parser/parse_X.h will expose the parsing for the header named X.”(命名约定是,parser/parse_X.h 将公开头部名称为 X 的解析。)这一部分解释了头文件的命名规则。根据这个规则,如果要解析特定头部类型 “X”,可以查找名为 “parser/parse_X.h” 的头文件,该头文件包含了解析该头部的相关代码。
  4. “For example, here is the implementation of the parsing of the To header, exposed by parser/parse_to.h :”

    • “For example, here is the implementation of the parsing of the To header, exposed by parser/parse_to.h :”(例如,这是解析 To 头部的实现,由 parser/parse_to.h 公开:)这部分提供了一个示例,说明如何在 “parser/” 目录中找到特定头部的解析实现。在示例中,它展示了解析 “To” 头部的代码实现位于名为 “parser/parse_to.h” 的头文件中。

总之,这段文本指出了在 “parser/” 目录中可以找到解析和提取已知SIP头部信息的代码实现,同时提供了头文件的命名约定,以便开发人员能够找到特定头部的解析实现。示例中提到了 “To” 头部的解析实现,它可以在名为 “parser/parse_to.h” 的头文件中找到。

For parsing a specific header type and extracting the header type relevant information, the parser/ directory contains all implementations for known headers. The naming convention is that parser/parse_X.h will expose the parsing for the header named X.
For example, here is the implementation of the parsing of the To header, exposed by parser/parse_to.h :

注意:
hdr_field 结构中的 void *parsed 元素将包含标头特定的解析器结构,该结构也将被分配到私有内存中,并在 SIP 消息处理完成时自动释放。

After parse_to_header() successfully returns, the developer can start accessing the parsed TO header in the following way :

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

OpenSIPS 解析器还提供了解析单个 SIP URI(统一资源标识符)的功能。

在 SIP(Session Initiation Protocol)中,URI 用于标识和定位通信终端和资源。SIP URI 是一种特殊的 URI,用于在 SIP 协议中唯一标识用户或资源。它通常包括用户的 SIP 地址(例如,sip:user@example.com)。

文本中提到,OpenSIPS 解析器不仅可以解析整个 SIP 消息的头部信息,还可以解析单个 SIP URI。这意味着开发人员可以使用 OpenSIPS 解析器来分析和提取单个 SIP URI 中的各个部分,例如用户、主机、端口等,以便进一步处理或路由 SIP 请求。这为开发人员提供了处理 SIP URI 的灵活性和功能。

parser/parse_uri.h exposes :

/*
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() 函数的工作原理,特别是关于内存分配的情况。以下是对这段话的解释:

  1. “The parse_uri() function does not allocate any memory”

    • “The parse_uri() function does not allocate any memory”(parse_uri() 函数不分配任何内存):这部分说明了 parse_uri() 函数不会在其内部动态分配新的内存空间。
  2. “it just populates the sip_uri structure with references to the buf parameter provided as input.”

    • “it just populates the sip_uri structure with references to the buf parameter provided as input.”(它只是使用输入参数 buf 中的引用来填充 sip_uri 结构体):这一部分解释了 parse_uri() 函数的操作方式。它不会创建新的内存分配,而是将 sip_uri 结构体的字段设置为引用输入参数 buf 中的数据。

总的来说,这段文本强调了 parse_uri() 函数的节约内存的特性。它不会动态分配新的内存,而是直接使用传递给函数的输入参数 buf 中的数据来填充 sip_uri 结构体的字段。这可以提高性能和效率,因为它避免了不必要的内存分配和复制操作。

文本末尾提到了一个示例,说明了如何使用 parse_uri() 函数来解析 “To” 头部的 URI(统一资源标识符)。这个示例展示了如何在代码中使用这个函数,以便开发人员了解如何解析特定的 SIP 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 Parsing the SDP Body

这句话的意思是,OpenSIPS 提供了一组用于操作 SIP 消息主体的函数,这些函数可以在 SIP 消息主体上执行各种操作。具体来说,这些函数的相关信息可以在名为 “parser/msg_parser.h” 的头文件中找到和访问。这个头文件似乎包含了一些用于处理 SIP 消息主体的函数的声明和定义,可以在 OpenSIPS 中使用这些函数来执行特定的操作,例如检索、修改或处理 SIP 消息的主体内容。开发人员可以查看这个头文件以了解可用的函数和如何使用它们来处理 SIP 消息主体。

/*
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 ).

这段文本描述了一个函数的参数和返回值。以下是对这段话的解释:

1. "Parameters:"
   - "Parameters:"(参数)这一部分说明了函数的输入参数和输出参数。

2. "msg - the SIP message to fetch the body for"
   - "msg" 是函数的第一个输入参数,表示要获取主体内容的 SIP 消息。
   - "the SIP message to fetch the body for"(用于获取主体内容的 SIP 消息)解释了这个参数的作用。

3. "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"
   - "body" 是函数的第二个参数,它是一个输出参数。这个参数将用于保存 SIP 消息中主体的指针和主体的长度。
   - "output param"(输出参数)表示这是一个用于输出数据的参数。
   - "which will hold the body pointer inside the SIP message and the body length"(它将保存SIP消息中主体的指针和主体的长度)说明了这个参数将包含哪些信息。
   - "or {NULL,0} in case of no body present"(或者在没有主体的情况下为 {NULL,0})表示如果 SIP 消息中没有主体,那么参数将被设置为 `{NULL,0}`,表示没有主体。

4. "Returns:"
   - "Returns:"(返回值)这一部分说明了函数的返回值。

5. "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 )."
   - "0 in case of success"(在成功的情况下返回0)表示如果函数成功执行并获取了主体信息,它将返回0。
   - "or -1 in the case of parsing errors"(或者在解析错误的情况下返回-1)表示如果在执行函数时发生解析错误,它将返回-1。
   - "the function needs to internally parse all the headers in order to detect the body length"(函数需要内部解析所有头部以检测主体的长度)进一步解释了如果发生解析错误,是因为函数需要在内部解析所有头部以检测主体的长度。这可能会导致解析错误的情况,例如无效的头部或不正确的消息格式。

综合起来,这段文本描述了一个函数,它用于获取 SIP 消息中的主体信息。它的输入参数是要处理的 SIP 消息,输出参数用于保存主体的指针和长度。函数返回0表示成功获取主体,返回-1表示发生解析错误。要注意的是,为了检测主体的长度,函数需要在内部解析所有头部。
*/
int get_body(struct sip_msg *msg, str *body)

为了解析 SDP 主体并提取有关会话的各种信息,parser/sdp/sdp.h 公开了如下信息:

/*
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

5. Changing SIP Messages

这段文本目的是解释OpenSIPS中对SIP消息进行更改的标准方法,称为"lumps系统"。我将尝试以更简单的方式来解释:

OpenSIPS是一种用于处理SIP(Session Initiation Protocol)消息的软件。
"lumps系统"是OpenSIPS中的一种机制,用于对SIP消息进行各种操作,例如添加、删除或修改消息的一部分。
这个系统类似于Linux中的差异/补丁工具,开发人员可以指定在SIP消息上执行的操作,但这些操作不会立即应用到消息上。
操作被记录在一个列表中,只有在OpenSIPS脚本完全执行后,消息即将被传递之前,这些操作才会应用到消息上。
这意味着,如果你在脚本中添加了一个新的消息头部,你不能立刻在脚本中检查这个头部是否存在,因为它只会在脚本执行后才会应用。
总之,这个系统允许开发人员对SIP消息进行复杂的操作,但这些操作不会立即生效,而是在适当的时候才会被应用到消息上,以确保消息的正确处理。这种机制有助于自定义SIP消息的处理方式。

5.1 SIP Message Lumps

This type of lumps operate on the current SIP message context.
From operational point of view, they are also split into two categories :

这段文本解释了关于"lumps系统"的另一个方面,特别是它们对当前SIP消息上下文的操作和操作的分类。让我来解释:

"这类型的lumps"指的是在"lumps系统"中的一种操作,这些操作会影响当前的SIP消息上下文,也就是当前正在处理的SIP消息。

从操作角度来看,这些操作可以分为两个主要类别,意思是从功能上来看,这些操作可以分为两种不同的类型或类别。

具体来说,这段文本正在解释一种与SIP消息处理相关的机制,这种机制可以根据操作的性质和影响对它们进行分类。这些分类有助于更好地理解和组织"lumps系统"中的操作,以便更有效地进行SIP消息的处理。

这种类型的块在当前 SIP 消息上下文上运行。
从操作角度来看,它们也分为两类:

1、Delete Lumps
data_lump.h exposes

/*
Parameters :
      msg - the SIP message the lump will affect
      offset - the offset in the SIP message at which to start deleting
      len - the number of characters to delete from the SIP message
      type - indication on which header the current lump affects ( can be 0 )
Returns :
      the created lump structure for deleting part of the SIP message. Can be further used to chain together different types of lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/
struct lump* del_lump(struct sip_msg* msg, unsigned int offset,
        unsigned int len, enum _hdr_types_t type);

Example of deleting the RPID header :

/* first parse the header to figure out where it actually starts in the SIP message */
      if( parse_headers(msg,HDR_RPID_F,0)<0 || msg->rpid == NULL ){
            LM_DBG(“No rpid header – nothing to delete \n”);
            return 0;
      }

      /* delete the entire RPID header */
      if ( del_lump(msg, msg->rpid->name.s-msg->buf, msg->rpid->len,HDR_RPID_T )== NULL) {
            LM_ERR(“Failed to delete RPID header \n”);
            return -1;
      }

2、Add Lumps
data_lump.h exposes

/*
Parameters :
      after/before - the lump where we will connect our new lump
      new_hdr - string to be added
      len - length of the string to be added
      type - header type that is affected by the current change ( can be 0 )
Returns :
      the created lump structure for adding to the SIP message. Can be further used to chain together different types of lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/
struct lump* insert_new_lump_after(struct lump* after,                                            
                char* new_hdr, unsigned int len, enum _hdr_types_t type);                                        
struct lump* insert_new_lump_before(struct lump* before, char* new_hdr,
                unsigned int len,enum _hdr_types_t type);

如果开发人员的愿望只是将特定字符串添加到 SIP 消息中,则必须创建一个新的锚块,然后必须将其作为第一个参数提供给 insert_new_lump_after/insert_new_lump_before。
为了创建新的锚块,data_lump.h 还导出

/*
Parameters :
      msg - the SIP message that will be affected by the lump anchor
      offset - the offset in the SIP message where the anchor will be placed
      len - not currently used ( should be 0 )
      type - header type that is affected by the current change ( can be 0 )
Returns:
      the created lump structure for adding to the SIP message. Can be further used to chain together different types of lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/
struct lump* anchor_lump(struct sip_msg* msg, unsigned int offset,
                int unsigned len, enum _hdr_types_t type)

Example of adding a new SIP header at the end of the SIP message headers :

/* make sure we detect all headers */
      if (parse_headers(msg, HDR_EOH_F, 0) == -1) {
            LM_ERR("error while parsing message\n");
            return -1;
      }

      /* add the anchor at the very end of the SIP headers */
      anchor = anchor_lump(msg, msg->unparsed - msg->buf, 0, 0);
      if (anchor == NULL) {
            LM_ERR(“Failed to create lump anchor\n”);
            return -1;
      }
      len =  sizeof(“MY_HDR: MY_VAL\r\n”) -1;
      new_hdr=pkg_malloc(len);
      if (!new_hdr) {
            LM_ERR(“No more pkg mem\n”);
            return -1;
      }

      memcpy(new_hdr,”MY_HDR: MY_VAL\r\n”,len);
      if (insert_new_lump_after(anchor, new_hdr, len, 0) == 0) {
            LM_ERR("can't insert lump\n");
            pkg_free(new_hdr);
            return -1;
      }

      /* job done, the PKG new_hdr mem will be free internally when the lump will be applied */
      return 0;

如果我们想要替换SIP消息中的特定部分,可以将操作分为两个步骤。首先,通过调用 “del_lump” 函数删除我们不再需要的部分,然后使用返回的 “lump” 来添加一个新的 “lump” 到它之后。下面是更详细的解释:

删除不需要的部分:首先,我们要删除SIP消息中不再需要的部分。这是通过调用名为 “del_lump” 的函数来完成的。这一步的目的是将我们想要替换的部分从消息中删除。

添加新部分:一旦删除了不需要的部分,就可以使用返回的 “lump” 来添加新的内容。通常,这涉及使用另一个函数或方法来在删除的部分之后添加新的 “lump” 或数据。

这个示例是具体说明如何替换SIP消息中的RPID(Remote-Party-ID)头的内容。这个过程允许您根据需要修改SIP消息,例如更新或更改特定的头信息。

 /* first parse the header to figure out where it actually starts in the SIP message */
      if( parse_headers(msg,HDR_RPID_F,0)<0 || msg->rpid == NULL ){
            LM_DBG(“No rpid header – nothing to delete \n”);
            return 0;
      }

      /* delete just the contents of the RPID header */
      del =  del_lump(msg, msg->rpid->body.s-msg->buf, msg->rpid->body.len,HDR_RPID_T);
      if ( del == NULL) {
            LM_ERR(“Failed to delete RPID header \n”);
            return -1;
      }

      len =  sizeof(“sip:new_rpid@my_domain.com\r\n”) -1;
      new_rpid=pkg_malloc(len);
      if (!new_rpid) {
            LM_ERR(“No more pkg mem\n”);
            return -1;
      }
      memcpy(new_rpid,“sip:new_rpid@my_domain.com\r\n”,len);

      if(insert_new_lump_after(del,new_rpid,len,HDR_RPID_T)==NULL) {
            LM_ERR("Failed to insert new callid\n");
            pkg_free(new_rpid);
            return -1;
      }
5.2 SIP Reply Lumps

When used in the case of a SIP request, these lumps will operate on the SIP reply that will be internally generated when rejecting a request from within OpenSIPS ( if the Request if forwarded instead of rejected at OpenSIPS level, these lumps will have no effect ). Since the reply will be internally generated by OpenSIPS, the Reply Lumps can only add new content.

当在SIP请求的情况下使用这些 “lumps” 时,它们将影响到在OpenSIPS内部生成的SIP响应。这种情况通常发生在OpenSIPS拒绝处理一个SIP请求时,而不是将其转发。如果请求在OpenSIPS级别被转发而不是被拒绝,这些 “lumps” 将不会产生任何效果。

具体解释如下:

当SIP请求被OpenSIPS拒绝时:在这种情况下,OpenSIPS会生成一个SIP响应,以回应该请求。这个响应可以包含一些修改或处理后的内容,例如添加新的头信息或修改原有的内容。这个过程中,这些 “lumps” 将用于操作响应消息。

当SIP请求被OpenSIPS转发时:如果SIP请求在OpenSIPS级别被转发到其他地方而不是被拒绝,那么生成的响应将不受这些 “lumps” 的影响。因为在这种情况下,响应将由接收方生成,而不是OpenSIPS生成,所以 “lumps” 无法对其产生影响。

总的来说,这段话解释了这些 “lumps” 在处理SIP请求时的作用范围和限制。它们主要用于在OpenSIPS拒绝请求并生成响应时修改响应的内容,但不会影响转发请求的情况。

data_lump_rpl.h exposes

/*
Parameters :
      msg - the SIP Request that the reply will be generated for
      s - the string to be added to the reply
      len - the length of the string to be added
      flags - Since the reply will be generated by OpenSIPS, it is important to mark your lump if it should be added to the Reply headers or to the Reply body. Relevant flags for these cases are LUMP_RPL_HDR and LUMP_RPL_BODY.
Returns :
      the created lump structure for adding to the SIP reply. Can be further used to chain together lumps in the message attached list of lumps. NULL is returned in case of internal error.
*/
struct lump_rpl* add_lump_rpl(struct sip_msg *msg, char *s, int len, int flags);

Example of Adding contact header to the internally generated reply :

      static char ct[CT_LEN] = “Contact: opensips@my_domain.com\r\n”;

      /* we are adding a lump to the headers, so we pass the  LUMP_RPL_HDR flag
      also , our buffer is located in a static buffer, thus no need for the core to allocate memory for this lump, we also pass the LUMP_RPL_NODUP flag */
      if (add_lump_rpl(msg, ct, CT_LEN, LUMP_RPL_HDR |  LUMP_RPL_NODUP)==0) {
            LM_ERR("unable to add lump\n");
            return -1;
      }

6. Extending OpenSIPS core Config File

OpenSIPS 使用 flex 和 bison 来解析配置文件,然后构建 SIP 消息从网络级别读取后将经过的整个操作树。

当涉及直接在核心中扩展 OpenSIPS 配置文件时,开发人员可以选择添加新的核心参数或新的核心功能。

这段话讨论了如何扩展OpenSIPS核心配置文件以及开发人员可以采取的两种主要扩展方式。以下是每个部分的解释:

  1. “Extending OpenSIPS core Config File”:

    • 这句话表明主题是如何扩展OpenSIPS的核心配置文件,这是OpenSIPS的关键组成部分之一。
  2. “OpenSIPS uses flex and bison in order to parse the configuration file…”:

    • 这部分说明了OpenSIPS使用了flex和bison两个工具来解析其配置文件。这两个工具用于分析配置文件中的语法和结构,以便OpenSIPS可以理解配置文件中的内容。
  3. “…and then build the entire action tree that a SIP message will go through once it is read from network level.”:

    • 这一句解释了OpenSIPS的工作流程。一旦OpenSIPS从网络级别读取了SIP消息,它会构建一个完整的操作树。这个操作树代表了SIP消息在经过OpenSIPS处理后将要执行的一系列操作。
  4. “When it comes to extending the OpenSIPS configuration file directly in the core…”:

    • 这部分提到了扩展OpenSIPS核心配置文件的情况。这意味着开发人员希望直接修改或扩展OpenSIPS的核心配置文件以满足特定需求。
  5. “…the developer can either choose to add a new core parameter, or a new core function.”:

    • 这一句解释了在扩展核心配置文件时开发人员的两个主要选择。他们可以选择添加新的核心参数,这些参数允许更灵活地配置OpenSIPS的行为。或者,他们可以选择添加新的核心函数,这意味着他们可以扩展OpenSIPS的功能,使其执行新的任务或操作。

总之,这段话强调了OpenSIPS如何解析配置文件、构建操作树以及如何通过添加新的核心参数或核心函数来扩展其核心配置文件,以满足特定需求或实现新功能。这为开发人员提供了灵活性和自定义的机会,以根据其需求定制OpenSIPS的行为。

6.1 Adding a core parameter

这段话讨论了一个逐步教程,教程的目标是实现一个名为"udp_workers"的核心参数。该参数是一个整数,用于控制每个UDP接口的OpenSIPS进程数量。以下是对每个句子的解释:

  1. “In the following step by step tutorial, we will follow the implementation of the udp_workers core parameter…”

    • 这句话说明了教程的主题,即教程将逐步指导如何实现名为"udp_workers"的核心参数。该参数的作用是控制每个UDP接口上运行的OpenSIPS进程数量。
  2. “First of all, we will have to add the variable that will hold the value of our new core parameter.”

    • 这一句表明第一步是添加一个变量,用于存储新核心参数的值。在教程中,将创建一个变量,用于存储"udp_workers"参数的值。
  3. “In our case, in globals.h we have added…”

    • 这句话指出,在这个特定教程案例中,变量的添加操作将在"globals.h"文件中完成。该文件是存储全局变量和参数的地方。
extern int udp_workers_no;

Adding the variable here will make it visible in both the OpenSIPS core and the OpenSIPS modules. main.c will hold the actual variable :(在此处添加变量将使其在 OpenSIPS 内核和 OpenSIPS 模块中都可见:)

/* Default value in case the parameter is not set from the script */
int udp_workers_no = 8;

现在,在 cfg.lex 下,我们需要指示词法分析器识别我们的新标记:

/* Default value in case the parameter is not set from the script */
UDP_WORKERS udp_workers

Next, we will have to modify the grammar in order to accept our new parameter. In cfg.y , first we re-specify the lexer token:
接下来,我们必须修改语法才能接受新参数。在 cfg.y 中,首先我们重新指定词法分析器标记:

%token UDP_WORKERS

最后,我们设置新token的解析规则:

| UDP_WORKERS EQUAL NUMBER { udp_workers_no=$3; }
| UDP_WORKERS EQUAL error { yyerror("number expected"); }

我们的变量将有一个数字变量,其他任何内容都会在解析 OpenSIPS 脚本时触发并出错。

6.2 Adding a core function

在下面的分步教程中,我们将跟踪 xlog 核心函数的实现,该函数用于将信息打印到日志记录工具。

请注意:xlog 可以接收单个参数(要打印的字符串),也可以接收两个参数(日志级别,然后是要打印的字符串)。

6.3 Adding a core Pseudo-Variable

所有 OpenSIPS 核心伪变量都在 pvar.c 中定义

7. Adding Transformations

转换是可以直接对任何 OpenSIPS 伪变量进行操作的函数。转换将伪变量的值作为输入并对其进行处理,输出转换后的版本。

一些转换示例:

“uri”转换(适用于 SIP URI,它们允许提取各种有用的信息,例如 URI 的用户名或域部分、各种参数等)

“s”类转换(适用于通用字符串,并提供各种有用的方法,如获取字符串的长度、搜索字符串中字符的第一次出现等)

… many others! – see http://www.opensips.org/Documentation/Script-Tran-3-1

这段话是在讨论OpenSIPS中的"变换"(Transformations)功能。变换是一种函数,它可以直接作用于任何OpenSIPS伪变量(pseudo-variable)。变换接受伪变量的值作为输入,并对其进行处理,输出一个经过转换的版本。

以下是一些变换的示例:

  1. "uri"变换:这些变换用于处理SIP URIs,并允许提取各种有用的信息,如URI的用户名或域部分,各种参数等。

  2. "s"类别的变换:这些变换用于处理通用字符串,并提供各种有用的方法,比如获取字符串的长度,查找字符串内第一次出现某个字符的位置等。

还有许多其他类型的变换,你可以在OpenSIPS文档的链接中找到更多信息。总的来说,变换是OpenSIPS中的功能,用于处理和转换伪变量的值,以便在脚本中执行各种操作和任务。

# example of usage
$var(tutorial)  = “OpenSIPSDevel”;
xlog("Our variable has $(var(tutorial){s.len}) characters\n");
# ... which will print "Our variable has 13 characters\n"

请注意,转换可以链接在一起,因此在实现新转换时请务必考虑到这一点!

8. Locking API

OpenSIPS has its own locking API, and it is recommended to use it instead of the system exposed locks, since they offer greater flexibility - depending on the use case and the menuconfig provided compilation flags, the OpenSIPS generic locks can be switched to:

architecture specific locks
with futexes
with adaptive waiting (yield execution instead of blocking)
with busy-waiting
SysV locks
pthread mutexes
POSIX semaphores
umutexes (FreeBSD)

The Locking API offers two distinct functionalities, one for using single lock elements, and another for operating on entire sets of locks.

这段话在讨论OpenSIPS的锁定(locking)系统以及为什么建议使用它而不是系统提供的锁。OpenSIPS具有自己的锁定API,推荐使用它,因为它们提供更大的灵活性。具体来说,根据使用情况和提供的编译标志(menuconfig),OpenSIPS的通用锁可以切换到以下不同类型的锁定方式:

  1. 特定体系结构的锁定:根据硬件体系结构使用锁。

  2. 使用futexes的锁定:使用Fast User-Space Mutexes(futexes)。

  3. 具有自适应等待的锁定:在等待时让执行让步,而不是阻塞。

  4. 忙等待锁定:在锁定被释放之前一直尝试获取锁。

  5. SysV锁定:使用System V风格的锁定。

  6. pthread mutexes:使用POSIX线程(pthread)的互斥锁。

  7. POSIX信号量:使用POSIX信号量进行锁定。

  8. umutexes(FreeBSD):在FreeBSD上使用的一种锁定方式。

这个锁定API提供了两种不同的功能,一种是用于操作单个锁元素,另一种是用于操作整套锁定。这种多样性和灵活性使得OpenSIPS可以根据不同的使用情况选择最合适的锁定机制,以满足不同的性能和需求要求。

9. Timer API

OpenSIPS exposes an API for recurrent job scheduling, with second and microsecond precision.
The OpenSIPS timer architecture involves the following types of processes (which may be listed at runtime by running opensips-cli -x mi ps):

“time_keeper” - a process which keeps track of the time since startup, using two global counters of second and microsecond precision. It also
“timer” - a process responsible for timer job scheduling. It does not run the jobs themselves, it simply dispatches them for execution to other processes
“SIP receiver” and “TCP receiver” - SIP workers, responsible for SIP message processing. Yet any of them may also receive an above-mentioned high-priority timer job sent by the “timer” worker, which they will immediately execute, disregarding any pending SIP messages on the network
“Timer handler” - for the cases where the SIP workers are so busy processing traffic (e.g. they are all stuck in some slow DB queries) that timer jobs are at risk of getting delayed, this process is meant to save the day and ensure the timer jobs execute on time。

这段话涉及OpenSIPS的定时器(timer)架构以及它的定时任务调度系统。下面是对其中的关键概念的解释:

  1. 重复性任务调度:OpenSIPS提供了一个API,用于以秒和微秒精度进行重复性任务调度。这意味着你可以安排定时任务,以便在每秒或每微秒触发。

  2. 时间管理器(time_keeper):这是一个进程,它跟踪自启动以来的时间,使用秒和微秒级别的全局计数器。它用于测量时间的流逝。

  3. 定时器(timer):这是一个进程,负责定时任务的调度。它不执行任务本身,只是将它们分派到其他进程以执行。

  4. SIP接收器(SIP receiver)和TCP接收器(TCP receiver):这些是负责处理SIP消息的SIP工作进程。它们还可以接收定时任务,这些任务是由“定时器(timer)”工作进程发送的,并且可以立即执行,无论网络上是否有挂起的SIP消息。

  5. 定时器处理程序(Timer handler):在某些情况下,SIP工作进程可能因为繁忙处理流量(例如,它们都陷入了某些慢速数据库查询)而导致定时任务可能被延迟。定时器处理程序的作用是确保定时任务按时执行,即使SIP工作进程繁忙,也能保证定时任务按计划执行。

总之,OpenSIPS的定时器架构允许你安排和管理定时任务,以秒和微秒的精度,而各种不同类型的工作进程负责执行这些任务,确保它们按时执行。这可以帮助确保OpenSIPS在处理SIP消息的同时,还能准时执行其他任务。

timer.h 公开了用于在 OpenSIPS 中调度重复作业的所有相关功能。要注册具有秒精度的新计时器函数,请使用:

/*
Parameters:
        label – opaque string containing a short function description (for displaying/logging purposes)
        f – the actual function to be called
        param – parameter to be provided to the timer function
        interval – the interval, in seconds, that the function needs to be called at
        flags – the nature of the job: is it real-time (must be skipped if it cannot run on time) or critical (must never skip a single run, despite delays)

Returns:
        0 in case of success, negative code in case of internal error.
*/
int register_timer(char *label, timer_function f, void* param,
      unsigned int interval, unsigned short flags);
/*
The timer job prototype:

Parameters:
      ticks – represents the current number of seconds since OpenSIPS startup
      param - the parameter provided at job registration
*/
typedef void (timer_function)(unsigned int ticks, void* param);

To register a microsecond-precision job, you can use:

/*
Parameters:
        label – opaque string containing a short function description (for displaying/logging purposes)
        f – the actual function to be called
        param – parameter to be provided to the timer function
        interval – the interval, in microseconds, that the function needs to be called at
        flags – the nature of the job: is it real-time (must be skipped if it cannot run on time) or critical (must never skip a single run, despite delays)

Returns:
        0 in case of success, negative code in case of internal error
*/
int register_utimer(char *label, utimer_function f, void* param,
      unsigned int interval, unsigned short flags);
这里需要注意的是,在分叉任何其他工作进程之前,必须在“服务员”进程的上下文中调用所有上述与计时器相关的函数(因此,在分叉之前,可以从模块的 mod_init() 回调中调用,也可以直接从核心中调用)。

10. Management Interface API

The Management Interface is the abstract layer that is commonly used to control and monitor OpenSIPS. The MI Interface supports multiple actual back-ends (e.g. FIFO, Datagram, JSON-RPC, XML-RPC). Thanks to its modularity and clear separation between structuring of the data (logic) and representation of the data structures (transport) layers, the developer is only left to define the functions which (de)structure the data, and then it is up to the OpenSIPS script writer to choose what transport she will actually use for controlling OpenSIPS.
The MI interface heavily uses JSON in order to interface with its transport layer:

the interface will provide as input a JSON (mi_item_t) to the user-defined MI function, with its required input fields
an MI function must also return a JSON, which will then be converted by the transport layer to the appropriate representation

Further on, we will focus on the core MI functions, with a specific focus on the log_level MI function. Note that modules may also export MI functions (and commonly do so) - see the Modules Development MI functions topic for more information on that.

这段话描述了OpenSIPS(开源SIP服务器)的管理接口(Management Interface)以及该接口的工作原理和特点:

  1. 管理接口:管理接口是一个抽象层,通常用于控制和监视OpenSIPS。它允许用户以不同方式与OpenSIPS进行交互。

  2. 支持多个后端:管理接口支持多个实际后端,例如FIFO、Datagram、JSON-RPC和XML-RPC等。这意味着你可以选择不同的后端来与OpenSIPS进行通信。

  3. 模块化和数据结构分离:管理接口的设计具有模块化的特点,清晰地分离了数据结构(逻辑)和数据结构表示(传输)层。开发人员只需定义(解)结构化数据的函数,然后由OpenSIPS脚本编写人员选择实际用于控制OpenSIPS的传输方式。

  4. JSON的广泛应用:管理接口广泛使用JSON与其传输层进行交互。接口将JSON作为输入提供给用户定义的管理接口函数,该JSON包含所需的输入字段。管理接口函数也必须返回JSON,然后传输层将其转换为适当的表示形式。

总之,管理接口提供了一种通用的方式,使用户能够以多种方式控制和监视OpenSIPS。这种设计的灵活性和模块化性质允许开发人员根据他们的需求选择适当的交互方式,并将数据转换为JSON以与OpenSIPS进行通信。接下来,该文将着重关注核心管理接口函数,特别是"log_level MI"函数,同时提到了模块也可以导出管理接口函数的事实。

The structures commonly used for exporting MI functions are found in mi/mi.h :

typedef struct mi_export_ {
    /* the name of the function users will invoke it from their transport of choice */
    char *name;

    /* short description of the usage of this function */
    char *help;

    /* flags for this function.  The current options are :
         - MI_ASYNC_RPL_FLAG - the function has an asynchronous behavior (e.g: MI functions that send out SIP messages and do not wait for their reply)
         - MI_NO_INPUT_FLAG - the function does not receive any parameters
    */
    unsigned int flags;

    /* an initialization function to be called by OpenSIPS (one time) */
    mi_child_init_f *init_f;

    /* the possible combinations of arguments which may be supplied to this function, along with their handlers */
    mi_recipe_t recipes[MAX_MI_RECIPES];
} mi_export_t;

/* Example of core MI exported function */
static mi_export_t mi_core_cmds[] = {
...
    { "log_level", "gets/sets the per process or global log level in OpenSIPS",
        0, 0, {
        {w_log_level,   {0}},
        {w_log_level_1, {"level", 0}},
        {w_log_level_2, {"level", "pid", 0}},
        {EMPTY_MI_RECIPE}
        }
    },
...
};


/* For exporting the populated array of MI functions
Parameters :
      mod_name : the name of the module exporting these functions
      mis : the array of exported MI functions
Returns :
      0 on success, negative in case of error
*/
int register_mi_mod( char *mod_name, mi_export_t *mis);

/* Example of usage */
if (register_mi_mod( "core", mi_core_cmds) < 0) {
      LM_ERR("unable to register core MI cmds\n");
      return -1;
}

Statistics API

OpenSIPS and that can be fetched by the outside world ( via the MI interface ) for understanding the OpenSIPS load / health status / etc.
The advantages of using the OpenSIPS Statistics API instead of regular counters is :

easily fetched from the MI Interface
on supported architectures, the statistics do not use an explicit lock ( the consistency is ensured by employing assembly code ), thus you will get better performance

这段话描述了OpenSIPS公开的统计API,它可以从核心或模块中使用。这些统计数据本质上是由OpenSIPS内部递增/递减的计数器,外部世界可以通过管理接口(MI接口)获取这些计数器的值,以了解OpenSIPS的负载、健康状况等信息。

使用OpenSIPS统计API而不是常规计数器的优势包括:

  1. 容易从MI接口获取:统计数据可以轻松地通过管理接口(MI接口)获取。这意味着用户可以通过编程方式或其他工具来获取这些计数器的值,以进行监视和分析。

  2. 性能更好:在支持的体系结构上,这些统计数据不需要显式锁定(锁定由汇编代码确保一致性),因此可以获得更好的性能。这意味着即使在高并发的情况下,获取统计数据也不会对系统性能产生太大的负担。

综上所述,OpenSIPS的统计API允许用户轻松地获取有关OpenSIPS负载和健康状况的信息,而不会对系统性能产生太大的影响,并提供了一种通过管理接口访问这些信息的便捷方式。

The most important structures used for extending statistics are exported by statistics.h :

typedef struct stat_export_ {
        char* name;                /* null terminated statistic name */
        unsigned short flags;      /* flags */
        stat_var** stat_pointer;   /* pointer to the variable's mem location *
                                    * NOTE - it's in shm mem */
} stat_export_t;

12. SQL Database API

OpenSIPS exposes a SQL database API that the module developers can use for operating the most common SQL queries. Advantages here are :

writing back-end independent code, since the DB API is decoupled from the actual modules implementing the back-end specific code
the ability to expose SQL-like capabilities to back-ends who are not internally SQL ( eg. the db_flatstore modules operates directly with flat-text files, yet the developer can insert into the file as if he was inserting into a regular SQL database

db/db.h exposes most of the database related functions. At startup, the developer will have just the database URL where he needs to connect. By calling db_bind_mod , the OpenSIPS DB API will try to automatically locate the actual DB module that support that specific back-end, and will return all the needed functions for operating on the back-end.

这段话讲述了OpenSIPS公开的SQL数据库API,模块开发人员可以使用它来执行最常见的SQL查询。以下是其中的优点:

  1. 编写与后端数据库无关的代码:OpenSIPS的数据库API与实际实现后端特定代码的模块解耦。这意味着开发人员可以编写与后端数据库无关的通用代码,而不必担心特定数据库的细节。

  2. 向非SQL后端提供SQL功能:有些后端并不内置SQL功能,例如db_flatstore模块直接使用平面文本文件作为后端存储,但开发人员可以使用OpenSIPS的数据库API将数据插入文件中,就像插入到常规SQL数据库中一样。

在这里,db/db.h公开了大多数与数据库相关的函数。在启动时,开发人员只需提供数据库的URL,然后通过调用db_bind_mod,OpenSIPS数据库API会尝试自动定位支持特定后端的实际DB模块,并返回操作该后端所需的所有函数。

总之,OpenSIPS的数据库API使模块开发人员能够编写与后端数据库无关的代码,并能够在不同类型的后端上执行SQL查询,从而提供了更大的灵活性和通用性。

/**                                        
 * \brief Bind database module functions                            
 *                                                                      
 * This function is special, it's only purpose is to call find_export function in
 * the core and find the addresses of all other database related functions. The
 * db_func_t callback given as parameter is updated with the found addresses.
 *                                                            
 * This function must be called before any other database API call!
 *                                                              
 * The database URL is of the form "mysql://username:password@host:port/database" or
 * "mysql" (database module name).
 * In the case of a database connection URL, this function looks only at the first
 * token (the database protocol). In the example above that would be "mysql":
 * \see db_func_t                                                            
 * \param mod database connection URL or a database module name
 * \param dbf database module callbacks to be further used                  
 * \return returns 0 if everything is OK, otherwise returns value < 0
 */                                      
int db_bind_mod(const str* mod, db_func_t* dbf);

typedef struct db_func {
      unsigned int           cap;           /* Capability vector of the database transport */
      db_use_table_f         use_table;     /* Specify table name */
      db_init_f              init;          /* Initialize database connection */
      db_close_f             close;         /* Close database connection */
      db_query_f             query;         /* query a table */
      db_fetch_result_f      fetch_result;  /* fetch result */
      db_raw_query_f         raw_query;     /* Raw query - SQL */
      db_free_result_f       free_result;   /* Free a query result */
      db_insert_f            insert;        /* Insert into table */
      db_delete_f            delete;        /* Delete from table */
      db_update_f            update;        /* Update table */
      db_replace_f           replace;       /* Replace row in a table */
      db_last_inserted_id_f  last_inserted_id;  /* Retrieve the last inserted ID
                                                    in a table */
      db_insert_update_f insert_update;     /* Insert into table, update on duplicate key */
} db_func_t;

/* Example of usage below */
db_func_t sql_functions;
db_url = str_init("mysql://root:vlad@localhost/opensips");

if (db_bind_mod(db_url, &sql_functions) < 0){
      /* most likely the db_mysql modules was not loaded, or it was loaded after our module */
      LM_ERR("Unable to bind to a database driver\n");
      return -1;
}

在成功地将模块绑定到后端数据库之后,开发人员还必须确保从脚本编写者的角度提供的URL指向一个后端,该后端还支持将要进一步使用的功能(例如,当操作平面文本文件时,db_last_inserted_id_f函数将不会被填充,因此如果C代码调用该函数,模块将崩溃)。为了实现这一点,开发人员需要使用DB_CAPABILITY宏。这个宏的目的是用来检查后端数据库是否支持特定功能,从而避免在不支持的情况下调用导致模块崩溃的函数。

简而言之,这段话提醒开发人员,在使用特定功能之前,需要检查所选的后端数据库是否支持这些功能,以避免潜在的问题和模块崩溃。这是通过使用DB_CAPABILITY宏来实现的,它可以在运行时检查数据库的功能,以确保安全的操作。

/**
 * Returns true if all the capabilities in cpv are supported by module
 * represented by dbf, false otherwise
 */
#define DB_CAPABILITY(dbf, cpv) (((dbf).cap & (cpv)) == (cpv))

/**
 * Represents the capabilities that a database driver supports.
 */
typedef enum db_cap {
        DB_CAP_QUERY =     1 << 0,  /**< driver can perform queries                                     */
        DB_CAP_RAW_QUERY = 1 << 1,  /**< driver can perform raw queries                                 */
        DB_CAP_INSERT =    1 << 2,  /**< driver can insert data                                         */
        DB_CAP_DELETE =    1 << 3,  /**< driver can delete data                                         */
        DB_CAP_UPDATE =    1 << 4,  /**< driver can update data                                         */
        DB_CAP_REPLACE =   1 << 5,  /**< driver can replace (also known as INSERT OR UPDATE) data       */
        DB_CAP_FETCH   =   1 << 6,  /**< driver supports fetch result queries                           */
        DB_CAP_LAST_INSERTED_ID = 1 << 7,  /**< driver can return the ID of the last insert operation   */
        DB_CAP_INSERT_UPDATE = 1 << 8, /**< driver can insert data into database and update on duplicate */
        DB_CAP_MULTIPLE_INSERT = 1 << 9 /**< driver can insert multiple rows at once */
} db_cap_t;


/**
 * All database capabilities except raw_query, replace, insert_update and
 * last_inserted_id which should be checked separately when needed
 */
#define DB_CAP_ALL (DB_CAP_QUERY | DB_CAP_INSERT | DB_CAP_DELETE | DB_CAP_UPDATE)

/* Example of usage below */
if (!DB_CAPABILITY(sql_functions, DB_CAP_ALL)) {
      LM_CRIT("Database modules does not "
            "provide all functions needed by our module\n");
      return -1;
}

13. NoSQL API

14. Event Interface API

15. BIN Interface API

The Binary Internal Interface is an OpenSIPS core interface which offers an efficient way for communication between individual OpenSIPS instances.
This is especially useful in scenarios where realtime data (such as dialogs) cannot be simply stored in a database anymore, because failover would require entire minutes to complete. This issue can be solved with the new internal binary interface by replicating all the events related to the runtime data (creation / updating / deletion) to a backup OpenSIPS instance.
The BIN interface functionality is exported by the bin_interface.h file.
Using the interface has two steps :

creating and sending the new event from the Active OpenSIPS server
receiving and processing the event in the Backup OpenSIPS server
For creating and sending a new event, the following methods are to be used :

这段话介绍了OpenSIPS的二进制内部接口(Binary Internal Interface),它提供了一种高效的通信方式,用于在不同的OpenSIPS实例之间进行通信。

这在一些情况下特别有用,例如实时数据(如对话)不再可以简单地存储在数据库中,因为故障切换可能需要数分钟才能完成。通过使用新的内部二进制接口,可以将与运行时数据相关的所有事件(创建/更新/删除)复制到备份的OpenSIPS实例来解决这个问题。

BIN接口的功能由bin_interface.h文件导出。使用该接口通常需要以下两个步骤:

  1. 在活动的OpenSIPS服务器上创建并发送新事件。
  2. 在备份的OpenSIPS服务器上接收并处理事件。

在创建和发送新事件方面,需要使用一些特定的方法来完成。

/**
 * bin_init - begins the construction of a new binary packet (header part):
 *
 * +-------------------+------------------------------------------------------+
 * |  8-byte HEADER    |                 BODY                max 65535 bytes  |
 * +-------------------+------------------------------------------------------+
 * | PK_MARKER |  CRC  | LEN | MOD_NAME | CMD | LEN | FIELD | LEN | FIELD |...|
 * +-------------------+------------------------------------------------------+
 *
 * @param: { LEN, MOD_NAME } + CMD
 */
int bin_init(str *mod_name, int cmd_type)

/*
 * copies the given string at the 'cpos' position in the buffer
 * allows null strings (NULL content or NULL param)
 *
 * @return: 0 on success
 */
int bin_push_str(const str *info)

/*
 * adds a new integer value at the 'cpos' position in the buffer
 *
 * @return: 0 on success
 */
int bin_push_int(int info)

/**
 * bin_send - computes the checksum of the current packet and then
 * sends the packet over UDP to the @dest destination
 *
 * @return: number of bytes sent, or -1 on error
 */
int bin_send(union sockaddr_union *dest)

在接收端,开发人员必须首先注册一个回调,该回调将在接收特殊类型的 BIN 消息时触发,方法如下:

/**
 * bin_register_cb - registers a module handler for specific packets
 * @mod_name: used to classify the incoming packets
 * @cb:       the handler function, called once for each matched packet
 *
 * @return:   0 on success
 */
int bin_register_cb(char *mod_name, void (*cb)(int))

回调只会针对 BIN 包的 mod_name 类触发,并且回调还会接收包类型,以便能够区分多种类型的事件(例如创建、更新、删除等)。

之后,您应该使用 pop 方法来提取包的内容:

/*
 * pops an str from the current position in the buffer
 * @info:   pointer to store the result
 *
 * @return: 0 on success
 *
 * Note: The pointer returned in @info str is only valid for the duration of
 *       the callback. Don't forget to copy the info into a safe buffer!
 */
int bin_pop_str(str *info)

/*
 * pops an integer value from the current position in the buffer
 * @info:   pointer to store the result
 *
 * @return: 0 on success
 */
int bin_pop_int(void *info)

请参阅专用于二进制接口的主页,了解如何为二进制包配置 OpenSIPS 侦听器

16. Module Development

16.1 Introduction

Due to the OpenSIPS modular architecture, the easiest way to add new features ( new parameters, script functions, MI function etc ) is to incorporate them into a new OpenSIPS module.
An OpenSIPS module is actually a shared library ( .so file ) which OpenSIPS can dynamically load at OpenSIPS startup, if the module is loaded from within the OpenSIPS script, by using the loadmodule directive :

这段话介绍了OpenSIPS的模块化架构以及如何添加新功能。由于OpenSIPS采用模块化的架构,最简单的方式来添加新功能(如新参数、脚本函数、MI函数等)是将它们合并到一个新的OpenSIPS模块中。

OpenSIPS模块实际上是一个共享库文件(.so文件),OpenSIPS可以在启动时动态加载这个模块,如果模块是从OpenSIPS脚本内部加载的,可以使用loadmodule指令来加载。这种模块化的方式使得在OpenSIPS中添加和扩展功能变得非常灵活和可控。模块可以包含特定功能的实现,然后通过加载模块,将这些功能引入到OpenSIPS中,从而扩展了OpenSIPS的能力。

loadmodule "mynewmod.so"

加载新模块后,OpenSIPS 核心将查找 struct module_exports 类型的导出变量。在开发新的 OpenSIPS 模块时,该结构和变量至关重要

 struct module_exports{
              char* name;                     /*!< null terminated module name */
              char *version;                  /*!< module version */
              char *compile_flags;            /*!< compile flags used on the module */
              unsigned int dlflags;           /*!< flags for dlopen */

              cmd_export_t* cmds;             /*!< null terminated array of the exported
                                           commands */
              param_export_t* params;         /*!< null terminated array of the exported
                                           module parameters */

              stat_export_t* stats;           /*!< null terminated array of the exported
                                           module statistics */

              mi_export_t* mi_cmds;           /*!< null terminated array of the exported
                                           MI functions */

              pv_export_t* items;             /*!< null terminated array of the exported
                                           module items (pseudo-variables) */

              proc_export_t* procs;           /*!< null terminated array of the additional
                                           processes required by the module */

              init_function init_f;           /*!< Initialization function */
              response_function response_f;   /*!< function used for responses,
                                           returns yes or no; can be null */
              destroy_function destroy_f;     /*!< function called when the module should
                                           be "destroyed", e.g: on opensips exit */
              child_init_function init_child_f;/*!< function called by all processes
                                            after the fork */
        };

module_exports 内容(以及上面的注释)是不言自明的。

接下来,我们将讨论 module_exports 结构的每个成员以及在构建 nw OpenSIPS 时如何使用它。纯粹作为示例,请参阅下面的对话框模块使用的导出

struct module_exports exports= {
              "dialog",        /* module's name */
              MODULE_VERSION,
              DEFAULT_DLFLAGS, /* dlopen flags */
              cmds,            /* exported functions */
              mod_params,      /* param exports */
              mod_stats,       /* exported statistics */
              mi_cmds,         /* exported MI functions */
              mod_items,       /* exported pseudo-variables */
              0,               /* extra processes */
              mod_init,        /* module initialization function */
              0,               /* reply processing function */
              mod_destroy,
              child_init       /* per-child init function */
        };

17. Module APIs

Within OpenSIPS, one modules might need to access the functionality of another module ( one very common example are modules desiring to do operations on a per dialog basis, thus needing part of the dialog module functionality ). Instead of directly accessing the functionality from within the target module, OpenSIPS heavily uses the concept of a ‘module exported API’.
The common approach used throughout OpenSIPS is that the target module should implement a form of loading it’s API - which in fact translates into populating a structure with pointers of the functions that need to be exported, as well as various other structure members that indicate various behavior.
The module that needs to operate with the above API should first call ( within its mod_init ) the function to bind to the needed module’s API, and then operate with the received structure.
Find below the most heavily used module APIs in OpenSIPS.

这段话讲述了在OpenSIPS内部,一个模块可能需要访问另一个模块的功能(一个非常常见的例子是希望在对话基础上执行操作的模块,因此需要部分对话模块的功能)。但是,与其直接从目标模块内部访问功能,OpenSIPS大量使用了“模块导出的API”概念。

在OpenSIPS中通常采用的方法是,目标模块应该实现一种加载其API的方式 - 实际上是通过填充一个结构体的指针来导出需要导出的函数,以及指示各种行为的其他结构体成员。

需要使用上述API的模块应首先在其mod_init函数内调用绑定到所需模块API的函数,然后使用接收到的结构进行操作。这种方法使得模块之间的通信更加模块化和可维护,模块可以通过API提供对其内部功能的访问,同时保持接口的清晰和可扩展性。最后,文中提到了在OpenSIPS中最常用的模块API。

18. Video Tutorial

A full video tutorial ( 7 video sessions of 1-2 hours ) going through the OpenSIPS development process can be found here , along with some source code examples used in the video tutorial.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值