1
引言
随着网络技术的发展,出现了许多新的基于网络的服务,
VoIP
就是其中非常重要的一项。用于实现
VoIP
的协议主要包括
H.323
、
SIP(Session
Initiation Protocol
,会话发起协议
)
和
MGCP(Media Gateway Control Protocol
,媒体网关控制协议
)
,而
SIP
又是其中最有发展前景的一个协议。SIP是由
IETF
(
Internet
工程任务组)提出的IP电话信令协议。基于
SIP
协议标准,整合传统的语音及增值服务,并提供最新的即时通信服务以及
IP
网络上的视频服务,并且可以为其他更多的增值应用服务提供一个标准的具有高扩展性的平台。系统平台完全采用
Internet
的分布式的体系结构,具有高度的灵活性和可扩展性。正是由于
SIP
这一系列的优点,使得其广泛应用于下一代网络中,成为下一代网络的标准协议之一。
代理服务器是
SIP
系统中的一个非常重要的元素,负责接收用户代理发来的请求,根据网络策略将请求发给相应的服务器,并根据收到的应答对用户作出响应。它可以根据需要对收到的消息改写后再发出(
SIP
代理服务器如图
1
)。一般来说,一个
SIP
代理服务器会同时收到许多呼叫请求和应答,代理服务器必须维护与
Call
相关的所有信息,例如
Call
、
Dialog
、
Transaction
等。而一个有状态的代理服务器又可以“分支”一个请求,路由它到多个地点,从而得到来自多个终端的应答。传统的代理服务器的实现是把事务信息用一个单链表组织起来,当收到一个请求或应答时,就采用顺序搜索的方式在单链表中进行查找,从而进行事务匹配。当代理服务器要同时处理多个消息时,每个消息都要在单链表中进行顺序查找,这样代理服务器的性能就会降低。本文中,我们设计了一种新的存储事务信息的结构,采用哈希表的方式来在内存中进行数据的存储。这样当需要进行事务匹配的时候,就可以采用基于哈希函数的搜索算法,提高查找的速度,从而提高整个代理服务器的性能。
2
lhash
介绍
在一般的数据结构如线性表和树中,记录在结构中的相对位置是与记录的关键字之间不存在确定的关系,在结构中查找记录时需进行一系列的关键字比较。这一类查找方法建立在“比较”的基础上,查找的效率与比较次数密切相关。理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立确定的对应关系,使每个关键字和结构中一个唯一的存储位置相对应。在查找时,只需根据这个对应关系找到给定值。这种对应关系即是哈希函数,按这个思想建立的表为哈希表。
哈希表存在冲突现象:不同的关键字可能得到同一哈希地址。在建造哈希表时不仅要设定一个好的哈希函数,而且要设定一种处理冲突的方法。
本文中实现哈希表用的是
lhash
库
[2]
,此库已经被包含在所有的
OpenSSL
和
SSLeay
中。
Openssl
函数使用哈希表来加快查询操作,并能存放任意形式的数据,比如配置文件的读取、内存分配中被分配内存的信息等。其中被
用到的基本函数如下所示。
1) LHASH *lh_new (LHASH_HASH_FN_TYPE h, LHASH_COMP_FN_TYPE c)
功能:生成哈希表
说明:输入参数
h
为哈希函数,
c
为比较函数。这两个函数都是回调函数。 因为哈希表用于存放任意的数据结构,哈希表存放、查询、删除等操作都需要比较数据和进行哈希运算,而哈希表不知道用户数据如何进行比较,也不知道用户数据结构中需要对哪些关键项进行散列运算。所以,用户必须提供这两个回调函数。
2) void *lh_delete(LHASH *lh, const void *data)
功能:删除散列表中的一个数据
说明:
data
为数据结构指针。
3) void lh_doall(LHASH *lh, LHASH_DOALL_FN_TYPE func)
功能:处理哈希表中的所有数据
说明:
func
为外部提供的回调函数,本函数遍历所有存储在哈希表中的数据,每个数据被
func
处理。
4) void lh_doall_arg(LHASH *lh, LHASH_DOALL_ARG_FN_TYPE func, void *arg)
功能:处理哈希表中所有数据
说明:此参数类似于
lh_doall
函数,
func
为外部提供的回调函数,
arg
为传递给
func
函数的参数。本函数遍历所有存储在哈希表中的数据,每个数据被
func
处理。
5) void lh_free(LHASH *lh)
功能:释放哈希表。
6
)
void *lh_insert(LHASH *lh, void *data)
功能:往哈希表中添加数据。
说明:
data
为需要添加数据结构的指针地址。
7
)
void *lh_retrieve(LHASH *lh, const void *data)
功能:查询数据。
说明:从哈希表中查询数据,
data
为数据结构地址,此数据结构中必须提供关键项(这些关键项对应于用户提供的哈希函数和比较函数)以供查询,如果查询成功,返回数据结构的地址,否则返回
NULL
。
3
基于lhash的代理服务器的实现
为了使得哈希表符合我们的实际需要,我们相应地设计了结构体和
API
函数[4]。
3.1
哈希表的结构体
我们为访问
lhash
库定义了一个新的结构体。
Typedef struct jsHash{
char *key;
//
关键字串
void *value;
//
指向事务信息存储的地址
}jsHash_t;
其中
key
用于事务信息管理的键值,
value
域中存放的是实际存储事务信息的内存地址。当我们需要从哈希表中提取一些数据时,可以通过强制类型转换把
void
类型转换成为我们实际需要的数据类型。
3.2
API
函数的实现
1
)
int JSHASH_init(int num)
功能:用于创建和初始化哈希表。
2
)
int JSHASH_insert(int table_id,jsHash_t *jsHash)
功能:插入给定的数据到给定的哈希表中。
3
)
jsHash_t *JSHASH_retrieve(int table_id,char *key)
功能:根据给定的键值从给定的哈希表中提取数值。
4
)
jsHash_t *JSHASH_remove(int table_id,char *key)
功能:根据给定的键值从给定的哈希表中删除数据。
5
)
int JSHASH_doall(int table_id,void (*func)())
功能:处理给定哈希表中的所有数据。
说明:
func
是一个指向函数的指针,本函数遍历所有存储在哈希表中的数据,每个数据被
func
处理。
4
基于
lhash
的事务匹配
下面我们将描述在代理服务器中如何使用新的基于哈希表存储的搜索算法来进行事务匹配。我们以请求消息为例来进行分析,对于响应消息可以进行类似的处理。
4.1
基于
lhash
的
SIP
事务匹配算法
通过
RFC3261
对
SIP
的描述,我们已经知道
SIP
信令信息可分为
Call
、
Dialog
和
Transaction
,
而事务又可分为服务器端事务和客户端事务。在
SIP
代理服务器中,一个
SIP
请求又可能通过“分支”被路由到多个终端。因此
SIP
代理服务器必须为客户端的一个请求维护多个事务。传统上,我们把若干个事务组成一个单链表,当需要进行事务匹配时,首先锁定这个单链表,然后在单链表上进行顺序搜索。同样的,在进行增加,删除,读取事务信息时都要进行相应的操作,
从而降低了整个代理服务器的性能。我们可以简单地概述这种基于字符串比较的顺序搜索的算法如下:
for(all Call)
if (Compare(Call_id)==match)
for(all Dialog)
if(Compare(From,To)==match)
for(all Transaction)
if(Compare(Cseq,Branch)==match)
return Transaction
而使用了基于哈希表的算法后,我们也可以简单地进行概述如下:
index=MakeHashIndex(Call_id,From,To,Cseq,Branch);
HashRetrieve(index);
4.2
请求事务匹配过程
当
SIP
定理服务器收到请求时,它执行
SIP
语法检查、认证检查,然后进入内部逻辑处理去搜索事务信息,接着执行代理服务器剩下的步骤。因此我们可以把顺序搜索事务信息这一步替换成为使用我们改进的哈希搜索算法来进行事务匹配,如图
2
所示。
在顺序搜索算法中,当服务器解析消息后,提取关键字信息,然后开始去查找
Call
。如果它在呼叫消息的存储区中发现了这个
Call
,
那么就从取得的
Call
数据中继续查找
Dialog
和
Transaction
。
在哈希搜索算法中,服务器通过调用
MakeHashIndex
函数生成键值,然后调用
HashRetrieve
函数去直接访问与相应键值相对应的存储区。
5
小结
在本文中,我们描述了怎样使用哈希算法来提高服务器的处理性能。哈希算法虽然相应地提高了处理器的性能,但由于使用了哈希表,它也增加了额外的内存空间。为了保证此算法的健壮性及防止内存泄漏,我们还需要通过测试对此算法进行相应的性能评估。在以后的研究中,将对此算法进行改进,并希望能够用在
SIP
系统的其它地方,从而提高整个
SIP
系统的性能。