0

0

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

php中文网

php中文网

发布时间:2016-06-13 12:28:32

|

1014人浏览过

|

来源于php中文网

原创

深入理解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的简单示意图

以cgi模式和apache2服务器为例,它们的启动方法如下:

立即学习PHP免费学习笔记(深入)”;

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完成启动,整个过程处于单进程单线程的环境中,这个阶段包括配置文件解析、模块加载、系统资源初始化(例如日志文件、共享内存段、数据库连接等)等工作。

MindShow
MindShow

MindShow官网 | AI生成PPT,快速演示你的想法

下载

在运行阶段,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上客户-服务器事务的时序如图所示:

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函数。

相关文章

PHP速学教程(入门到精通)
PHP速学教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
全国统一发票查询平台入口合集
全国统一发票查询平台入口合集

本专题整合了全国统一发票查询入口地址合集,阅读专题下面的文章了解更多详细入口。

19

2026.02.03

短剧入口地址汇总
短剧入口地址汇总

本专题整合了短剧app推荐平台,阅读专题下面的文章了解更多详细入口。

27

2026.02.03

植物大战僵尸版本入口地址汇总
植物大战僵尸版本入口地址汇总

本专题整合了植物大战僵尸版本入口地址汇总,前往文章中寻找想要的答案。

15

2026.02.03

c语言中/相关合集
c语言中/相关合集

本专题整合了c语言中/的用法、含义解释。阅读专题下面的文章了解更多详细内容。

3

2026.02.03

漫蛙漫画网页版入口与正版在线阅读 漫蛙MANWA官网访问专题
漫蛙漫画网页版入口与正版在线阅读 漫蛙MANWA官网访问专题

本专题围绕漫蛙漫画(Manwa / Manwa2)官网网页版入口进行整理,涵盖漫蛙漫画官方主页访问方式、网页版在线阅读入口、台版正版漫画浏览说明及基础使用指引,帮助用户快速进入漫蛙漫画官网,稳定在线阅读正版漫画内容,避免误入非官方页面。

13

2026.02.03

Yandex官网入口与俄罗斯搜索引擎访问指南 Yandex中文登录与网页版入口
Yandex官网入口与俄罗斯搜索引擎访问指南 Yandex中文登录与网页版入口

本专题汇总了俄罗斯知名搜索引擎 Yandex 的官网入口、免登录访问地址、中文登录方法与网页版使用指南,帮助用户稳定访问 Yandex 官网,并提供一站式入口汇总。无论是登录入口还是在线搜索,用户都能快速获取最新稳定的访问链接与使用指南。

114

2026.02.03

Java 设计模式与重构实践
Java 设计模式与重构实践

本专题专注讲解 Java 中常用的设计模式,包括单例模式、工厂模式、观察者模式、策略模式等,并结合代码重构实践,帮助学习者掌握 如何运用设计模式优化代码结构,提高代码的可读性、可维护性和扩展性。通过具体示例,展示设计模式如何解决实际开发中的复杂问题。

3

2026.02.03

C# 并发与异步编程
C# 并发与异步编程

本专题系统讲解 C# 异步编程与并发控制,重点介绍 async 和 await 关键字、Task 类、线程池管理、并发数据结构、死锁与线程安全问题。通过多个实战项目,帮助学习者掌握 如何在 C# 中编写高效的异步代码,提升应用的并发性能与响应速度。

2

2026.02.03

Python 强化学习与深度Q网络(DQN)
Python 强化学习与深度Q网络(DQN)

本专题深入讲解 Python 在强化学习(Reinforcement Learning)中的应用,重点介绍 深度Q网络(DQN) 及其实现方法,涵盖 Q-learning 算法、深度学习与神经网络的结合、环境模拟与奖励机制设计、探索与利用的平衡等。通过构建一个简单的游戏AI,帮助学习者掌握 如何使用 Python 训练智能体在动态环境中作出决策。

3

2026.02.03

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
RunnerGo从入门到精通
RunnerGo从入门到精通

共22课时 | 1.8万人学习

尚学堂Mahout视频教程
尚学堂Mahout视频教程

共18课时 | 3.2万人学习

Linux优化视频教程
Linux优化视频教程

共14课时 | 3.1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号