FreeSwitch是一款优秀的开源软件,里面很多代码技巧值得大家学习。
1.条件判断转化成函数
比如nta.c:2768,tport_is_stream把复杂的条件判断提取成一个函数,
利用函数命名tport_is_stream让代码清晰易读。
stream = tport_is_stream(tport);
/* Try to use compression on reverse direction if @Via has comp=sigcomp */
if (stream &&
sip->sip_via && sip->sip_via->v_comp &&
tport_can_send_sigcomp(tport) &&
tport_name(tport)->tpn_comp == NULL &&
tport_has_compression(tport_parent(tport), sip->sip_via->v_comp)) {
tport_set_compression(tport, sip->sip_via->v_comp);
}
/** Test if transport is stream. */
int tport_is_stream(tport_t const *self)
{
return self && !self->tp_pre_framed && self->tp_addrinfo->ai_socktype == SOCK_STREAM;
}
2.数据隐藏
数据隐藏是让两个模块间分隔清晰的一种实践方法(Data hiding is a practice that makes separation between two modules very clear.)。模块外的代码无法直接访问模块内的数据,只能通过模块提供的方法达到访问数据的目的。数据隐藏也让定义两个对象间的协议变得更简单,所有协议均通过函数调用来实现。
C语言中如何实现数据隐藏?最简单的回答是只在头文件中申明数据结构但不定义它们。在<sofia-sip/msg.h>头文件中有一个针对结构体struct msg_s的typedef msg_t类型,但实际的结构体是在msg_internal.h头文件中定义。msg模块外的代码无法直接访问msg_s结构体,只能通过<sofia-sip/msg.h>头文件中提供的函数来访问。msg的实现使得改变结构体内部非常自由,只需保持函数接口不变即可。
msg_types.h
/** Message object. */
typedef struct msg_s msg_t;
msg_internal.h
struct msg_s {
su_home_t m_home[1]; /**< Memory home */
msg_mclass_t const *m_class; /**< Message class */
int m_oflags; /**< Original flags */
msg_pub_t *m_object; /**< Public view to parsed message */
size_t m_maxsize;/**< Maximum size */
size_t m_size; /**< Total size of fragments */
msg_header_t *m_chain; /**< Fragment chain */
msg_header_t **m_tail; /**< Tail of fragment chain */
msg_payload_t *m_chunk; /**< Incomplete payload fragment */
/* Parsing/printing buffer */
struct msg_mbuffer_s {
char *mb_data; /**< Pointer to data */
usize_t mb_size; /**< Size of buffer */
usize_t mb_used; /**< Used data */
usize_t mb_commit; /**< Data committed to msg */
unsigned mb_eos:1; /**< End-of-stream flag */
unsigned :0;
} m_buffer[1];
msg_buffer_t *m_stream; /**< User-provided buffers */
size_t m_ssize; /**< Stream size */
unsigned short m_extract_err; /**< Bitmask of erroneous headers */
/* Internal flags */
unsigned m_set_buffer:1;/**< Buffer has been set */
unsigned m_streaming:1; /**< Use streaming with message */
unsigned m_prepared:1; /**< Prepared/not */
unsigned :0;
msg_t *m_next; /**< Next message */
msg_t *m_parent; /**< Reference to a parent message */
int m_refs; /**< Number of references to this message */
su_addrinfo_t m_addrinfo; /**< Message addressing info (protocol) */
su_sockaddr_t m_addr[1]; /**< Message address */
int m_errno; /**< Errno */
};
3.接口
抽象接口是在Sofia库中使用的另一项面向对象实践。在<sofia-sip/msg_types.h>头文件中定义的解析器头(Parser headers)是抽象接口的一个很好的示例。消息头类型msg_header_t使用两个结构体来定义:struct msg_common_s(msg_common_t)和struct msg_hclass_s(msg_hclass_t)。
在msg_hclass_t结构体中使用虚函数表方式来实现抽象接口。这有点类似C++中实现抽象类和虚函数的方式。对每个头的实现来说(For implemenation of each header),用负责编码、解码和操作头结构体的函数来初始化函数表。与C++不同,对象(msg_hclass_t)的类用实际数据结构体来表示,结构体可以包括头特定数据,例如头名称。
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
}};
4.继承和后代对象
继承在Sofia库中作为有限使用的面向对象实践出现。继承的通常例子就是su_home_t的使用。很多对象继承自su_home_t,这意味着继承对象可以使用很多来自<su_alloc.h>基于home的内存管理函数。
从这个角度看,继承意味着一个指向继承对象的指针可以强制转换成指向基类对象的指针。或者说,继承类的内存空间最前部必须是基类对象:
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是只有一个元素的数组。
5.宏的使用
代码简化
比如tport.c的STACK_RECV宏,因为结构体比较复杂,利用宏就可以让代码看起来很简洁,值得借鉴。
/** Pass message to the protocol stack */
void
tport_base_deliver(tport_t *self, msg_t *msg, su_time_t now)
{
STACK_RECV(self, msg, now);
}
#define STACK_RECV(tp, msg, now) \
(tp)->tp_master->mr_tpac->tpac_recv((tp)->tp_master->mr_stack, (tp), \
(msg), (tp)->tp_magic, (now))
错误处理简化
简化错误处理,比如tport.c的tport_listen函数,
static
tport_primary_t *tport_listen(tport_master_t *mr,
tport_vtable_t const *vtable,
tp_name_t tpn[1],
su_addrinfo_t *ai,
tagi_t *tags)
{
tport_primary_t *pri = NULL;
int err;
int errlevel = 3;
char buf[TPORT_HOSTPORTSIZE];
char const *protoname = vtable->vtp_name;
char const *culprit = "unknown";
su_sockaddr_t *su = (void *)ai->ai_addr;
/* Log an error, return error */
#define TPORT_LISTEN_ERROR(errno, what) \
((void)(err = errno, \
((err == EADDRINUSE || err == EAFNOSUPPORT || \
err == ESOCKTNOSUPPORT || err == EPROTONOSUPPORT || \
err == ENOPROTOOPT ? 7 : 3) < SU_LOG_LEVEL ? \
su_llog(tport_log, errlevel, \
"%s(%p): %s(pf=%d %s/%s): %s\n", \
__func__, pri ? (void *)pri : (void *)mr, what, \
ai->ai_family, protoname, \
tport_hostport(buf, sizeof(buf), su, 2), \
su_strerror(err)) : (void)0), \
tport_zap_primary(pri), \
su_seterrno(err)), \
(void *)NULL)
/* Create a primary transport object for another transport. */
pri = tport_alloc_primary(mr, vtable, tpn, ai, tags, &culprit);
if (pri == NULL)
return TPORT_LISTEN_ERROR(su_errno(), culprit);
if (pri->pri_primary->tp_socket != INVALID_SOCKET) {
int index = 0;
tport_t *tp = pri->pri_primary;
su_wait_t wait[1] = { SU_WAIT_INIT };
if (su_wait_create(wait, tp->tp_socket, tp->tp_events) == -1)
return TPORT_LISTEN_ERROR(su_errno(), "su_wait_create");
/* Register receiving or accepting function with events specified above */
index = su_root_register(mr->mr_root, wait, tport_wakeup_pri, tp, 0);
if (index == -1) {
su_wait_destroy(wait);
return TPORT_LISTEN_ERROR(su_errno(), "su_root_register");
}
tp->tp_index = index;
}
pri->pri_primary->tp_has_connection = 0;
SU_DEBUG_5(("%s(%p): %s " TPN_FORMAT "\n",
__func__, (void *)pri, "listening at",
TPN_ARGS(pri->pri_primary->tp_name)));
return pri;
}
6. 利用Table简化代码逻辑
比如nua_server.c的nua_stack_process_request函数处理,根据nua_server_methods这个table来查找要处理的方法,从而避免了switch,case。这个技巧也非常值得学习。
int nua_stack_process_request(nua_handle_t *nh,
nta_leg_t *leg,
nta_incoming_t *irq,
sip_t const *sip)
{
nua_server_methods_t const *sm;
if (method > sip_method_unknown && method <= sip_method_publish)
sm = nua_server_methods[method];
else
sm = nua_server_methods[0];
sm->sm_init(sr);
nh = nua_stack_incoming_handle(nua, irq, sip, create_dialog);
sm->sm_preprocess(sr);
nua_server_report(sr);
}
nua_server_methods_t const *nua_server_methods[] = {
/* These must be in same order as in sip_method_t */
&nua_extension_server_methods,
&nua_invite_server_methods, /**< INVITE */
NULL, /**< ACK */
NULL, /**< CANCEL */
&nua_bye_server_methods, /**< BYE */
&nua_options_server_methods, /**< OPTIONS */
&nua_register_server_methods, /**< REGISTER */
&nua_info_server_methods, /**< INFO */
&nua_prack_server_methods, /**< PRACK */
&nua_update_server_methods, /**< UPDATE */
&nua_message_server_methods, /**< MESSAGE */
&nua_subscribe_server_methods,/**< SUBSCRIBE */
&nua_notify_server_methods, /**< NOTIFY */
&nua_refer_server_methods, /**< REFER */
&nua_publish_server_methods, /**< PUBLISH */
NULL
};
7.可变参数的处理
sofia-sip库很多函数都用了可变参数,所以要读懂sofia-sip库代码,这块的处理也必须要掌握。
Tagging是Sofia软件包中的一项将参数打包给函数的机制。它允许将具备不固定类型的可变个数参数传递给函数。对应用程序开发人员而言,tagging以宏的形式出现用来封装参数。展开tagging宏后将得到一个包含tag(说明参数的类型)和值(不透明数据的指针)的结构体。
int nua_client_tcreate(nua_handle_t *nh,
int event,
nua_client_methods_t const *methods,
tag_type_t tag, tag_value_t value, ...)
{
int retval;
ta_list ta;
ta_start(ta, tag, value);
retval = nua_client_create(nh, event, methods, ta_args(ta));
ta_end(ta);
return retval;
}
调用例子:
sip_rack_t rack[1];
sip_rack_init(rack);
rack->ra_response = sip->sip_rseq->rs_response;
rack->ra_cseq = sip->sip_cseq->cs_seq;
rack->ra_method = sip->sip_cseq->cs_method;
rack->ra_method_name = sip->sip_cseq->cs_method_name;
error = nua_client_tcreate(nh, nua_r_prack,
&nua_prack_client_methods,
SIPTAG_RACK(rack),
TAG_END());
#define TAG_END() (tag_type_t)0, (tag_value_t)0
typedef struct {
tagi_t tl[2];
va_list ap;
} ta_list;
typedef intptr_t tag_value_t;
/** Tag item. */
typedef struct {
tag_type_t t_tag; /**< Tag */
tag_value_t t_value; /**< Value */
} tagi_t;
struct tag_type_s {
char const *tt_ns; /**< Tag namespace (e.g., "sip" or "nua") */
char const *tt_name; /**< Tag name (e.g, "min_se") */
tag_class_t *tt_class; /**< Tag class defines the type of the value */
tag_value_t tt_magic; /**< Class-specific data
(e.g., pointer to header class structure) */
};
f(xxx,...,tag_type_t tag, tag_value_t value, ...)
ta_list ta;
do
{
tag_type_t ta_start__tag = (tag); tag_value_t ta_start__value = (value); __builtin_va_start( (ta).ap, (value) ); while ( (ta_start__tag) == tag_next && (ta_start__value) != 0 )
{
ta_start__tag = ( (tagi_t *) ta_start__value)->t_tag; if ( ta_start__tag == tag_null || ta_start__tag == ( (void *) 0) )
break;
if ( ta_start__tag == tag_next )
{
ta_start__value = ( (tagi_t *) ta_start__value)->t_value;
} else { ta_start__tag = tag_next; break; }
}
(ta).tl->t_tag = ta_start__tag; (ta).tl->t_value = ta_start__value; if ( ta_start__tag != ( (void *) 0) && ta_start__tag != tag_null && ta_start__tag != tag_next )
{
va_list ta_start__ap; __builtin_va_copy( (ta_start__ap), ( (ta).ap) ); (ta).tl[1].t_tag = tag_next; (ta).tl[1].t_value = (tag_value_t) tl_vlist( ta_start__ap ); __builtin_va_end( ta_start__ap );
} else { (ta).tl[1].t_value = 0; (ta).tl[1].t_value = (tag_value_t) 0; }
}
while ( 0 );