BOA入门分析

http://sgabor.blog.51cto.com/7214420/1429673

常用的Web服务器有:Boa,thttpd,httpd,其中httpd只支持静态页面,thttpd和Boa支持动态页面高级应用,Boa在资源利用效率上比thttpd好。Boa支持认证,支持CGI,作为该嵌入式系统的Web服务器,系统的软件开发模型选用B/S模型,嵌入式Web服务器在Web浏览器和设备之间提供了统一的GUI接口,客户机可以通过HTTP协议与嵌入式WebServer建立连接。

一.Boa主流程分析

       Boa.c程序main中主要流程:

wKioL1Om_43xclp7AAKHj1ZzJX4595.jpg

       umask()函数设置限制新文件权限的掩码,umask设置的是权限是补码,用户登陆系统时,umask命令都被执行,并自动设置掩码改变默认值,新的权限将会把旧的覆盖。

      

devnullfd = open("/dev/null", 0);dup2(devnullfd,STDIN_FILENO;对输入重定向,写入/dev/null的东西会被系统丢掉,目的是对stdin进行保护。

      

parse_commandline(argc, argv);执行命令行,其中case'r':chdir(optarg)chroot(optarg)chdir("/");重新设定根目录,限制用户访问路径,提高服务器安全性。

      

fixup_server_root();用来检查服务器路径有没有被定义,如果没定义就停止执行并提示。

 

read_config_files(void);解析配置文件,保证所有的全局变量正确初始化.gethostname函数返回本地主机的标准主机名,并通过gethostbyname()返回该主机名的包含主机名字和地址信息的hostent结构指针,主机名保存到server_name

single_post_limitcgi_rlimit_cpucgi_rlimit_datacgi_nicemax_connectionska_timeout等变量与程序资源分配有关。

rlimit资源限制参考http://blog.csdn.net/yuyin86/article/details/8014840

      

create_common_env(void);CGI的环境变量保存到*common_env[]内,环境变量如"PATH""SERVER_SOFTWARE""SERVER_PORT""DOCUMENT_ROOT""SERVER_ROOT""SERVER_ADMIN"

      

open_logs(void);程序主要记录access_log访问信息、error_log错误信息和cgi_log信息。

access_log记录了虚拟主机地址、访问客户端地址、访问时间、访问状态、读写数据大小、HTTP Refererheader user agent等,HTTP Referer记录了外连接情况,user agent用户代理是浏览器向访问网站提供浏览器信息的一种HTTP标志。

error_log记录效果:[08/Nov/1997:01:05:03-0600] request from 192.228.331.232 "GET /~joeblow/dir/ HTTP/1.0"("/usr/user1/joeblow/public_html/dir/"): write: Broken pipe

其中包含了错误时间内、请求客户端IPCGI发送方式为GET、路径信息和错误因素。

 

       create_server_socket();创建服务器的套接字,SOCK_STREAM提供有序的、可靠的、双向的和基于连接的字节流,IPPROTO_TCP指定了Internet地址族使用TCPset_nonblock_fd()设置非阻塞方式,fcntl(server_s, F_SETFD, 1)设定close-on-exec,调用exec函数时关闭server socket,避免CGI往里面写内容。然后进行bind捆绑,并监听客户端发送的请求。

      

       init_signals();初始化各种信号的回调函数,在多线程运行中控制程序运行。

 

       build_needs_escape();浏览器在发送请求时,会把请求字符串进行转义操作,服务器要对收到的请求进行反转移操作。

 

       do_forkfork子进程,创建守护进程,作为服务程序使用,等待客户端程序与它通信。

      

       drop_privs();通过修改服务进程ID、组ID、改变权限。

 

        loop(int server_s)是一个select循环,Web服务器正常运行在循环中,循环检测各种信号发生,根据需要修改请求状态(阻塞和就绪),并作相应的处理,后文详细描述。

 

二.request请求处理

       request结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
struct  request {               
     enum  REQ_STATUS status;       /*请求状态,包括BODY_READ,BODY_WRITE,WRITE,PIPE_READ,PIPE_WRITE,IOSHUFFLE,DONE,TIMED_OUT,DEAD*/
     enum  KA_STATUS keepalive;    /* keepalive status 表示连接状态,是检测死连接的一种机制 */
     enum  HTTP_VERSION http_version;
     enum  HTTP_METHOD method;     /* M_GET, M_POST, etc.HTTP与浏览器的交互,从url读取信息的方法 */
     enum  RESPONSE_CODE response_status;  /* HTTP码应码.
                                           1xx:信息,请求收到,继续处理
                                           2xx:成功,行为被成功地接受、理解和采纳
                                           3xx:重定向,为了完成请求,必须进一步执行的动作
                                           4xx:客户端错误,请求包含语法错误或者请求无法实现
                                           5xx:服务器错误,服务器不能实现一种明显无效的请求
                                   */
     enum  CGI_TYPE cgi_type;
     enumCGI_STATUS cgi_status;             
     char  *pathname;              /* 请求文件路径名 */
     Range *ranges;               /* 请求内容搜索范围 */
     int  data_fd;                 /* 数据文件描述符 */
     unsigned  long  filesize;      /* 文件大小*/
     unsigned  long  filepos;       /* position in file */
     unsigned  long  bytes_written;  /* 被写入长度*/
     char  *data_mem;              /* 数据映射*/
     char  *header_line;           /* beginning of un or incompletelyprocessed header line */
     char  *header_end;            /* last known end of header, or endof processed data */
     int  parse_pos;               /* how much have we parsed */
     int  buffer_start;            /* buffer开始地址*/
     int  buffer_end;              /* buffer结束地址 */
     char  *if_modified_since;     /* 判断服务器端的资源是否被修改 */
     time_t  last_modified;        /* 服务器端的资源修改时间 */
     int  cgi_env_index;           /* CGI变量排序*/
     /* Agent and referer for logfiles */
     char  *header_host;             /*主机信息*/
     char  *header_user_agent;  /*代理信息*/
     char  *header_referer;  /*参考信息*/
     char  *header_ifrange;
     char  *host;                
     int  post_data_fd;            /* post数据临时文件描述符 */
        /* env variable */
     char  *path_info;           
     char  *path_translated;     
     char  *script_name;         
     char  *query_string;       
     char  *content_type;         
     char  *content_length;     
     struct  mmap_entry *mmap_entry_var;
  
     int  fd;                      /* 客户端套接字描述符 */
     time_t  time_last;            /* 上一次时间 */
     char  local_ip_addr[BOA_NI_MAXHOST];  /* 本地虚拟主机IP地址*/
     char  remote_ip_addr[BOA_NI_MAXHOST];  /* 远程客户端IP地址 */
     unsigned  int  remote_port;             /* 远程客户端端口号 */
     unsigned  int  kacount;                 /*存活连接数目*/
     int  client_stream_pos;    
     char  buffer[BUFFER_SIZE + 1];  /* 通用I/O缓冲数据*/
     char  request_uri[MAX_HEADER_LENGTH + 1];  /*uri*/
     char  client_stream[CLIENT_STREAM_SIZE];  /* 客户端发送的数据流 */
     char  *cgi_env[CGI_ENV_MAX + 4];  /* CGI 环境变量 */
#ifdef ACCEPT_ON
     char  accept[MAX_ACCEPT_LENGTH];  /* Accept:fields */
#endif
     struct  request *next;        /* 下一个请求*/
     struct  request *prev;        /*上一个请求 */
};

 

request态分别是获取、就绪、执行、阻塞、释放。

 

request相关的变量:

pending_requests 待处理请求,为1时表示有请求需要处理。

request *request_ready 就绪请求,就绪状态的请求可以马上处理。

request  *request_block 阻塞请求,阻塞的请求处于等待状态,需要先移到就绪区才能进行处理。

request *request_free  空闲的请求空间

 

与其相关函数有

ready_request ( )block_request( )

update_blocked();fd_update()process_requests()

get_requests( ); free_request( );

req_flush();

 

ready_request(request * req),把请求从阻塞链表移到就绪链表,并删除对应req->status的文件描述符集合。

fd_update()先判断是否为死链接,然后调用update_blocked完成请求就绪,并删除文件描述符集合的操作。

 

block_request(request* req),把请求从就绪链表移到阻塞链表,并根据req->status添加文件描述符的集合。

       update_blocked();根据存活时间判断阻塞链表内请求的状态更新阻塞链表,决定是否把请求从阻塞链表移到就绪链表。

 

       get_requests( );通过accept接收客户端连接信息,进行连接以后调用new_request()申请请求空间,开始接收客户端浏览器发送的请求。

请求信息例:

1
2
3
4
5
6
7
8
9
     conn->fd = fd;     
     conn->status = READ_HEADER;
     conn->header_line =conn->client_stream;
     conn->time_last = current_time;
     conn->kacount = ka_max;
     conn->remote_port = net_port(&remote_addr);
     conn->http_version= HTTP10;
     conn->method = M_GET;
     conn->status = DONE;

 

       free_request(request * req); get_requests( )相反,负责关闭连接,释放请求空间。

 

process_requests()执行请求,首先判断是否有请求,如果有请求则进行连接并接收请求。根据current->status进

read_header(current);

read_body(current);

write_body(current);

process_get(current);

read_from_pipe(current);

write_from_pipe(current);

执行DONE时调用req_flush(),req_flush函数计算需要发送数据的字节,字节数大于0则进行发送bytes_written = write(req->fd, req->buffer +req->buffer_start,bytes_to_write);

发送后返回整数值, -2表示错误,-1表示block0表示处理完毕,>0表示还有数据,需要对其进行处理后再发送。

 

三.loop循环分析

首先对信号进行分析,程序调用init_signals()初始化信号回调函数,包括SIGSEGVSIGBUSSIGTERMSIGHUPSIGINTSIGCHLDSIGALRMSIGPIPESIGUSR1SIGUSR2sigaction(SIGSEGV, &sa, NULL);sigaction函数是用作检查/修改与指定信号相关联的处理动作.

 

函数分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
void  loop( int  server_s)
{
     FD_ZERO(BOA_READ); //每次循环都要清空集合,否则不能检测描述符变化
     FD_ZERO(BOA_WRITE);
     max_fd = -1;
        /*捕捉信号,并调用对应函数进行处理*/
     while  (1) {
         if  (sighup_flag)
             sighup_run();      
         if  (sigchld_flag)
             sigchld_run();
         if  (sigalrm_flag)
             sigalrm_run();
         if  (sigterm_flag) {
             if  (sigterm_flag == 1) {
                 sigterm_stage1_run();
                 BOA_FD_CLR(req, server_s,BOA_READ);
                 close(server_s);
                 /* make sure the server isn'tin the block list */
                 server_s = -1;
             }
             if  (sigterm_flag == 2 &&!request_ready && !request_block) {
                 sigterm_stage2_run();  /*terminal */
             }
         else  {
             if  (total_connections >max_connections) {
     /*连接数太多,则清除一个文件描述符集合*/
                 BOA_FD_CLR(req, server_s,BOA_READ);
             else  {
     /*创建一个文件描述符集合*/
                 BOA_FD_SET(req, server_s,BOA_READ);  /* server always set */
             }
         }
         pending_requests = 0;
         if  (max_fd) {
             struct  timeval req_timeout;  /*timeval for select */
             req_timeout.tv_sec = (request_ready ? 0 :default_timeout);
             req_timeout.tv_usec = 0l;  /* resettimeout */
/* 监视我们需要监视的文件描述符的变化情况,读写或异常Select函数使用参考http://genime.blog.163.com/blog/static/1671577532012418341877/ */
if  (select(max_fd + 1, BOA_READ,
                        BOA_WRITE, NULL,
                        (request_ready ||request_block ?
                         &req_timeout :NULL)) == -1) {
                 if  ( errno  == EINTR)
                     continue ;        /* while(1) */
                 else  if  ( errno  != EBADF) {
                     DIE( "select" );
                 }
             }
             if  (!sigterm_flag &&FD_ISSET(server_s, BOA_READ)) {
                 pending_requests = 1;
             }
             time (&current_time);
         }
         max_fd = -1;
         if  (request_block) {
             /* 如果request_block=1,则把请求信号移到就绪链表*/
             fdset_update();
         }
         if  (pending_requests || request_ready){
            /* 有待处理请求或者就绪请求则进行请求处理*/
             process_requests(server_s);
         }
     }
}

sighup调用sighup_run(),清除mimepasswdalias哈希表,清空request_free链表,并重新读取配置文件。

sigchld_flag调用sigchld_run(),处理子进程。

sigalrm_flag调用sigalrm_run(),将mimepasswd哈希表信息写到日志里。

 

SIGTERM信号,

sigterm_flag =1

1
2
3
4
5
6
7
void  sigterm_stage1_run( void )
{                                /* lame duck mode */
    time (&current_time);
    log_error_time();
    fputs ( "caught SIGTERM, starting shutdown\n" , stderr);
     sigterm_flag =2;
}

sigterm_flag =2时,进行关闭操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void  sigterm_stage2_run( void )
{                                /* lame duckmode */
    log_error_time();
     fprintf (stderr,
            "exiting Boa normally (uptime %d seconds)\n" ,
             ( int )(current_time - start_time));
     chdir(tempdir);
    clear_common_env();         //清空环境变量
     dump_mime();                     //清空mime_hashtable
     dump_passwd();                 //清空passwd_hashtable
     dump_alias();                      //清空alias
    free_requests();                 //释放请求空间
    range_pool_empty();          //清空队列
    free (server_root);                      //释放服务器
    free (server_name);           
     server_root =NULL;
    exit (EXIT_SUCCESS);        //退出。
}

 

Loop循环先进行信号捕获,如果捕获到信号进行相应的操作,如果没有则进行请求的处理:首先检测是否有待处理请求,当一个请求到来时,将创建一个子进程为用户的连接服务。根据请求的不同,服务器返回HTML文件或者通过CGI调用外部应用程序,返回处理结果。服务器通过CGI与外部程序和脚本之间进行交互,根据客户端在进行请求时所采取的方法,服务器会收集客户所提供的信息,并将该部分信息发送给指定的CGI扩展程序。CGI扩展程序进行信息处理并将结果返回服务器,然后服务器对信息进行分析,并将结果发送回客户端。

 

四.CGI

       CGICommon Gateway Interface)是外部应用扩展应用程序与WWW服务器交互的一个标准接口。按照CGI标准编写的外部扩展应用程序可以处理客户端浏览器输入的数据,从而完成客户端与服务器的交互操作。而CGI规范就定义了Web服务器如何向扩展应用程序发送消息,在收到扩展应用程序的信息后又如何进行处理等内容。通过CGI可以提供许多静态的HTML网页无法实现的功能,比如搜索引擎、基于Web的数据库访问等等。

服务器程序可以通过三种途径接收信息:环境变量、命令行和标准输入.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
switch  (req->method) {
         case  M_POST:
             w = "POST" //环境变量
             break ;
         case  M_HEAD:           //标准输入
             w = "HEAD" ;
             break ;
         case  M_GET:              //命令行
             w = "GET" ;
             break ;
         default :
             w = "UNKNOWN" ;
             break ;
         }

BOA使用get方法获得静态文档。调用init_get(request * req)

先打开目标文件目录

memcpy(gzip_pathname, req->pathname, len);

data_fd = open(gzip_pathname, O_RDONLY);

读取文件信息,然后根据req结构更新文件信息,并把数据写入req->data_mem

memcpy(req->buffer + req->buffer_end, req->data_mem+ r->start, bytes_free);

修改后调用process_get().

 

BOA使用POST方法处理较大的数据以及动态脚本,程序调用create_common_env(void)通过env_gen_extra( )函数读取环境变量"PATH""SERVER_SOFTWARE""SERVER_PORT""DOCUMENT_ROOT""SERVER_ROOT""SERVER_ADMIN"值并保存在**common_env.

add_to_common_env( )增加环境变量项clear_common_env()删除环境变量项。

init_cgi(request * req)函数处理HTTP头信息,绑定pipecgi的输入输出,为数据传输做准备,创建子进程,子进程从管道pipes[0]读取数据,父进程写数据到pipes[1]并输出。

process_cgi_header(request * req);函数对浏览器发送的http头信息进行处理,成为浏览器可以读懂的字符串。服务器经过处理把结果发送回CGIcgi 程序输出HTML页面的方式是使用printf 把页面一行一行地打印出来,如

fprintf(cgiOut, "<HTML><HEAD>/n"); 

fprintf(cgiOut, "<TITLE>helloworld</TITLE></HEAD>/n"): 

fprintf(cgiOut, "<BODY><H1> hello world</H1>/n"); 

信息传送到浏览器并在显示器上显示。

 

结语:

       编程初入门,部分代码为猜测,内容可能出现较多错误,请不吝指正。


  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值