深切了解PHP内核(三)概览-SAPI概述

 本文链接:http://www.orlion.ml/234/

1、在PHP生命周期的逐一阶段,一些暨劳动相关的操作都是通过SAPI接口实现。这些内置实现的情理位置于PHP源码的SAPI目录。这个目录存放了PHP对一一服务器抽象层的代码,例如命令行程序的落实,Apache的mod_php模块实现同fastcgi的兑现等等

当挨家挨户服务器抽象层之间遵守着平等之预定,这里我们称之为SAPI接口。每个SAPI实现还是一个_sapi_module_struct结构体变量。(SAPI接口)。在PHP的源码中,当得调用服务器相关消息时,全部经过SAPI接口中对应的方调用实现,而这些主意以一一服务器抽象层实现时还见面发各自的落实。由于过多操作的通用性,有大非常片段接口方法以的是默认方法。下图为SPAI的简示意图

图片 1

盖cgi模式以及apache2服务器也条例,它们的起步方法如下:

cgi_sapi_module.startup(&cgi_sapi_module) // cgi模式 cgi/cgi_main.c文件

apache_sapi_module.startup(&apache_sapi_module); // apache服务器  apache2handler/sapi_apache2.c文件

这里的cgi_sapi_module是sapi_module_struct结构体的静态变量。它的startup方法对php_cgi_startup函数指针。在这个结构体中除startup函数指针,还有好多旁方法或者字段,这些构造于服务器的接口实现着都发定义

 

整个SAPI类似于一个面向对象中之模版方法模式之运。SAPI.c和SAPI.h文件所含有的片段函数就是模板方法模式面临的抽象模板,各个服务器对sapi_module的定义跟有关兑现则是一个个切实可行的模版

 

2、Apache模块

(1)当PHP需要以Apache服务器下运作时,一般的话,它可mod_php5模块的形式集成,此时mod_php5模块的作用是接收Aapche传递过来的PHP文件要,并拍卖这些请求,然后拿处理后底结果回到给Apache。如果我们在Apache启动前于那安排文件中布局了PHP模块,PHP模块通过挂号apache2的ap_hook_post_config挂钩,在Apache启动之早晚启动之模块以收取PHP文件的恳求。

除此之外这种启动时之加载方式,Apache的模块可当运转的时段动态装载,这意味对服务器可以进行力量扩展而非需更对源代码进行编译,甚至不欲再次开服务器。我们所要做的单独是深受服务器发送信号HUP或者AP_SIG_GEACEFUL通知服务器又载入模块。但是在动态装载之前我们要用模块编译成为动态链接库。此时底动态加载就是加载动态链接库。Apache中对动态链接库的处理是经模块mod_so来完成的,因此mod_so模块不克让动态加载,它不得不本静态编译进Apache的主干。这表示它同Apache一起启动之。

 

Apache是哪些加载模块的也罢?以mod_php5为条例,首先在httpd.conf中上加一行:

LoadModule php5_module modules/mod_php5.so

以配置文件被补充加了所示的下令后,Apache在加载模块时见面依据模块名查找模块并加载。Apache的各一个模块都是以module结构体的款式存在,module结构的name属性在终极是由此宏STANDARD20_MODULE_STUFF以__FILE__反映。通过事先的下令中指定的途径找到有关的动态链接库文件后,Apache通过内的函数获取动态链接库中之始末,并以模块的内容加载到内存中指定变量中。

以审激活模块之前,Apache会检查有着加载的模块是否也真的Apache模块。最后Apache会调用相关的函数(ap_add_loaded_module)将模块激活,此处的激活就是将模块放入相应的链表中(ap_top_modules链表)

Apache加载的是PHP模块,那么是模块时怎么落实的也?Apache2的mod_php5模块包括sapi/apache2handler和sapi/apache2filter两单目录,在apache2_handle/mod_php5.c文件被,模块定义的相干代码如下:

AP_MODULE_DECLARE_DATA module php5_module = {
    STANDARD20_MODULE_STUFF,
        /* 宏,包括版本,小版本,模块索引,模块名,下一个模块指针等信息,其中模块名以__FILE__体现*/
    create_php_config,      /* create per-directory config structure */
    merge_php_config,       /* merge per-directory config structures */
    NULL,                   /* create per-server config structure */
    NULL,                   /* merge per-server config structures */
    php_dir_cmds,           /*模块定义的所有命令*/
    php_ap2_register_hook  /*注册钩子,此函数通过ap_hoo_开头的函数在一次处理过程中对于指定的步骤注册钩子*/
};

它所对应的凡Apache的module结构,module的构造定义如下:

typedef struct module_struct module;
struct module_struct {
    int version;
    int minor_version;
    int module_index;
    const char *name;
    void *dynamic_load_handle;
    struct module_struct *next;
    unsigned long magic;
    void (*rewrite_args) (process_rec *process);
    void *(*create_dir_config) (apr_pool_t *p, char *dir);
    void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
    void *(*create_server_config) (apr_pool_t *p, server_rec *s);
    void *(*merge_server_config) (apr_pool_t *p, void *base_conf, void 
*new_conf);
    const command_rec *cmds;
    void (*register_hooks) (apr_pool_t *p);
}

 

点的模块结构以及我们以mod_php5.c中所观看底构造产生好几例外,这是出于STANDARD20_MODULE_STUFF的来头,这个宏它包含了眼前8只字段的概念。STANDARD20_MODULE_STUFF宏的定义如下:

/** Use this in all standard modules */
#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \
                MODULE_MAGIC_NUMBER_MINOR, \
                -1, \
                __FILE__, \
                NULL, \
                NULL, \
                MODULE_MAGIC_COOKIE, \
                                NULL      /* rewrite args spot */

在php5_module定义之布局中,php_dir_cmds是模块定义的所有的命令集合,定义之情如下:

const command_rec php_dir_cmds[] =
{
    AP_INIT_TAKE2("php_value", php_apache_value_handler, NULL,
        OR_OPTIONS, "PHP Value Modifier"),
    AP_INIT_TAKE2("php_flag", php_apache_flag_handler, NULL,
        OR_OPTIONS, "PHP Flag Modifier"),
    AP_INIT_TAKE2("php_admin_value", php_apache_admin_value_handler,
        NULL, ACCESS_CONF|RSRC_CONF, "PHP Value Modifier (Admin)"),
    AP_INIT_TAKE2("php_admin_flag", php_apache_admin_flag_handler,
        NULL, ACCESS_CONF|RSRC_CONF, "PHP Flag Modifier (Admin)"),
    AP_INIT_TAKE1("PHPINIDir", php_apache_phpini_set, NULL,
        RSRC_CONF, "Directory containing the php.ini file"),
    {NULL}
};

 

这是mod_php5模块定义之指令表。它事实上是一个commond_rec结构的数组。当Apache遇到指令的时节以相继遍历各个模块中的指令表,查找是否发充分模块能处理该令,如果找到,则调用响应的处理函数,如果有指令表中的模块都不可知处理该令,那么以报错,如达到所展现,mod_php5模块仅提供php_value等5个指令。

php_ap2_register_hook函数的定义如下:

void php_ap2_register_hook(apr_pool_t *p)
{
    ap_hook_pre_config(php_pre_config, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_post_config(php_apache_server_startup, NULL, NULL, 
APR_HOOK_MIDDLE);
    ap_hook_handler(php_handler, NULL, NULL, APR_HOOK_MIDDLE);
    ap_hook_child_init(php_apache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
}

以上代码声明了pre_config,post_config,handler和child_init4独关系和相应的处理函数。其中pre_config,post_config,child_init是开行挂钩,它们于服务器启动时调用。handler挂钩是告挂钩,它当服务器处理要时调用。其中以post_config挂钩中启动php。它经过php_apache_server_startup函数实现,php_apache_server_startup函数通过调用sapi_startup启动sapi,并经过调用php_apache2_startup来注册sapi
module
struct,最后调用php_module_startup初始化php,其中以见面初始化Zend引擎,以及填充zend_module_struct中的treat_data成员(通过php_startup_sapi_content_types)等。

  到这里,我们领略了Apache加载mod_php5模块的周经过,可是这个过程及我们的饿SAPI有啊关系呢?mod_php5也定义了属于Apache的sapi_module_struct结构:

static sapi_module_struct apache2_sapi_module = {
"apache2handler",
"Apache 2.0 Handler",

php_apache2_startup,                /* startup */
php_module_shutdown_wrapper,            /* shutdown */

NULL,                       /* activate */
NULL,                       /* deactivate */

php_apache_sapi_ub_write,           /* unbuffered write */
php_apache_sapi_flush,              /* flush */
php_apache_sapi_get_stat,           /* get uid */
php_apache_sapi_getenv,             /* getenv */
php_error,                  /* error handler */

php_apache_sapi_header_handler,         /* header handler */
php_apache_sapi_send_headers,           /* send headers handler */
NULL,                       /* send header handler */

php_apache_sapi_read_post,          /* read POST data */
php_apache_sapi_read_cookies,           /* read Cookies */

php_apache_sapi_register_variables,
php_apache_sapi_log_message,            /* Log message */
php_apache_sapi_get_request_time,       /* Request Time */
NULL,                       /* Child Terminate */

STANDARD_SAPI_MODULE_PROPERTIES
};

 

这些方式都属Apache服务器,以朗诵取cookie为条例,当我们于Apache服务器环境下,在PHP中调用读取Cookie时,最终获得之数码的职是以激活SAPI时,它所调用的不二法门是read_cookie。

SG(request_info).cookie_data = sapi_module.read_cookies(TSRMLS_C);

对各一个服务器在加载时,我们都指定了sapi_module,而Apache的sapi_module是apache2_sapi_module。其中针对应read_cookie的点子是php_apache_sapi_read_cookie函数。这为是定义SAPI结构的说辞:统一接口,面向接口编程,具有更好的扩展性和适应性。

(2)Apache的运作过程

Apache的运行包括启动阶段同运行等,启动等Apache以root完成启动,整个经过处于单进程单线程的条件被,这个阶段包括安排文件分析、模块加载、系统资源初始化(例如日志文件、共享内存段、数据库连接等)等工作。

每当运行阶段,Apache主要办事是拍卖用户的劳务要,在这等级Apache以普通用户运行。主要是安全性考虑,Apache对HTTP的求可以分为连接、处理及断开连接三独好的品。

2、FastCGI

(1)cgi是通用网关接口(Common Gateway
Intedface),它可被一个客户端起网页浏览器为实施于Web服务器上之程序要数据。CGI描述了客户端以及斯序中传输数据的正规。CGI的一个目的是单身为其他语言,所以CGI可以据此其他语言编写,只要这种语言有标准输入、输出及环境变量。如PHP、perl、tcl等。

FastCGI是Web服务器和处理程序之间通信的同等栽协议,是CGI的平等种植改进方案,FastCGI像是一个常驻型的CGI,它可一直推行,在伸手到达时未见面花费时间去fork一个历程来处理(这是CGI对位人诟病的fork-and-execute模式)。正是因她才是一个通信协议,它还支持分布式的演算,即FastCGI程序可以以网站服务器以外的主机上推行并且接受来自其它网站服务器的要

FastCGI的全方位工艺流程是这般的:

  Step1:Web Server启动时载入FastCGI进程管理器(IIS ISAPI或Apache
Module)

  Step2:FastCGI进程管理器自身初始化,启动多单CGI解释器进程(可见多只php-cgi)并等来自web
server的连年

  Step3:当客户端请求到达Web
Server时,FastCGI进程管理器选择并连接至一个CGI解释器。Web
Server将CGI环境变量和规范输入发送到FastCGI子进程php-cgi

  Step4:FastCGI子进程就处理后拿业内输出及不当新歌词起同连接返回Web
Server
当FastCGI子进程关闭连接时,请求虽结束。FastCGI子进程就等待并处理来自FastCGI进程管理器(运行在Web
Server中)的生一个连。在CGI模式面临,php-cgi在这个就脱离了。

 

(2)php中CGI实现

PHP的CGI实现了Fastcgi协议。是一个TCP或UDP协议的服务器接受来自Web服务器的求,当启动时创造TCP/UDP协议的服务器的socket监听,并领有关请求并拓展拍卖。随后就进了PHP的生命周期:模块初始化,sapi初始化,处理PHP请求,模块关闭,sapi关闭等
就做了所有CGI的生命周期。

盖TCP为例在,在TCP的服务端,一般会实行这样几个步骤:

1、调用socket函数创建一个TCP用的流式套接字;

2、调用bind函数将服务器的地面地址与眼前创建的套接字绑定;

3、调用listen函数将新创造的套接字作为监听,等待客户端发起的总是,当客户端起多单连续连接至者法接字时,可能用排队处理;

4、服务器进程调用accept函数进入阻塞状态,直到来客户过程调用connect函数而树立由一个连;

5、当跟客户端创建连接后,服务器调用read_stream函数读取客户端的乞求;

6、处理完数据后,服务器调用write函数向客户端发送应答

TCP上客户-服务器业务之时序如图所示:

图片 2

php的CGI实现从cgi_main.c文件的main函数开始,在main函数中调用了定义在fastcgi.c文件中之初始化,监听等函数。对比TCP的流水线,我们查阅php对TCP协议的落实,虽然php本身为实现了这些流程,但是于main函数中有的经过让封装成一个函数实现。对诺TCP的操作流程,PHP首先会见执行创建socket,绑定套接字,创建监听:

if (bindpath) {
    fcgi_fd = fcgi_listen(bindpath, 128);   //  socket˥˦2sfcgi_initɩ    
Ȑ
    ...
}

在fastcgi.c文件中,fcig_listen函数主要用以创造、绑定socket并开始监听,它走了了前面所列TCP流程的面前三独号,

 if ((listen_socket = socket(sa.sa.sa_family, SOCK_STREAM, 0)) < 0 ||
        ...
        bind(listen_socket, (struct sockaddr *) &sa, sock_len) < 0 ||
        listen(listen_socket, backlog) < 0) {
        ...
    }

当服务端初始化完成后,进程调用accept函数进入阻塞状态,在main函数中我们看看如下代码:

  while (parent) {
        do {
            pid = fork();   //  oҟ
ȨėJ
            switch (pid) {
            case 0: //  ȨėJ
                parent = 0;

                /* don't catch our signals */
                sigaction(SIGTERM, &old_term, 0);   //  ľâ¯ķ
                sigaction(SIGQUIT, &old_quit, 0);   //  ľĿɰ£ƺ
                sigaction(SIGINT,  &old_int,  0);   //  ľĿKȠƺ
                break;
                ...
                default:
                /* Fine */
                running++;
                break;
        } while (parent && (running < children));

    ...
        while (!fastcgi || fcgi_accept_request(&request) >= 0) {
        SG(server_context) = (void *) &request;
        init_request_info(TSRMLS_C);
        CG(interactive) = 0;
                    ...
            }

而达到之代码是一个生成子进程,并伺机用户请求。在fcgi_accept_request函数中,程序会调用accept函数阻塞新创办的线程。当用户的要到达时,fcgi_accept_request函数会判断是否处理用户之呼吁,其中会过滤某些连接要,忽小受限制客户之求,如果程序受理用户的要,他拿分析请求的音,将有关的变量写到相应的变量中。其中于读取请求内容时调用了safe_read方法。如下所示:main()->fcgi_accept_request()->fcgi_read_request()->safe_read()

static inline ssize_t safe_read(fcgi_request *req, const void *buf, size_t 
count)
{
    size_t n = 0;
    do {
    ... //  省略 对win32的处理
        ret = read(req->fd, ((char*)buf)+n, count-n);   //  非win版本的读操作
D‰
    ... // 省略
    } while (n != count);

}

万一齐相应服务器端读取用户的要数据。

每当恳求初始化完成,读取请求了后,就该处理要的PHP文件了。假设此次请求为PHP_MODE_STANDARD则会调用php_execute_script执行PHP文件。在这个函数中其先初始化此文件有关的部分内容,然后再度调用zend_execute_scripts函数,对PHP文件进行词法分析与语法分析,生成中间代码,并施行zend_execute函数,从而执行这些中间代码。

  以处理终结用户之求后,服务端将回到信息给客户端,此时以main函数中调用的是fcgi_finish_request(&request
, 1);fcgi_finish_request函数定义在fasftcgi.c文件中。

  以殡葬了请的回后,服务器端将见面履关闭操作,仅限于CGI本身的关闭,程序执行的凡fcgi_close函数。

相关文章