Linux使用ragel进行文本快速解析
1、前言
在项目中我们经常涉及一些文本解析处理的场景,大部分场景是进行简单解析且数据量不大,但本文要讨论的是某些特定场景下,需要高性能解析大量文本的处理,如百万级每秒的日志解析。翻看Intel 的 hyperscan 依赖,发现有一款解析引擎Ragel,特性非常棒,可以拉出来进行实践。
当我们对文本(协议)进行解析的时候,有许多工具、语言可以进行选择。需求考虑一种实用、高效的可编程语言,但又要避免乏味、语义晦涩的写法。在这些工具像Lex、Re2C,或者sed、awk、perl脚本中,我们会发现有一个共同点:他们都是使用正则表达式匹配引擎,结合特定的程序处理逻辑来实现的。
经过大家的经验累积,我们得出一种结论:正则表达式可以清晰描述处理流程,而状态机可以可靠、快速地执行该流程。所以引入Ragel的编程哲学:开发者使用易理解的正则表达式来设计流程,然后生成高效的状态机代码给机器运行。
2、相关知识
以下通过 Ragel 官方手册[^2] 我们进行一部分的翻译理解,来逐步掌握这个工具。
Ragel 是一款代码生成器,他对常规的语言进行编译,生成有限状态机(FSM)代码。Ragel支持生成C、C++、ASM。Ragel 的状态机不仅可以使用正则表达式识别字节序列,而且可以在匹配过程中执行相关操作。Ragel生成相关的嵌入代码将使用内联操作,并不会破坏原语言的语法结构。
Ragel的核心语言包括正则表达式运算符、动作(action)嵌入运算符。用户的正则表达式将被编译为确定的状态机,并且执行指定的动作。编程的核心在于理解正则表达式和确定性有限状态机的关系。
2.1、简介
2.1.1 Ragel 优势
- 实现健壮的协议;
- 解析数据格式;
- 编程语言的语法分析;
- 验证用户输入;
2.1.2 Ragel 特性
- 结合常规代码、状态图表、扫描器就能构建FSM;
- 支持在FSM任意位置插入执行的动作;
- 使用防御性操作控制流程不确定性;
- 使用Hopcroft算法实现最小自动机;
- 支持Graphviz软件对FSM可视化;
- 使用单字节、双字节、字长的字母表;
- 在非依赖情况下生成C、C++、ASM(GNU, x86_64, System V ABI) 代码;
- 可选使用表、控制流去驱动状态机;
2.2 状态机(mechine)概念
Ragel 对于字符串的解析,是用一种结合正则表达式的机器语言来描述,可表现为内嵌的状态机语言规范。这种机器语言通过扫描输入的字符串,匹配状态机的特定状态进行处理,当扫描结束的时候,处理也随之完成。这种有限状态机语言可以由数行代码完成,使用标识符 %%{ 开始、使用 }%% 结束,也可以使用 %% 开始的一行进行实现。
下面看一个简单例子,该例子判断输入字符串中是否含有’foo’或’bar’字符:
#include <string.h>
#include <stdio.h>
%%{
machine foo;
main :=
( ’foo’ | ’bar’ )
0 @{
res = 1; };
}%%
%% write data;
int main( int argc, char **argv )
{
int cs, res = 0;
if ( argc > 1 ) {
char *p = argv[1];
char *pe = p + strlen(p) + 1;
%% write init;
%% write exec;
}
printf("result = %i\n", res );
return 0;
}
Ragel语法的文件为rl后缀命名,内部expression支持正则表达式的语法:
命名:machine fsm_name;
定义: = ;
实例化: = ;
引用:include FsmName “inputfile.rl”;
引用:import “inputfile.h”;
2.3 动作(action)的概念
Ragel 允许自定义动作嵌入到正则表达式对应的状态机动作之中,随着FSM的状态迁移,自定义动作随之执行。与正则表达式一样,action 动作操作符是可以任意组合的。由于动作可以嵌入组成,用户可以完全自由地定义动作。
action ActionName {
/* Code an action here. */
count += 1;
}
动作的触发是在配合 FSM 状态迁移进行,按照动作大类区分有:
>开始状态
< 除了开始状态的其他状态
$ 任意状态下
% 结束状态
@ 除了结束状态的任意状态
<> 中间状态(除了开始状态和结束状态)
按动作小类区分有:
~ (to)迁移其他状态时的动作
* (from) 从其他状态迁移过来时的动作
/ (eof)EOF时的动作
! (err)发生错误时的动作
^ (lerr)本地错误时的动作
下面列举一个例子来表示如何处理错误,在解析失败时时进行信息提示,并跳转到错误处理,当错误处理完成后