Chinaunix首页 | 论坛 | 博客
  • 博客访问: 150282
  • 博文数量: 43
  • 博客积分: 15
  • 博客等级: 民兵
  • 技术积分: 100
  • 用 户 组: 普通用户
  • 注册时间: 2010-08-04 00:08
个人简介

Know C, C++ and shell programming, math and sport is my favour too. VIM Fans.

文章分类

全部博文(43)

文章存档

2015年(1)

2014年(27)

2013年(15)

我的朋友

分类: 服务器与存储

2014-04-21 21:32:40

From: http://blog.csdn.net/linxinze520/article/details/7594603#t10

一、zhttpd功能说明

1Linux下的http服务器zhttpd,支持功能:

   1)支持多个并发用户访问

   2)使用配置文件配置根目录等选项。

   3)支持CGI

4)支持简单的脚本;

5)支持日志系统(log_zhttpd_*文件)

2、其中脚本支持语法为:

1)以开始,以?>结尾

   2)支持整型、字符串型数据

3)支持整型变量存储,变量名可为多个字符

   4)支持整型的+-*/%()=运算法则

   5)支持print打印整型、字符串型

   6)支持打印变量值

   7)支持while if else控制结构,支持控制结构的嵌套

   8)支持> >=< <= != ==六种比较运算

   9)支持 &&||复合比较运算

   10)支持对空格和TAB的忽略处理

   11)支持{}多重组合

12)支持编译错误的行数提示

二、zhttpd配置参数说明

1zhttpd.conf配置文件说明

#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       存放htmlcgiphp文件路径

2、脚本编程例子

a=0;

print("

循环打印020的偶数:

");

while (a<20)

{

  if ((a%2) == 0)

    {

          print("

");

          print a;

          print("

"); 

    }

    a=a+1;

}

?>

三、zhttpd编译与运行

1、编译环境:linux

[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)

2、编译与运行

zhttpd.v.1.0中,进入./src文件夹,执行:

$ make clean

$ make

执行完成之后,会自动复制zhttpd运行文件到./example,进入./example,执行:

$ ./zhttpd

然后在浏览器输入,,将会输出百度的首页(静态网页测试);

在浏览器输入,测试cgi GET表单;

在浏览器输入,测试cgi POST表单;

在浏览器输入,测试脚本,将会输出一个图片和020的偶数。

四、zhttpd详细设计

如图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程序接口说明

 

 

1、服务器框架设计——支持多并发用户访问

                                                                                                                                          

3多并发模型

参照lighttpd框架,zhttpd服务也使用了多进程+多路复用(select,epoll)的网络模型,如图3所示。在父进程与子进程之间的关系是watcherworker。下面是watcherworker之间的关系:

父进程负责创建子进程并且监控是否有子进程退出,如果有,那么再次创建出子进程;而子进程是worker,是具体执行服务器操作的工作者,在被创建完毕之后退出循环,去做处理数据的事情.而如果父进程接收到退出信号退出这个循环,那么退出之前先让进程组内所有子进程退出要退出了,作一些清理的工作。

 

2、支持CGI GET/POST交互设计

 

在处理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发送过来的数据。

5POST表单的处理图示。

5 POST表单

3、脚本引擎设计

1先进行对字符串存放结构Str_var进行说明:

/*!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()在字符串存放结构内存放此字符串,然后返回改字符串在字符串存放结构中的索引(或者称为地址)。

2对存放变量结构mVar进行说明:

/*!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存放着该字符串在存放字符串结构数组中的索引。然后返回该变量在存放变量数组中的索引。

3对指令操作结构体Execute_cmdr进行说明:

/*!struct

**************************************************************


结构体名:Execute_cmd

   :指令操作结构体

参数说明:

      int type:指令操作将要执行的操作,对应于:Z_ACT_*

      int (*exe_func)(int val):指令操作对应的函数指针,对应于:avt_*函数

      char *name:该操作的名称

--------------------------------------------------------------

变量说明:

      static Execute_cmd exe_cmd[]:定义好的指令操作

 

函数说明:   

      avt_*等函数,根据该函数名就可以知道该函数功能,avt_*系列函数都是通过

      操作堆栈模拟运行。

**************************************************************/

4对堆栈执行指令存放结构Command进行说明:

/*!struct

**************************************************************


结构体名:Command

   :堆栈执行指令存放结构

参数说明:

    int act_type:执行指令将要执行的操作,对应于:Z_ACT_*

    int cmd_type:执行指令的类型,对应于:CMD_*_TYPE

    int i_val:当为整形数值时,存放整形数值的值

    int i_var:当为整形变量时,存放整形变量在整形变量存放结构中的地址

    int str_var:当为字符串常量时,存放字符串常量在存放字符串结构数组中的地址

    int ctrl:当为操作指令时,标记是否需要执行指令操作,对应于:CTRL_TRUECTRL_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

**************************************************************/

   aflex进行词法分析之后,解析出来的token会通过yylval传递给yacc。在传递值之前,flex会调用相应函数进行操作:

如果是整形数据值,即直接把该整形数据传送给yacc

如果是整形变量,即首先在整形变量数组插入该变量,再把该变量在整形变量中的地址传送给yacc

如果是字符串变量,首先在字符串变量数组中插入该变量,再把该字符串在字符串变量中的地址传送给yacc

     b、然后yacc通过语法分析,分别针对yystype中三种情况调用add_command建立执行指令:

如果是整形数据值,即i_val=该值;如果是整形变量,即i_var=该变量在整形变量中的地址;

如果是字符串变量,即str_var=该字符串在字符串变量中的地址。

5对前面几个数据结构进行举例说明

  下面举例进行说明,如程序式:a=2;

对上面的程序式,将产生3Command元素:

对于“a”,因a是变量名,因此会产生一个加入变量的操作,此时,在mVar结构中存放一个name=”a”的变量,索引为i,因此Command结构中i_var=i,因为a是变量,因此会有个向栈压入变量的动作,因此,Command结构中act_type= Z_ACT_PUSHVARcmd_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

6对存放堆栈控制指令st_ctrl进行举例说明

/*!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 elsewhile或者进行比较运算或者复合比较运算的时候产生的,用来进行判断该Command是否执行act_*操作。

6还有个堆栈结构不进行详细说明

6脚本引擎运行

 

由于堆栈和堆栈的操作都很熟悉,就不进行介绍,但是在脚本引擎中,用到堆栈虚拟机,堆栈虚拟机用到两种堆栈结构:

ZStack value_stack:数据栈

ZStack ctrl_stack:操作栈

数据栈存放是的Command的结构在Command数组中的索引,操作栈存放的是st_ctrl结构在st_ctrl结构数组的索引。堆栈虚拟机就是通过操作这两个栈进行运算。

 

7脚本引擎的运行原理

如图6所示,脚本引擎先通过FLEX进行词法分析,此时会产生变量、数值和操作符,再通过YACC产生Command结构数组,然后通过循环执行Command结构数组中每一个元素,此时会用到数据栈和操作栈。

对于顺序执行的程序语句来说,就是简单地对数据栈进行压栈和出栈,可以具体看下程序就能明白其意思。

8)对if-elsewhile语句设计思路进行说明:

If-else设计思路(请参考stack.cavt_if()/avt_else()/avt_if_end()三个函数):

当执行到if指令时,先到操作指令st_ctrl结构数组中加入个if操作指令,然后判断if是否与whileif else嵌套,如果不是,即从操作栈ctrl_stack去栈顶值的addr-1(执行free_stackpop_stack时把value初始化为-1),如果为嵌套,即根据前面的控制指令中control值相&,意思为:如果前面的操作control0,即前面操作不生效,&的结果为0,即嵌套内此if肯定不生效。

例子:

  if stmr1

  {

     if stmr2

  }

  当执行到“if stmr2”时操作栈内应存在“if stmr1”操作指令:

  如果stmr1false,即不会执行“if stmr2”,即stmr一定为false

  如果stmr1true,“stmr2”不知道是否生效,此处需要看&的结果。

当执行到else的时候,说明else前面的if肯定已经执行过(无论ifcontrol01都已经执行),因此这里出栈是对if出栈。因为ifelse是对立的,因此:如果else不嵌套,即addr2==-1elsecontrol等于ifcontrol的非;如果else是嵌套的,且前面嵌套的control1,那嵌套内的ifelse都可能执行,因为ifelse是对立,因此elsecontrolifcontrol的非;如果嵌套的control0,那嵌套内的ifelsecontrol肯定都为0,即已经出栈的if控制指令与else的控制指令是一样的。

例子:

  if stmr1

  {

          if stmr2

          else stmr3

  }

  

  如果stmr10,那stmr2stmr3一定为0;如果stmr11,那stmr2=!stmr3.

无论是if还是elsepush_stack,因此if-else执行完成之后,一定要pop_stack

 

While的设计思路(请参考stack.cact_begin_while()/act_while()/act_end_while()

注意此处传入的indexadd_ctrl中的index,while的执行过程可以拆分如下:

begin_while

while (exempr) act_while

{

      

}

act_end_while

 

exempr为前面的>==之类的条件判断。

不嵌套情况下:

1act_begin_while初始化当前指令索引index到控制指令,并push_stack,stack[1],stack[1]带有当前执行指令的地址(index

2exempr执行后会产生一个带有control的控制指令,并push_stack,stack[2]

3act_whilepop stack[2],pop stack[1],两个stack.control &后赋值给stack[1]push_stack[1],为新的stack[1](带有index

4act_end_whilepop stack[1],判断stack[1].control,如果为0,就把执行act_end_while的执行指令的i返回(跳出循环),

如果为1,把index返回(继续循环)

嵌套情况与不嵌套情况的差别只为前面嵌套的control值,如果为0,那么上面第1步和第三步的&后的值都为0,即不会执行,如果为1,即跟不嵌套情况一样。

五、zhttpd的限制

 1、由于zhttpd的脚本引擎是使用数组作为堆栈,其他所有结构都是静态数组,这就会造成脚本引擎将不能进行太复杂的操作。

 2、在从php文件分离出脚本语言时,只采用了BUFSIZ8k)的缓存存储脚本语言,因此php文件不能超过8k,如果超过,将可能产生错误;在print操作时只使用了512字节进行存储print的数据,如果print的数据超过512字节,将会发生错误。

 3、在接收客户端的请求报文的时候,使用了个BUFSIZ大小的缓存进行存储,因此如果请求报文大于BUFSIZ,将会出错;在处理cgi POST表单的时候,POST表单内容是直接截取客户端的请求报文,如果报文大于BUFSIZ,将会出错。

  如果后期有时间,可以对第1点将会对脚本引擎的堆栈和存放结构的静态数组改为链表,对第23点的限制可以使用动态分配内存实现,出个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

阅读(2037) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~