Know C, C++ and shell programming, math and sport is my favour too. VIM Fans.
分类: 服务器与存储
2014-04-21 21:32:40
From: http://blog.csdn.net/linxinze520/article/details/7594603#t10
(1)支持多个并发用户访问
(2)使用配置文件配置根目录等选项。
(3)支持CGI;
(4)支持简单的脚本;
(5)支持日志系统(log_zhttpd_*文件)。
(1)以开始,以?>结尾
(2)支持整型、字符串型数据
(3)支持整型变量存储,变量名可为多个字符
(4)支持整型的+、-、*、/、%、()、=运算法则
(5)支持print打印整型、字符串型
(6)支持打印变量值
(7)支持while if else控制结构,支持控制结构的嵌套
(8)支持>、 >=、<、 <= 、!=、 ==六种比较运算
(9)支持 &&、||复合比较运算
(10)支持对空格和TAB的忽略处理
(11)支持{}多重组合
(12)支持编译错误的行数提示
#server
HostName=zhttpd 服务器名字
HostPort=8080 服务器端口号
ChildsNum=2 开启子进程个数
LISTEN_NUM=20 监听个数
#cgi envp
CGI_INTERFACE=CGI/1.2 cgi协议版本
HTTP_PROTOCOL=HTTP/1.1 http协议版本
#resource file path
ResourceFilePath=www 存放html、cgi、php文件路径
a=0;
print("循环打印0到20的偶数:
");while (a<20)
{
if ((a%2) == 0)
{
print(" ");
print a;
print("
");}
a=a+1;
}
?>
[root@localhost example]# uname -a
Linux localhost.localdomain 2.6.18-53.el5xen #1 SMP Wed Oct 10 17:06:12 EDT 2007 i686 i686 i386 GNU/Linux
[root@localhost example]# gcc -v
使用内建 specs。
目标:i386-redhat-linux
配置为:../configure --prefix=/usr --mandir=/usr/share/man --infodir=/usr/share/info --enable-shared --enable-threads=posix --enable-checking=release --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-libgcj-multifile --enable-languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-gcj-1.4.2.0/jre --with-cpu=generic --host=i386-redhat-linux
线程模型:posix
gcc版本 4.1.2 20070626 (Red Hat 4.1.2-14)
在zhttpd.v.1.0中,进入./src文件夹,执行:
$ make clean
$ make
执行完成之后,会自动复制zhttpd运行文件到./example,进入./example,执行:
$ ./zhttpd
然后在浏览器输入,,将会输出百度的首页(静态网页测试);
在浏览器输入,测试cgi GET表单;
在浏览器输入,测试cgi POST表单;
在浏览器输入,测试脚本,将会输出一个图片和0到20的偶数。
如图1所示,服务器在接收到客户端的http报文请求之后,会分三种情况进行处理:
(1)如果客户端请求的是静态网页(包含图片等文件),就直接调用发送文件接口发送该请求文件;
(2)如果客户端请求的是.php格式的脚本文件,即先调用脚本引擎对脚本文件内容进行解析出html内容与开始的脚本文件,把解析得到的html内容和经过脚本引擎解释得到的结果写到一个临时文件,最后调用发送文件系统进行发送。
(3)如果是cgi程序,即建立服务器与cgi程序之间的好环境变量和通信管道。
图1服务器总框图
在程序设计中,在解析客户端请求报文的时候,会根据请求报文中的请求文件类型和文件的格式,解析出五种状态,如下面的枚举:
typedef enum {Z_STATIC=0, Z_GET, Z_POST, Z_INVALID, Z_SCRIPT} check_req;
Z_STATIC表示静态网页,Z_GET表示cgi GET表单方式,Z_POST表示cgi POST表单方式,Z_SCRIPT表示脚本文件,Z_INVALID表示错误类型,这五种类型的接口程序如图2所示:
图 2程序接口说明
图3多并发模型
参照lighttpd框架,zhttpd服务也使用了多进程+多路复用(如select,epoll)的网络模型,如图3所示。在父进程与子进程之间的关系是watcher和worker。下面是watcher与worker之间的关系:
父进程负责创建子进程并且监控是否有子进程退出,如果有,那么再次创建出子进程;而子进程是worker,是具体执行服务器操作的工作者,在被创建完毕之后退出循环,去做处理数据的事情.而如果父进程接收到退出信号退出这个循环,那么退出之前先让进程组内所有子进程退出要退出了,作一些清理的工作。
在处理cgi GET表单时,服务器发送给cgi程序是通过环境变量发送的,并且GET的表单内容是存放在环境变量QUERY_STRING中,因此在服务器与cgi程序通信之前,会先把GET表单进行解析,并建立环境变量。
同时,由于cgi程序是通过标准输出向服务器输出数据,因此在通信之前,还需要建立一条服务器与cgi程序之间的通信管道,重定向cgi程序的标准输出到服务器的管道读端口。如图 4是服务器与cgi程序之前的通信图示。
图 4 GET表单
在处理cgi POST表单的时候,服务器通过环境变量发送POST表单的长度,服务器向cgi程序输入数据除了采用环境变量之外,还把POST的表单内容通过写管道输入到cgi的标准输入,同时cgi程序输出数据时是通过标准输出发送到服务器。
因此在服务器向cgi程序传送数据的时候,还需要建立两个管道,并分别把cgi程序的标准输入与标准输出进行重定向。
即,在第一条管道中,服务器往管道写端写数据,管道的另一端读端重定向到cgi程序的标准输入,cgi程序通过标准输入读数据。
在第二条管道中,把管道的写端重定向到cgi程序的标准输出,cgi程序通过标准输出向服务器发送数据,服务器通过管道的读端读取cgi发送过来的数据。
图5是POST表单的处理图示。
图 5 POST表单
/*!struct
**************************************************************
结构体名:Str_var
功 能:字符串存放结构
参数说明:
char *str: 字符串内容
--------------------------------------------------------------
其他说明:
MAX_STR_VAR_NUM:存放字符串数组最大长度
变量说明:
Str_var str_var[MAX_STR_VAR_NUM]:存放字符串数组
int str_var_cur_index:当前字符串数组中总元素个数
int str_var_index:当前元素的索引
函数说明:
int add_string(char *str):
功能:先检查字符串是否存在该字符串,如果是,返回该字符串在字符
串数组中的地址,如果不存在,在字符串数组中建立一个字符串元
素,返回改元素在数组的地址。
参数:char *str:字符串
返回值:内容为str的变量的索引(即在数组中的地址)
void str_var_init(void):字符串数组初始化
void free_string(void):free动态分配的内存
**************************************************************/
当在flex识别到一个字符串的时候,会调用add_string()在字符串存放结构内存放此字符串,然后返回改字符串在字符串存放结构中的索引(或者称为地址)。
/*!struct
**************************************************************
结构体名:mVar
功 能:存放变量结构
参数说明:
int i_val:该整形变量的值
int str_addr:存放字符串变量的地址,在本引擎中没有使用
char *name; 变量的名字
--------------------------------------------------------------
其他说明:
MAX_M_VAR_NUM:变量数组最大长度
变量说明:
mVar m_var[MAX_M_VAR_NUM]:存放变量结构的数组
int m_var_index:当前变量数组中总元素个数
int m_var_cur_index:当前元素的索引
函数说明:
int add_var(char *name):
功能:先检查变量名是否存在该变量,如果是,返回该变量在变量数组中的地址
如果不存在,在变量数组中建立一个变量元素,返回改元素在数组的地址。
参数:char *name:变量的名字
返回值:名为name的变量的索引(即在数组中的地址)
void m_var_init(void):整形变量数组初始化
void free_var(void):free动态分配的内存
void show_m_var(void):打印整形变量数组
**************************************************************/
当flex识别到某字符或字符串是一个整型变量或者字符串变量的时候,会调用add_var()向存放变量结构存放此变量,如果是整型变量,那么i_val可以直接存放该整型值,如果是字符串变量,那么str_addr存放着该字符串在存放字符串结构数组中的索引。然后返回该变量在存放变量数组中的索引。
/*!struct
**************************************************************
结构体名:Execute_cmd
功 能:指令操作结构体
参数说明:
int type:指令操作将要执行的操作,对应于:Z_ACT_*
int (*exe_func)(int val):指令操作对应的函数指针,对应于:avt_*函数
char *name:该操作的名称
--------------------------------------------------------------
变量说明:
static Execute_cmd exe_cmd[]:定义好的指令操作
函数说明:
avt_*等函数,根据该函数名就可以知道该函数功能,avt_*系列函数都是通过
操作堆栈模拟运行。
**************************************************************/
/*!struct
**************************************************************
结构体名:Command
功 能:堆栈执行指令存放结构
参数说明:
int act_type:执行指令将要执行的操作,对应于:Z_ACT_*
int cmd_type:执行指令的类型,对应于:CMD_*_TYPE
int i_val:当为整形数值时,存放整形数值的值
int i_var:当为整形变量时,存放整形变量在整形变量存放结构中的地址
int str_var:当为字符串常量时,存放字符串常量在存放字符串结构数组中的地址
int ctrl:当为操作指令时,标记是否需要执行指令操作,对应于:CTRL_TRUE、CTRL_FALSE
--------------------------------------------------------------
其他说明:
MAX_CMD_LEN:执行指令数组最大长度
CMD_NULL_TYPE:错误标记
CMD_INT_TYPE:为值时的标记
CMD_STR_TYPE:字符串变量标记
CMD_VAR_TYPE:整形变量标记
CMD_CHAR_TYPE:操作符标记
变量说明:
Command cmd[MAX_CMD_LEN]:执行指令存放的数组
int cmd_cur_index:当前指令在执行指令数组中的索引
函数说明:
void cmd_init(void):
功能:执行指令初始化
参数: void
返回值:void
void show_cmd(int i):
功能:打印单个执行指令
参数: int i:该指令的索引
返回值:void
void print_cmd(void):
功能:打印0-cmd_cur_index个指令
参数: void
返回值:void
void add_command(int act_type, int var_type, YYSTYPE *val):
功能:给改指令向执行指令数组加入一个指令
参数: int act_type:执行指令将要执行的操作,对应于:Z_ACT_*
int var_type:执行指令的类型,对应于:CMD_*_TYPE
YYSTYPE *val:此参数由flex传递已经解析出来的联合体
返回值:void
**************************************************************/
a、flex进行词法分析之后,解析出来的token会通过yylval传递给yacc。在传递值之前,flex会调用相应函数进行操作:
如果是整形数据值,即直接把该整形数据传送给yacc;
如果是整形变量,即首先在整形变量数组插入该变量,再把该变量在整形变量中的地址传送给yacc;
如果是字符串变量,首先在字符串变量数组中插入该变量,再把该字符串在字符串变量中的地址传送给yacc。
b、然后yacc通过语法分析,分别针对yystype中三种情况调用add_command建立执行指令:
如果是整形数据值,即i_val=该值;如果是整形变量,即i_var=该变量在整形变量中的地址;
如果是字符串变量,即str_var=该字符串在字符串变量中的地址。
下面举例进行说明,如程序式:a=2;
对上面的程序式,将产生3个Command元素:
对于“a”,因a是变量名,因此会产生一个加入变量的操作,此时,在mVar结构中存放一个name=”a”的变量,索引为i,因此Command结构中i_var=i,因为a是变量,因此会有个向栈压入变量的动作,因此,Command结构中act_type= Z_ACT_PUSHVAR,cmd_type= CMD_VAR_TYPE,如“a”的Command结构为:
cmd[j].act_type= Z_ACT_PUSHVAR
cmd[j].cmd_type= CMD_VAR_TYPE
cmd[j].i_val=0
cmd[j].i_var=i
cmd[j].str_var=-1
cmd[j].ctrl=0
依次类推,
对于“2”的Command结构为:
cmd[j].act_type= Z_ACT_PUSHVALUE
cmd[j].cmd_type= CMD_INT_TYPE
cmd[j].i_val=2
cmd[j].i_var=0
cmd[j].str_var=-1
cmd[j].ctrl=-1
对于“=”的Command结构为:
cmd[j].act_type= Z_ACT_ASSIGN
cmd[j].cmd_type= CMD_CHAR_TYPE
cmd[j].i_val=0
cmd[j].i_var=0
cmd[j].str_var=-1
cmd[j].ctrl=0
/*!struct
**************************************************************
结构体名:st_ctrl
功 能:存放堆栈控制指令
参数说明:
int act_type:该指令的执行操作,对应于Command中的act_type
int control:该指令是否进行操作,对应于Command中的ctrl
int addr:存放Command指令的地址
--------------------------------------------------------------
其他说明:
MAX_CTRL_NUM:控制指令数组最大长度
变量说明:
st_ctrl ctrl[MAX_CTRL_NUM]:控制指令数组
int st_ctrl_cur_index:控制指令数组当前元素索引
函数说明:
void ctrl_init(void):初始化控制指令数组
void show_ctrl(void):打印控制指令数组
void add_ctrl(int act_type, int control, int var_addr):
在控制指令数组中加入一个控制指令
**************************************************************/
控制指令是在进行if else、while或者进行比较运算或者复合比较运算的时候产生的,用来进行判断该Command是否执行act_*操作。
图 6脚本引擎运行
由于堆栈和堆栈的操作都很熟悉,就不进行介绍,但是在脚本引擎中,用到堆栈虚拟机,堆栈虚拟机用到两种堆栈结构:
ZStack value_stack:数据栈
ZStack ctrl_stack:操作栈
数据栈存放是的Command的结构在Command数组中的索引,操作栈存放的是st_ctrl结构在st_ctrl结构数组的索引。堆栈虚拟机就是通过操作这两个栈进行运算。
如图6所示,脚本引擎先通过FLEX进行词法分析,此时会产生变量、数值和操作符,再通过YACC产生Command结构数组,然后通过循环执行Command结构数组中每一个元素,此时会用到数据栈和操作栈。
对于顺序执行的程序语句来说,就是简单地对数据栈进行压栈和出栈,可以具体看下程序就能明白其意思。
If-else设计思路(请参考stack.c中avt_if()/avt_else()/avt_if_end()三个函数):
当执行到if指令时,先到操作指令st_ctrl结构数组中加入个if操作指令,然后判断if是否与while或if else嵌套,如果不是,即从操作栈ctrl_stack去栈顶值的addr为-1(执行free_stack或pop_stack时把value初始化为-1),如果为嵌套,即根据前面的控制指令中control值相&,意思为:如果前面的操作control为0,即前面操作不生效,&的结果为0,即嵌套内此if肯定不生效。
例子:
if stmr1
{
if stmr2
}
当执行到“if stmr2”时操作栈内应存在“if stmr1”操作指令:
如果stmr1为false,即不会执行“if stmr2”,即stmr一定为false;
如果stmr1为true,“stmr2”不知道是否生效,此处需要看&的结果。
当执行到else的时候,说明else前面的if肯定已经执行过(无论if的control为0或1都已经执行),因此这里出栈是对if出栈。因为if与else是对立的,因此:如果else不嵌套,即addr2==-1,else的control等于if的control的非;如果else是嵌套的,且前面嵌套的control为1,那嵌套内的if和else都可能执行,因为if与else是对立,因此else的control为if的control的非;如果嵌套的control为0,那嵌套内的if和else的control肯定都为0,即已经出栈的if控制指令与else的控制指令是一样的。
例子:
if stmr1
{
if stmr2
else stmr3
}
如果stmr1为0,那stmr2与stmr3一定为0;如果stmr1为1,那stmr2=!stmr3.
无论是if还是else都push_stack,因此if-else执行完成之后,一定要pop_stack。
While的设计思路(请参考stack.c中act_begin_while()/act_while()/act_end_while()):
注意此处传入的index和add_ctrl中的index,while的执行过程可以拆分如下:
begin_while
while (exempr) act_while
{
}
act_end_while
exempr为前面的>、==之类的条件判断。
不嵌套情况下:
1、act_begin_while初始化当前指令索引index到控制指令,并push_stack,为stack[1],stack[1]带有当前执行指令的地址(index)
2、exempr执行后会产生一个带有control的控制指令,并push_stack,为stack[2]
3、act_while先pop stack[2],再pop stack[1],两个stack.control &后赋值给stack[1]再push_stack[1],为新的stack[1](带有index)
4、act_end_while先pop stack[1],判断stack[1].control,如果为0,就把执行act_end_while的执行指令的i返回(跳出循环),
如果为1,把index返回(继续循环)
嵌套情况与不嵌套情况的差别只为前面嵌套的control值,如果为0,那么上面第1步和第三步的&后的值都为0,即不会执行,如果为1,即跟不嵌套情况一样。
1、由于zhttpd的脚本引擎是使用数组作为堆栈,其他所有结构都是静态数组,这就会造成脚本引擎将不能进行太复杂的操作。
2、在从php文件分离出脚本语言时,只采用了BUFSIZ(8k)的缓存存储脚本语言,因此php文件不能超过8k,如果超过,将可能产生错误;在print操作时只使用了512字节进行存储print的数据,如果print的数据超过512字节,将会发生错误。
3、在接收客户端的请求报文的时候,使用了个BUFSIZ大小的缓存进行存储,因此如果请求报文大于BUFSIZ,将会出错;在处理cgi POST表单的时候,POST表单内容是直接截取客户端的请求报文,如果报文大于BUFSIZ,将会出错。
如果后期有时间,可以对第1点将会对脚本引擎的堆栈和存放结构的静态数组改为链表,对第2、3点的限制可以使用动态分配内存实现,出个zhttpd.v.1.1版本。
欢迎大家一起学习.
源代码+文档:
参考资料(有些资料没有记录下来):
cgi+http服务器
http://linux.chinaunix.net/techdoc/develop/2008/06/18/1011838.shtml
http://www.cppblog.com/dyj057/archive/2007/08/24/30759.html?opt=admin
%D3%F3%C4%E0%C4%EA%B8%E2/blog/item/956b3a180d1369a14aedbc96.html
http://blog.csdn.net/lovejavalovejava/article/details/4076023
mashaodong1985/blog/item/7b6104ca1306bdf553664fc5.html
http://blog.sina.com.cn/s/blog_65403f9b0100gpt2.html
简单脚本解析器 linux c
http://www.cppblog.com/vczh/archive/2008/07/19/56585.html
http://kouriba.iteye.com/blog/1425667
语义分析
http://blog.sina.com.cn/s/blog_8b9e73c90100wpvl.html
http://www.cnblogs.com/yanlingyin/archive/2012/04/17/2451717.html
http://www.cnblogs.com/linxr/category/277711.html
lex:
http://www.cppblog.com/woaidongmao/archive/2008/09/19/62293.html
http://blog.csdn.net/dylgsy/article/details/4075984
http://www.cnblogs.com/dc2011/archive/2011/11/21/2256773.html
quankang/blog/item/bf687ced6725bdd1b21cb15c.html
语法树:
http://www.cppblog.com/woaidongmao/archive/2008/09/20/62322.html
http://www.cppblog.com/woaidongmao/archive/2008/09/20/62322.html
yacc