美文网首页
sofia-sip跨平台编程指南

sofia-sip跨平台编程指南

作者: 通往心的路 | 来源:发表于2018-07-29 20:19 被阅读0次

原文: http://sofia-sip.sourceforge.net/refdocs/programming.html

编写跨平台代码

大部分Sofia-SIP软件都是跨平台的。所有的核心模块(或最低要求)都是用ANSI C89加上一点ANSI C99标准编写。如果有特定平台代码,都会放到特定的c源文件,用wrapper方法跟其他代码分开。SU模块就是把平台相关的功能做了抽象处理,比如内存管理,sockets,线程和时间函数。

ANSI C99特性

Sofia-SIP代码使用了的ANSI C99特性:

  • 定长整数
  • va_copy() 和 snprintf() 函数

不能再Sofia-SIP代码中使用的 ANSI C99特性:

  • 代码和变量定义交叉。(又翻译:再函数代码中间定义变量;即必须把定义放在函数开始)

定长整数类型

总所周知,整数长度取决于硬件,系统和编译器。这就意味着int或long不一定是32位。导致编码时,程序员难以确保int类型能保存目标值而没有溢出。
如果你能保证不溢出,使用int类型是没问题。最开始只有int类型的原因是效率考量,int是存储地最快(同时也是最大的长度)。
千万不要假设任何类型的长度,而是用sizeof()。
ANSI C99标准定义了以下定长整数

  • int64_t
  • uint64_t
  • int32_t
  • uint32_t
  • int16_t
  • uint16_t
  • int8_t
  • uint8_t

使用这些类型,你必须include <sofia-sip/su-types.h>,它处理好了正确的具体包含。如果su模块不可用,你必须在源码文件中,使用下面的代码段获取正确的include:

#if HAVE_STDINT_H
#include <stdint.h>
#elif HAVE_INTTYPES_H
#include <inttypes.h>
#else
#error Define HAVE_STDINT_H as 1 if you have <stdint.h>, \
 or HAVE_INTTYPES_H if you have <inttypes.h>
#endif

字节序

主机字节序在不同平台不一样。当你只在本地处理数据,不需要考虑字节序。如果你要跟网络打交道,你就需要考虑了。
如果你想转换字节序,以下的函数可以调用:

  • htonl() 主机 => 网络,参数:ulong
  • htons() 主机 => 网络,参数:ushort
  • ntohl() 网络 => 主机,参数:ulong
  • ntohs() 网络 => 主机,参数:ushort

你需要include <netinet/in.h>include <sofia-sip/su.h>

自定义类型对其(align)

默认情况下,编译会对齐结构体,以保证快速访问。这意味着大部分字段(field)都按32位对齐。如果你优化内存,你可能需要结构体(struct)对齐。
位了告诉编译器,你的一些字段仅需部分位(少于内建类型长度),你可以使用位域。

struct foo {
  unsigned bar:5;
  unsigned foo:2;
  unsigned :0;
  int      something;
}

如果编译器决定pack这个结构体,bar,foo在开始的7位,something 从下一个32位开始。
位域有个问题:在ARM,不能访问非32位开始的32位字段,所以例子中 有个 :0 对齐的字段,警告:这个结构体在一些ARM gcc编译器上,可能初始化失败。(Kai Vehmanen 知道详细)
一个强制编译器对齐结构体的方式是,使用预编译指令 #pargma(pack). 这个指令是编译器自定的,所以如果要编写跨平台的代码,就不能使用它,即使在Sofia-SIP中的一些地方使用了它。另一个方案是编写特定函数去从32位字段中获取特定位,但不太方便,容易出错。
同样的对齐问题同样发生在转换char buffer 到 int32_t。在ARM平台,你只能从完整的32位数据中读int32_t。
一句话:使用位域和结构体对齐时,小心跨平台陷阱。如果不是必须,别用。

文件和目录结构

一个Sofia-SIP模块放在libsofia-sip-ua目录下,并且包含了<modulename>.docs (<modulename>当然就是模块名)。
如果你想开发一个新模块,联系Sofia-SIP开发组,他们会帮你创建一个基本的模块
模块概览:

  • <modulename>.docs
    文档 详细请查看 Module documentation in <module>.docs
  • pictures
    文档中包含的图片,文件格式为GIF(html页面使用)和EPS(latex使用)。如果其他程序生成的图片,必须包含源文件。
  • Makefile.am
    详细请看 dealing with GNU Autotools
  • (可选)源代码和测试,源码同样可以放在子文件夹

编写面向对象代码

虽然C没有提供任何面向对象特性,仍能按照OO方式写代码。Sofia-SIP代码使用了很多OO特性。

数据隐藏(Data Hiding)

数据隐藏能是两个模块分离的很清晰。外部调用无法直接访问内部数据,但是能使用提供的方法。数据隐藏同样让两个对象之间的交互变得简单,所有交互都通过函数调用。
那C如何实现数据隐藏呢?最简单的方式是,在头文件中,只声明结构,不定义他们。Sofia-SIP中,我们在<sofia-sip/msg.h>定义了typedef struct msg_s msg_t, 但是类型的定义在 msg_internal.h中。在msg模块外部就没法访问msg_t内部变量,但是能访问<sofia-sip/msg.h>中声明的函数。msg_t的定义同样能够自由的改变,只要接口(<sofia-sip/msg.h>中声明的函数)不变。

接口(Interface)

Sofia-SIP同样使用了抽象结构,消息头解析,定义在<sofia-sip/msg_types.h>。消息头类型msg_header_t是用两个C类型定义的: struct msg_common_sstruct msg_hclass_s
抽象结构使用在msg_hclass_t定义的虚函数表实现的,非常像c++实现的抽象类和虚函数。为了实现每一个头,函数表定义了函数指针去实现具体功能。不像C++,对象类(msg_hclass_t)是由真实结构表示的。

sip_contact_class[] =
  {{
    /* hc_hash: */     sip_contact_hash,
    /* hc_parse: */    sip_contact_d,
    /* hc_print: */    sip_contact_e,
    /* hc_dxtra: */    sip_contact_dup_xtra,
    /* hc_dup_one: */  sip_contact_dup_one,
    /* hc_update: */   sip_contact_update,
    /* hc_name: */     "Contact",
    /* hc_len: */      sizeof("Contact") - 1,
    /* hc_short: */    "m",
    /* hc_size: */     MSG_ALIGN(sizeof(sip_contact_t), sizeof(void*)),
    /* hc_params: */   offsetof(sip_contact_t, m_params),
    /* hc_kind: */     msg_kind_append,
    /* hc_critical: */ 0
   }};

继承

Sofia较少的使用了继承。最常见的使用inheritance是su_home_t。许多对象都继承至su_home_t, 这就意味着他们能使用home-based内存管理函数,在<su_alloc.h>声明。
这个场景中,继承意味着指向子类的指针能强转(cast)为指向基类的指针。也就是说,子类对象必须在开始处定义基类对象:

struct derived
{
  struct base base[1];
  int         extra;
  char       *data;
};

因此有三种办法把指向子类的指针指向基类:

struct base *base1 = (struct base *)derived;
struct base *base2 = &derived->base;
struct base *base3 = derived->base;

第三个能够通过编译器是因为base是一个单元素数组。

模板

Sofia-SIP使用宏实现了模板的功能。包括:

  • hash table
    定义在 <sofia-sip/htable.h>
  • red-black tree
    定义在 <sofia-sip/rbtree.h>

内存管理

home-based 内存管理机制,在需要分配许多内存块的情况下非常有用。分配器是通过分配中心保存各个分配内存块的引用来实现的。当分配中心释放,所有它保持引用的内存块都会被释放。
请查看<sofia-sip/su_alloc.h>和memory managment tutorial查看更多内容。

上下文内存管理

典型的例子就是使用su_home_t作为操作上下文的一部分

  struct context {
    su_home_t  ctx_home[1];      /* memory home */
    other_t   *ctx_other_stuff;  /* example of memory areas */
    ...
  };

  /* context pointer */
  struct context *ctx;

  /* Allocate memory for context structure and initialize memory home */
  ctx = su_home_clone(NULL, sizeof (struct context));

  /* Allocate memory and register it with memory home */
  ctx->ctx_other_stuff = su_zalloc(ctx->ctx_home, sizeof(other_t));

  ... processing and allocating more memory ...

  /* Release registered memory areas, home, and context structure */
  su_home_zap(ctx->ctx_home);

Combining allocations

Another place where home-based memory management makes programmers life easier is case where a sub-procedure makes multiple memory allocations and, in case the sub-procedure fails, all the allocations must be released and, in case the sub-procedure is succesfull, all allocations must be controlled by upper level memory management.

  int sub_procedure( su_home_t *top_home, ... )
  {
    su_home_t temphome[1] = { SU_HOME_INIT(temphome) };

    ... allocations and other processing ...

    /* was processing successfull ? */
    if (success) {
      /* ok -> move registered allocated memory to upper level memory home */
      su_home_move( top_home, temphome );
    }
    /* destroy temporary memory home (and registered allocations) */
    /* Note than in case processing was succesfull the memory     */
    /* registrations were already moved to upper level home.      */
    su_home_deinit(temphome);

    /* return ok/not-ok */
    return success;
  }

相关文章

网友评论

      本文标题:sofia-sip跨平台编程指南

      本文链接:https://www.haomeiwen.com/subject/wiftvftx.html