AC_BM算法在内容过滤中的实现
1.另一个相似算法的介绍
http://www.win.tue.nl/~watson/2R080/opdracht/p333-aho-corasick.pdf
2.AC_BM的实现(为我的设计和总结) 4
3.ac_bm 在内容过滤中的应用... 7
AC_BM算法在模式匹配中的实现
在了解AC_BM实现之前,请参阅纪烨的<关于多模式字符串匹配的研究和实现>。(见附录一)
由于只给出了AC_BM的工作过程与实现思路,而并没有相应的算法描述,所以只能由另一相似的算法改进成我们所需要的AC_BM算法。首先介绍一下这种与AC_BM相似的算法。
1.另一个相似算法的介绍
首先介绍一下在网上查找到的文章,名为:"Efficient String Matching:An Aid to Bibliographic Search"。其中介绍了一种类似AC_BM的算法,它也可以对多字符串进行匹配查找,只不过它是从待匹配串的左侧向右进行的。然后再对这个算法进行改动,以实现AC_BM算法。这篇文章原文是英文的,以下为我的翻译,也许会有表述不清的地方,请参阅原文。
Efficient String Matching:An Aid to Bibliographic Search
设K = {Yl,Y2 . . . . . Yk}是一个字符串的集合,里面的每一个元素都是一个字符串,这里我们称之为关键字。设x是一个任意的字符串,称之为待匹配串。当前的目的是在x中查找所有存在于K中的关键字。
首先,建立一个模式匹配自动机:
Fig. 1. Pattern matching machine.
(a) Goto function.
i 1 2 3 4 5 6 7 8 9
f ( i ) 0 0 0 1 2 0 3 0 3
(b) FailureNnction.
i output ( i )
2 {he}
5 {she, he}
7 {his}
9 {hers}
(c) Output function.
图1(a)中是一个匹配的自动机,其描述了对关键字{"he","she","his","hers"}的匹配过程。
图1(b)中是f( )函数的输入、输出值(后面会介绍)。
图1(c)中是在结点i处,可以匹配到的关键字。
状态0是起始状态。图1中的所示的状态有0,1,2,......9。goto函数g提供了一种映射:将当前状态及下一个输入的符号映射到一个新状态中或是映射为失败信号fail,如图1(a)所示。如在状态0中接收到字符'h',而进行状态1,可以表示为g(0, h) = 1。不存在的字符指向状态fail。因此,g(1,x )= f a i l,因为对于状态1来说,x既不是i也不是e。
匹配失败的函数f( )给出了另一个映射,它用在goto函数指向fail的情况下。一个确定的状态说明有可能存在一组已经发生的匹配,output函数就指出了每一个状态中发生匹配的内容(可能为空)。
模式匹配自动机的操作循环定义如下:
1. 如果g ( s , a ) = s',自动机进行goto转换,将当前状态转换到s',而将x中的下一个字符看作是当前字符。另外,如果output(s')不为空,则自动机输出output(s')中的字符串,这个查找循环结束。
2. 如果g ( s , a ) = f a i l,则自动机就会参考函数f( )。如果f ( s ) = s',则自动机就将当前状态转到s',并将字符'a'做为当前字符。
算法1:在待匹配串x中查找关键字。
输入:待匹配串x, g ( ), f ( ), output ( )。
输出:关键字的位置。
begin
state ~ 0
for i ~ 1 until n do
begin
while g (state, a i ) = fail do state= f ( s t a t e )
state= g (state, ai )
if output (state) != empty then
begin
print I
print output (state)
end
end
end
构造g( ), f( ), output( )
先明确一个定义depth:某结点(状态)的depth就是这个结点到根结点的字符串的长度。如图1,状态0的depth为0;1和3的depth为1;2、4、6的depth为2等等。
需要通过g( )来计算f( )。首先计算depth为1的状态的f ( )值,再计算depth为2的f( )值,依此类推。状态0没有f( )值,因为状态0对所有的字符a都存在有合法的g(0,a)值。
为了计算depth为d的状态s的f()值,我们对每一个depth为d-1的状态r如下操作:
1. 如果g(r,a) = fail对每一个字符a都成立,则不进行操作,得到下一个r,重复1。
2. 如果存在一个a,使g(r,a) =s,进行如下操作,否则得到下一个r,重复1。
a) 设置state = f (r)。
b) 执行0次或多次state = f (state),直到g (state,a)!=fail。(因为g (0,a)!=fail,所以此条件总可以满足。
c) f (s) = g (state,a)。
如:为了计算图1(a)中的f ( )值,我们可以首先设置f(1 ) = f ( 3 ) = 0 因为它们的depth为1。接下来可以计算depth为2的状态2、4、6的f ( ) 值。为了计算f ( 2 ) , 设state = f(1 ) = 0; 因为 g(0, e)= 0, 所以f ( 2 ) = 0. 为了计算 f ( 6 ) , 设state= f ( 1 ) = 0,因为 g(0, i) = 0,所以 f ( 6 )= 0。为了计算f ( 4 ) , 设state = f ( 3 ) = 0,因为 g(0, h) = 1, 所以f ( 4 ) = 1。用这种方法可以得到图1(b)中所示的f ( )值。
计算g( ) , f( ) output( ) 的算法如下:
Algorithm 2. 构造g( )
输入: 一组关键字: K = {Yl, Y2 . . . . . Yk}.
输出:g()及不完整的 output( )
Method:假设在计算的开始时 output(s)为空,g ( s , a) = f a i l如果a或 g(s, a) 没有被定义。函数 enter(y)是将关键字y插入到goto树中。
begin
newstate .-- 0
for i = 1 until k do enter(y i )
for all asuch that g(O, a) = f a i l d o g(O, a) = 0
end
procedure enter(a 1 a 2 • • • a m ):
begin
state = 0; j = 1
while g (state, aj ) != f a i l do
begin
state = g (state, a) )
j=j +1
end
for p = j until m do
begin
newstate = newstate + 1
g (state, ap ) = newstate
state = newstate
end
output(state) ~ { a I a 2 . . . a m}
end
下面的算法是用来计算f( )及完整的output( )的。
Algorithm 3 构造f( ) 及完整的output( )
输入:g( )及由算法2构造的output()函数
输出:f( ) 及完整的output( )
begin
queue=empty
for each a such that (g(O, a ) = s) != 0 do
begin
queue = queue ∪ {s }
f ( s ) = 0
end
while queue != empty do
begin
let r be the next state in queue
queue = queue - {r}
for each asuch that( g(r, a ) = s) != f a i l do
begin
queue = queue∪ {s }
state = f ( r )
while g (state, a ) = f a i l do state= f ( s t a t e )
f ( s ) = g(state, a)
output(s) = output(s) ∪ o u t p u t ( f ( s ) )
end
end
end
2.AC_BM的实现(以下为我的设计和总结)
首先,建立一个模式匹配自动机:
Fig. 1. Pattern matching machine.
(a) Goto function.
i 1 2 3 4 5 6 7 8 9
f ( i ) 0 0 0 1 2 0 3 0 3
(b) FailureNnction.
i output ( i )
2 {he}
5 {she, he}
7 {his}
9 {hers}
(c) Output function.
图1(a)中是一个匹配的自动机,其描述了对关键字{"he","she","his","hers"}的匹配过程。
图1(b)中是f( )函数的输入、输出值(后面会介绍)。
图1(c)中是在结点i处,可以匹配到的关键字。
状态0是起始状态。图1中的所示的状态有0,1,2,......9。goto函数g提供了一种映射:将当前状态及下一个输入的符号映射到一个新状态中或是映射为失败信号fail,如图1(a)所示。如在状态0中接收到字符'h',而进行状态1,可以表示为g(0, h) = 1。不存在的字符指向状态fail。因此,g(1,x )= f a i l,因为对于状态1来说,x既不是i也不是e。
匹配失败的函数f( )给出了另一个映射,它用在goto函数指向fail的情况下。一个确定的状态说明有可能存在一组已经发生的匹配,output函数就指出了每一个状态中发生匹配的内容(可能为空)。
模式匹配自动机的操作循环定义如下:
1. 如果g ( s , a ) = s',自动机进行goto转换,将当前状态转换到s',而将x中的下一个字符看作是当前字符。另外,如果output(s')不为空,则自动机输出output(s')中的字符串,这个查找循环结束。
2. 如果g ( s , a ) = f a i l,则自动机就会参考函数f( )。如果f ( s ) = s',则自动机就将当前状态转到s',并将字符'a'做为当前字符。
算法1:在待匹配串x中查找关键字。
输入:待匹配串x, g ( ), f ( ), output ( )。
输出:关键字的位置。
begin
state ~ 0
for i ~ 1 until n do
begin
while g (state, a i ) = fail do state= f ( s t a t e )
state= g (state, ai )
if output (state) != empty then
begin
print I
print output (state)
end
end
end
构造g( ), f( ), output( )
先明确一个定义depth:某结点(状态)的depth就是这个结点到根结点的字符串的长度。如图1,状态0的depth为0;1和3的depth为1;2、4、6的depth为2等等。
需要通过g( )来计算f( )。首先计算depth为1的状态的f ( )值,再计算depth为2的f( )值,依此类推。状态0没有f( )值,因为状态0对所有的字符a都存在有合法的g(0,a)值。
为了计算depth为d的状态s的f()值,我们对每一个depth为d-1的状态r如下操作:
1. 如果g(r,a) = fail对每一个字符a都成立,则不进行操作,得到下一个r,重复1。
2. 如果存在一个a,使g(r,a) =s,进行如下操作,否则得到下一个r,重复1。
a) 设置state = f (r)。
b) 执行0次或多次state = f (state),直到g (state,a)!=fail。(因为g (0,a)!=fail,所以此条件总可以满足。
c) f (s) = g (state,a)。
如:为了计算图1(a)中的f ( )值,我们可以首先设置f(1 ) = f ( 3 ) = 0 因为它们的depth为1。接下来可以计算depth为2的状态2、4、6的f ( ) 值。为了计算f ( 2 ) , 设state = f(1 ) = 0; 因为 g(0, e)= 0, 所以f ( 2 ) = 0. 为了计算 f ( 6 ) , 设state= f ( 1 ) = 0,因为 g(0, i) = 0,所以 f ( 6 )= 0。为了计算f ( 4 ) , 设state = f ( 3 ) = 0,因为 g(0, h) = 1, 所以f ( 4 ) = 1。用这种方法可以得到图1(b)中所示的f ( )值。
计算g( ) , f( ) output( ) 的算法如下:
Algorithm 2. 构造g( )
输入: 一组关键字: K = {Yl, Y2 . . . . . Yk}.
输出:g()及不完整的 output( )
Method:假设在计算的开始时 output(s)为空,g ( s , a) = f a i l如果a或 g(s, a) 没有被定义。函数 enter(y)是将关键字y插入到goto树中。
begin
newstate .-- 0
for i = 1 until k do enter(y i )
for all asuch that g(O, a) = f a i l d o g(O, a) = 0
end
procedure enter(a 1 a 2 • • • a m ):
begin
state = 0; j = 1
while g (state, aj ) != f a i l do
begin
state = g (state, a) )
j=j +1
end
for p = j until m do
begin
newstate = newstate + 1
g (state, ap ) = newstate
state = newstate
end
output(state) ~ { a I a 2 . . . a m}
end
下面的算法是用来计算f( )及完整的output( )的。
Algorithm 3 构造f( ) 及完整的output( )
输入:g( )及由算法2构造的output()函数
输出:f( ) 及完整的output( )
begin
queue=empty
for each a such that (g(O, a ) = s) != 0 do
begin
queue = queue ∪ {s }
f ( s ) = 0
end
while queue != empty do
begin
let r be the next state in queue
queue = queue - {r}
for each asuch that( g(r, a ) = s) != f a i l do
begin
queue = queue∪ {s }
state = f ( r )
while g (state, a ) = f a i l do state= f ( s t a t e )
f ( s ) = g(state, a)
output(s) = output(s) ∪ o u t p u t ( f ( s ) )
end
end
end
2.AC_BM的实现(以下为我的设计和总结)
有了g( ), f( ), output ( )之后,就可以进一步实现AC_BM算法了。
i ) 首先要找到当前的三个函数与AC_BM中所要实现的函数之间的异同。
1.AC_BM中的g'( ),output'( )与当前的g( ),output( )是一至的。因为它们都是从关键字树的根开始匹配。
2.AC_BM中的f '( )与目前的f( )是不同的,下面来进行分析:
由于当前算法的匹配是从待匹配串的左侧开始进行,遇到失配的情况时,树要向右移动,而AC_BM算法是从待匹配串的右侧开始进行,遇到的情况时,树要向左移动,因此在失配时,它们的移动是相反的,如:
a) f(5) = 2, f '(2) = 5;
f(7) = 3, f '(3) = 7;
b) 当f(a)=f(b) = c时,f '(c) 的值为a,b中depth值较小的那一个。如f(7)=f(9) = 3,则f '(3) = 7(状态7的depth 值为3,状态9的depth值为4)。
c) 对于g(0,a) = 0的a,存在有f '(a)的值:f ‘(a) 的值为树中(g(x,a)=s)!=fail时depth最小值的状态值s(若有几个depth相等的状态,则f '(a)的值可以为其中的任意一个,对匹配无影响)。
ii ) 程序中实现AC_BM算法的过程如下:
1. 用一个二维数组goto_state[ ][ ]进行g(s,a)的转换:goto_state[s][a] = s'。表示在状态s中输入字符a,则转换到状态s'。若goto_state[s][a] = FAIL,则表示在状态s中不接受字符a的输入(失配)。
2. 用一个一维数组fail[]表示f ( )。fail[s]=s ',说明在状态s中,发生失配时,要转移到哪个状态(注意这个fail[]表示的不是AC_BM的转移状态,而是前面的算法3所构造的f ( )。)
3. 用一个结构数组:struct ac_bm_node nodes[ ]来表示 AC_BM树中的每一个结点:nodes[s]表示状态为S的结点:
struct ac_bm_node {
INT_16 m_id; //表示此结点的状态号
INT_16 m_depth; //表示此结点的depth值
INT_16 m_move[MAX_SYMBOL+1]; //表示在此结点中遇到字符后要进行的移动
INT_16 m_output_id; // >= : 发生匹配的关键字序号。
// <0 : 此节点无匹配发生
};
用下面的结构来保存关键字:
structac_bm_keyword {
INT_16 m_id; //关键字的序号
INT_16 m_len; //关键字的长度。
INT_16 m_type; //0:丢弃,1:阻断,2:报警
INT_16 m_flag; // 0: 此关键字存在于ac_bm 树中,但将会被删除
// 1: 正常的关键字
// 2: 不存在于当前树中,但将会被添加
unsigned char* m_word; //指向关键字内容的指针
};
用下列结构来保存整个树的信息:
struct ac_bm_sub_tree {
struct ac_bm_node **m_node; //指向一个指针数组的首位置,
//数组中存放的是每个ac_bm_node的位置
struct k_word **m_kword; //指向一个指针数组的首位置,
//数组中存放的是每个ac_bm_keyword的位置
INT_16 m_min_len; //关键字的最短长度
INT_16 m_max_len; //关键字的最大长度
INT_16 m_node_num; //结点的数量(不包括0状态的数量)
};
4. 首先用函数mk_goto( )来生成数组goto_state[ ][ ]。(算法为前面描述的算法2)
5. 再用函数mk_fail( )生成数组fail[ ]。(算法为前面描述的算法3)
6. 最后利用函数mk_move( )来生成每一个结点中的数组m_move[ ],算法如下:
a) 定义数组bad_char[]:
若存在goto_state[s][a] = s',则bad_char[a] = d 表示所有s'中depth的最小值。若不存在goto_state[s][a] = s',则bad_char[a] 的值为struct ac_bm_tree中保存的m_max_len(即所有状态s中的depth的最大值)。
b) 根据fail[]设置一部分错误匹配时m_move[]的值:
if( nodes
.m_output_id >= 0 || goto_state[j] != FAIL) {
if(nodes[fail].m_move[j] == 0) {
nodes[fail].m_move[j] = MAX(0-min_len,
nodes[fail].m_depth-nodes.m_depth);
}else{
nodes[fail].m_move[j]= MAX(nodes[fail].m_move[j],
nodes[fail].m_depth-nodes.m_depth);
}
}
c) 根据goto_state[][]设置正确匹配时m_move[ ]的值:
if(goto_state[j] != FAIL) {
nodes.m_move[j] = goto_state[j];
}
d) 设置状态0时失配的m_move[]值:
if(nodes[0].m_move == 0 ) {
nodes[0].m_move = 0 - MIN(min_len, bad_char-1);
}
e) 设置其它的,前面没有设置过的m_move[]值(当前比较过的内容没有出现在树中,因此树向前移动,距离为所有关键字中的最小值):
if(nodes.m_move[j] == 0 ) {
nodes.m_move[j] = 0 - min_len;
}
iii ) 模式匹配的相关操作
1.用函数enter_tree(struct ac_bm_keyword *key_word,
INT_16 **goto_state,
struct ac_bm_sub_tree *sub_tree)
来将关键字keyword来更改goto_state[][]的状态值。将相应的修改sub_tree中的变量。
2.通过循环调用enter_tree()函数来生成goto[][]状态表。
3.通过调用mk_fail()函数来通过goto_state[][]状态表生成fail[]状态表。
4.通过调用int mk_move(INT_16 *fail,
INT_16** goto_state,
struct ac_bm_sub_tree *sub_tree)
来根据fail[]和goto_state[][]建立sub_tree中的m_move状态表。
(以上是基本的生成树的操作)
5.init_acbm_tree()是进行初始化ac_bm_tree的工作:为ac_bm_sub_tree分配内存,将各个变量置为0。
6.free_acbm_node():释放ac_bm_node结构的内存,但并不释放ac_bm_sub_tree的内存和ac_bm_keyword的内存。
7.free_acbm_tree():调用free_acbm_node()释放node的内存,再释放ac_bm_keywrod和ac_bm_sub_tree的内存。
8.add_keyword_async( ):在keyword链表中加入一个关键字,但是并不更新ac_bm树,不影响当前的匹配操作。
9.add_keyword():在keyword链表中加入一个关键字,再更新ac_bm树,使以后的匹配按照新的树进行。
10. remove_keyword_async( ) 在keyword链表中删除一个关键字,但是并不更新ac_bm树,不影响当前的匹配操作。
11. remove_keyword( ):在keyword链表中删除一个关键字,再更新ac_bm树,使以后的匹配按照新的树进行。
12. sync_tree( ):按照当前的关键字列表来更新ac_bm树。
13. query_keyword( ):在关键字链表中查询一个关键字。
14. match_keyword( ):用ac_bm树在一段buffer中进行模式匹配。
以上ac_bm 的实现代码都在filter/ac_bm目录下
3.ac_bm 在内容过滤中的应用
i ) 设计一个结构:
struct ac_bm_tree{
struct ac_bm_sub_tree* m_tree[CHAR_SET_NUM]; // ac_bm树的指针数组
INT_16 m_kword_num; // 树中的关键字的数量
ACBM_FLAG m_charset_flag; // 标志着m_tree中保存有哪些树
pthread_rwlock_t m_rwptr; // 读写锁
};
m_tree: 在内容过滤中,需要对GB2312,BIG5,UNICODE 这三个字符集中的内容进行过滤,所以,要生成三个ac_bm树(每个字符集中一个)。m_tree这个数组中就依次保存了三个树的指针。
m_keyword_num: 三个树中虽然不一样,但是每个树中的关键字数量是一样的,这个值就保存在m_keyword_num中。
m_charset_flag: 这是一个标志量,表示了在m_tree中都有哪些树。因为有时并不需要同时拥有三个字符集中的树,为了节省内存开销及程序执行时间,就不会生成不必要的树。第0位为1:包含有GB2312字符集的树;第1位为1:包含有BIG5字符集的树;第2位为1:包含有UNICODE字符集的树。
m_rwptr:这是一个读写锁。它不同于以往的线程锁,其作用是:
1) 只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读。
2) 仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于读。
(以上实现过程都在filter/mail目录下)
ii ) 以上的函数在smtp内容过滤中的调用方法。
1.在文件filter_smtp.c中,函数smtp_init()里调用load_smtp_acbm_tree( )读取规则文件,并加载ac_bm树的内容
2.在read_from_client( )中将3个全局变量的地址保存在结构smtp_info中。
3.在parse_mail_data.c 中修改原来的匹配方式,用ac_bm方法来进行"主题"、"正文"和"附件"的匹配。
2002-12-17
2.AC_BM的实现(以下为我的设计和总结)
有了g( ), f( ), output ( )之后,就可以进一步实现AC_BM算法了。
i ) 首先要找到当前的三个函数与AC_BM中所要实现的函数之间的异同。
1.AC_BM中的g'( ),output'( )与当前的g( ),output( )是一至的。因为它们都是从关键字树的根开始匹配。
2.AC_BM中的f '( )与目前的f( )是不同的,下面来进行分析:
由于当前算法的匹配是从待匹配串的左侧开始进行,遇到失配的情况时,树要向右移动,而AC_BM算法是从待匹配串的右侧开始进行,遇到的情况时,树要向左移动,因此在失配时,它们的移动是相反的,如:
a) f(5) = 2, f '(2) = 5;
f(7) = 3, f '(3) = 7;
b) 当f(a)=f(b) = c时,f '(c) 的值为a,b中depth值较小的那一个。如f(7)=f(9) = 3,则f '(3) = 7(状态7的depth 值为3,状态9的depth值为4)。
c) 对于g(0,a) = 0的a,存在有f '(a)的值:f ‘(a) 的值为树中(g(x,a)=s)!=fail时depth最小值的状态值s(若有几个depth相等的状态,则f '(a)的值可以为其中的任意一个,对匹配无影响)。
ii ) 程序中实现AC_BM算法的过程如下:
1. 用一个二维数组goto_state[ ][ ]进行g(s,a)的转换:goto_state[s][a] = s'。表示在状态s中输入字符a,则转换到状态s'。若goto_state[s][a] = FAIL,则表示在状态s中不接受字符a的输入(失配)。
2. 用一个一维数组fail[]表示f ( )。fail[s]=s ',说明在状态s中,发生失配时,要转移到哪个状态(注意这个fail[]表示的不是AC_BM的转移状态,而是前面的算法3所构造的f ( )。)
3. 用一个结构数组:struct ac_bm_node nodes[ ]来表示 AC_BM树中的每一个结点:nodes[s]表示状态为S的结点:
struct ac_bm_node {
INT_16 m_id; //表示此结点的状态号
INT_16 m_depth; //表示此结点的depth值
INT_16 m_move[MAX_SYMBOL+1]; //表示在此结点中遇到字符后要进行的移动
INT_16 m_output_id; // >= : 发生匹配的关键字序号。
// <0 : 此节点无匹配发生
};
用下面的结构来保存关键字:
structac_bm_keyword {
INT_16 m_id; //关键字的序号
INT_16 m_len; //关键字的长度。
INT_16 m_type; //0:丢弃,1:阻断,2:报警
INT_16 m_flag; // 0: 此关键字存在于ac_bm 树中,但将会被删除
// 1: 正常的关键字
// 2: 不存在于当前树中,但将会被添加
unsigned char* m_word; //指向关键字内容的指针
};
用下列结构来保存整个树的信息:
struct ac_bm_sub_tree {
struct ac_bm_node **m_node; //指向一个指针数组的首位置,
//数组中存放的是每个ac_bm_node的位置
struct k_word **m_kword; //指向一个指针数组的首位置,
//数组中存放的是每个ac_bm_keyword的位置
INT_16 m_min_len; //关键字的最短长度
INT_16 m_max_len; //关键字的最大长度
INT_16 m_node_num; //结点的数量(不包括0状态的数量)
};
4. 首先用函数mk_goto( )来生成数组goto_state[ ][ ]。(算法为前面描述的算法2)
5. 再用函数mk_fail( )生成数组fail[ ]。(算法为前面描述的算法3)
6. 最后利用函数mk_move( )来生成每一个结点中的数组m_move[ ],算法如下:
a) 定义数组bad_char[]:
若存在goto_state[s][a] = s',则bad_char[a] = d 表示所有s'中depth的最小值。若不存在goto_state[s][a] = s',则bad_char[a] 的值为struct ac_bm_tree中保存的m_max_len(即所有状态s中的depth的最大值)。
b) 根据fail[]设置一部分错误匹配时m_move[]的值:
if( nodes.m_output_id >= 0 || goto_state[j] != FAIL) {
if(nodes[fail].m_move[j] == 0) {
nodes[fail].m_move[j] = MAX(0-min_len,
nodes[fail].m_depth-nodes.m_depth);
}else{
nodes[fail].m_move[j]= MAX(nodes[fail].m_move[j],
nodes[fail].m_depth-nodes.m_depth);
}
}
c) 根据goto_state[][]设置正确匹配时m_move[ ]的值:
if(goto_state[j] != FAIL) {
nodes.m_move[j] = goto_state[j];
}
d) 设置状态0时失配的m_move[]值:
if(nodes[0].m_move == 0 ) {
nodes[0].m_move = 0 - MIN(min_len, bad_char-1);
}
e) 设置其它的,前面没有设置过的m_move[]值(当前比较过的内容没有出现在树中,因此树向前移动,距离为所有关键字中的最小值):
if(nodes.m_move[j] == 0 ) {
nodes.m_move[j] = 0 - min_len;
}
iii ) 模式匹配的相关操作
1.用函数enter_tree(struct ac_bm_keyword *key_word,
INT_16 **goto_state,
struct ac_bm_sub_tree *sub_tree)
来将关键字keyword来更改goto_state[][]的状态值。将相应的修改sub_tree中的变量。
2.通过循环调用enter_tree()函数来生成goto[][]状态表。
3.通过调用mk_fail()函数来通过goto_state[][]状态表生成fail[]状态表。
4.通过调用int mk_move(INT_16 *fail,
INT_16** goto_state,
struct ac_bm_sub_tree *sub_tree)
来根据fail[]和goto_state[][]建立sub_tree中的m_move状态表。
(以上是基本的生成树的操作)
5.init_acbm_tree()是进行初始化ac_bm_tree的工作:为ac_bm_sub_tree分配内存,将各个变量置为0。
6.free_acbm_node():释放ac_bm_node结构的内存,但并不释放ac_bm_sub_tree的内存和ac_bm_keyword的内存。
7.free_acbm_tree():调用free_acbm_node()释放node的内存,再释放ac_bm_keywrod和ac_bm_sub_tree的内存。
8.add_keyword_async( ):在keyword链表中加入一个关键字,但是并不更新ac_bm树,不影响当前的匹配操作。
9.add_keyword():在keyword链表中加入一个关键字,再更新ac_bm树,使以后的匹配按照新的树进行。
10. remove_keyword_async( ) 在keyword链表中删除一个关键字,但是并不更新ac_bm树,不影响当前的匹配操作。
11. remove_keyword( ):在keyword链表中删除一个关键字,再更新ac_bm树,使以后的匹配按照新的树进行。
12. sync_tree( ):按照当前的关键字列表来更新ac_bm树。
13. query_keyword( ):在关键字链表中查询一个关键字。
14. match_keyword( ):用ac_bm树在一段buffer中进行模式匹配。
以上ac_bm 的实现代码都在filter/ac_bm目录下
3.ac_bm 在内容过滤中的应用
i ) 设计一个结构:
struct ac_bm_tree{
struct ac_bm_sub_tree* m_tree[CHAR_SET_NUM]; // ac_bm树的指针数组
INT_16 m_kword_num; // 树中的关键字的数量
ACBM_FLAG m_charset_flag; // 标志着m_tree中保存有哪些树
pthread_rwlock_t m_rwptr; // 读写锁
};
m_tree: 在内容过滤中,需要对GB2312,BIG5,UNICODE 这三个字符集中的内容进行过滤,所以,要生成三个ac_bm树(每个字符集中一个)。m_tree这个数组中就依次保存了三个树的指针。
m_keyword_num: 三个树中虽然不一样,但是每个树中的关键字数量是一样的,这个值就保存在m_keyword_num中。
m_charset_flag: 这是一个标志量,表示了在m_tree中都有哪些树。因为有时并不需要同时拥有三个字符集中的树,为了节省内存开销及程序执行时间,就不会生成不必要的树。第0位为1:包含有GB2312字符集的树;第1位为1:包含有BIG5字符集的树;第2位为1:包含有UNICODE字符集的树。
m_rwptr:这是一个读写锁。它不同于以往的线程锁,其作用是:
1) 只要没有线程持有某个给定的读写锁用于写,那么任意数目的线程可以持有该读写锁用于读。
2) 仅当没有线程持有某个给定的读写锁用于读或用于写时,才能分配该读写锁用于读。
(以上实现过程都在filter/mail目录下)
ii ) 以上的函数在smtp内容过滤中的调用方法。
1.在文件filter_smtp.c中,函数smtp_init()里调用load_smtp_acbm_tree( )读取规则文件,并加载ac_bm树的内容
2.在read_from_client( )中将3个全局变量的地址保存在结构smtp_info中。
3.在parse_mail_data.c 中修改原来的匹配方式,用ac_bm方法来进行"主题"、"正文"和"附件"的匹配。