`
andrew__
  • 浏览: 9599 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

PHP扩展之资源的使用

    博客分类:
  • PHP
阅读更多
先描述下{资源}类型在内核中的结构:
//每一个资源都是通过它来实现的。
typedef struct _zend_rsrc_list_entry
{
    void *ptr;
    int type;
    int refcount;
}zend_rsrc_list_entry;
在真实世界中,我们经常需要操作一些不好用标量值表现的数据,比如某个文件的句柄,而对于C来说,它也仅仅是个指针而已。
#include <stdio.h>
int main(void)
{
    FILE *fd;
    fd = fopen("/home/jdoe/.plan", "r");
    fclose(fd);
    return 0;
}
C语言中stdio的文件描述符(file descriptor)是与每个打开的文件相匹配的一个变量,它实际上十一个FILE类型的指针,它将在程序与硬件交互通讯时使用。我们可以使用fopen函数来打开一个文件获取句柄,之后只需把这个句柄传递给feof()、fread()、fwrite()、fclose()之类的函数,便可以对这个文件进行后续操作了。既然这个数据在C语言中就无法直接用标量数据来表示,那我们如何对其进行封装才能保证用户在PHP语言中也能使用到它呢?这便是PHP中资源类型变量的作用了!所以它也是通过一个zval结构来进行封装的。 资源类型的实现并不复杂,它的值其实仅仅是一个整数,内核将根据这个整数值去一个类似资源池的地方寻找最终需要的数据。

资源类型变量的使用
资源类型的变量在实现中也是有类型区分的!为了区分不同类型的资源,比如一个是文件句柄,一个是mysql链接,我们需要为其赋予不同的分类名称。首先,我们需要先把这个分类添加到程序中去。这一步的操作可以在MINIT中来做:
#define PHP_SAMPLE_DESCRIPTOR_RES_NAME "山寨文件描述符"
static int le_sample_descriptor;
ZEND_MINIT_FUNCTION(sample)
{
    le_sample_descriptor = zend_register_list_destructors_ex(NULL, NULL, PHP_SAMPLE_DESCRIPTOR_RES_NAME,module_number);
    return SUCCESS;
}

//附加资料
#define register_list_destructors(ld, pld) zend_register_list_destructors((void (*)(void *))ld, (void (*)(void *))pld, module_number);
ZEND_API int zend_register_list_destructors(void (*ld)(void *), void (*pld)(void *), int module_number);
ZEND_API int zend_register_list_destructors_ex(rsrc_dtor_func_t ld, rsrc_dtor_func_t pld, char *type_name, int module_number);
接下来,我们把定义好的MINIT阶段的函数添加到扩展的module_entry里去,只需要把原来的"NULL, /* MINIT */"一行替换掉即可:
ZEND_MINIT(sample), /* MINIT */
ZEND_MINIT_FUNCTION()宏用来帮助我们定义MINIT阶段的函数,这我们已经在第一章里描述过了,但将会在第12章和第三章有更详细的描述。 What's important to know at this juncture is that the MINIT method is executed once when your extension is first loaded and before any requests have been received. Here you've used that opportunity to register destructor functionsthe NULL values, which you'll change soon enoughfor a resource type that will be thereafter known by a unique integer ID. 看到zend_register_list_destructors_ex()函数,你肯定会想是不是也存在一个zend_register_list_destructors()函数呢?是的,确实有这么一个函数,它的参数中比前者少了资源类别的名称。那这两这的区别在哪呢?
eaco $re_1;
//resource(4) of type (山寨版File句柄)

echo $re_2;
//resource(4) of type (Unknown)
创建资源
我们在上面向内核中注册了一种新的资源类型,下一步便可以创建这种类型的资源变量了。接下来让我们简单的重新实现一个fopen函数,现在叫sample_open:
PHP_FUNCTION(sample_fopen)
{
    FILE *fp;
    char *filename, *mode;
    int filename_len, mode_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",&filename, &filename_len,&mode, &mode_len) == FAILURE)
    {
        RETURN_NULL();
    }
    if (!filename_len || !mode_len)
    {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid filename or mode length");
            RETURN_FALSE;
    }
    fp = fopen(filename, mode);
    if (!fp)
    {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"Unable to open %s using mode %s",filename, mode);
            RETURN_FALSE;
    }
    //将fp添加到资源池中去,并标记它为le_sample_descriptor类型的。
    ZEND_REGISTER_RESOURCE(return_value,fp,le_sample_descriptor);
}
如果前面章节的知识你都看过的话,应该可以猜出最后一行代码是干啥的了。它创建了一个新的le_sample_descriptor类型的资源,此资源的值是fp,另外它把这个资源加入到一个存储资源的HashTable中,并把此资源在其中对应的数字Key赋给return_value。

资源并不局限于文件句柄,我们可以申请一块内存,并它指向它的指针来作为一种资源。所以资源可以对应任意类型的数据。
销毁资源
世间万物皆有喜有悲,有生有灭,到了我们探讨如何销毁资源的时候了。最简单的一种莫过于仿照fclose写一个sample_close()函数,在它里面实现对某种{资源:专指PHP的资源类型变量代表的值}的释放。

但是,如果用户端的脚本通过unset()函数来释放某个资源类型的变量会如何呢?它们可不知道它的值最终对应一个FILE指针啊,所以也无法使用fclose()函数来释放它,这个FILE句柄很有可能会一直存在于内存中,直到PHP程序挂掉,由OS来回收。但在一个平常的Web环境中,我们的服务器都会长时间运行的。 难道就没有解决方案了吗?当然不是,谜底就在那个NULL参数里,就是我们在上面为了生成新的资源类型,调用的zend_register_list_destructors_ex()函数的第一个参数和第二个参数。这两个参数都各自代表一个回调参数。第一个回调函数会在脚本中的相应类型的资源变量被释放掉的时候触发,比如作用域结束了,或者被unset()掉了。

第二个回调函数则是用在一个类似于长链接类型的资源上的,也就是这个资源创建后会一直存在于内存中,而不会在request结束后被释放掉。它将会在Web服务器进程终止时调用,相当于在MSHUTDOWN阶段被内核调用。有关persistent resources的事宜,我们将在下一节里详述。

我们先来定义第一种回调函数。
static void php_sample_descriptor_dtor(zend_rsrc_list_entry *rsrc TSRMLS_DC)
{
    FILE *fp = (FILE*)rsrc->ptr;
    fclose(fp);
}
然后用它替换掉zend_register_list_destructors_ex()函数的第一个参数NULL:
le_sample_descriptor = zend_register_list_destructors_ex(
        php_sample_descriptor_dtor,
        NULL,
        PHP_SAMPLE_DESCRIPTOR_RES_NAME,
        module_number);
现在,如果脚本中得到了一个上述类型的资源变量,当它被unset的时候,或者因为作用域执行完被内核释放掉的时候都会被内核调用底层的php_sample_descriptor_dtor来预处理它。这样一来,貌似我们根本就不需要sample_close()函数了!
<?php
  $fp = sample_fopen("/home/jdoe/notes.txt", "r");
  unset($fp);
?>
unset($fp)执行后,内核会自动的调用php_sample_descriptor_dtor函数来清理这个变量对应的一些数据。 当然,事情绝对没有这么简单,让我们先记住这个疑问,继续往下看。

Decoding Resources
我们把资源变量比作书签,可如果仅有书签的话绝对没有任何作用啊!我们需要通过书签找到相应的页才行。对于资源变量,我们必须能够通过它找到相应的最终数据才行!
ZEND_FUNCTION(sample_fwrite)
{
    FILE *fp;
    zval *file_resource;
    char *data;
    int data_len;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",&file_resource, &data, &data_len) == FAILURE )
    {
        RETURN_NULL();
    }
    /* Use the zval* to verify the resource type and
     * retrieve its pointer from the lookup table */
    ZEND_FETCH_RESOURCE(fp,FILE*,&file_resource,-1,PHP_SAMPLE_DESCRIPTOR_RES_NAME,le_sample_descriptor);
    
    /* Write the data, and
     * return the number of bytes which were
     * successfully written to the file */
    RETURN_LONG(fwrite(data, 1, data_len, fp));
}
zend_parse_parameters()函数中的r占位符代表着接收资源类型的变量,它的载体是一个zval*。然后让我们看一下ZEND_FETCH_RESOURCE()宏函数。
#define ZEND_FETCH_RESOURCE(rsrc, rsrc_type, passed_id,default_id, resource_type_name, resource_type)
    rsrc = (rsrc_type) zend_fetch_resource(passed_id TSRMLS_CC,default_id, resource_type_name, NULL,1, resource_type);
    ZEND_VERIFY_RESOURCE(rsrc);

//在我们的例子中,它是这样的:
fp = (FILE*) zend_fetch_resource(&file_descriptor TSRMLS_CC, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, NULL,1, le_sample_descriptor);
if (!fp)
{
    RETURN_FALSE;
}
zend_fetch_resource()是对zend_hash_find()的一层封装,它使用一个数字key去一个保存各种{资源}的HashTable中寻找最终需要的数据,找到之后,我们用ZEND_VERIFY_RESOURCE()宏函数校验一下这个数据。从上面的代码中我们可以看出,NULL、0是绝对不能作为一种资源的。 上面的例子中,zend_fetch_resource()函数首先获取le_sample_descriptor代表的资源类型,如果资源不存在或者接收的zval不是一个资源类型的变量,它便会返回NULL,并抛出相应的错误信息。 最后的ZEND_VERIFY_RESOURCE()宏函数如果检测到错误,便会自动返回,使我们可以从错误检测中脱离出来,更加专注于程序的主逻辑。现在我们已经获取到了相应的FILE*了,下面就用fwrite()向其中写入点数据吧!。
To avoid having zend_fetch_resource() generate an error on failure, simply pass NULL for the resource_type_name parameter. Without a meaningful error message to display, zend_fetch_resource() will fail silently instead.
我们也可以通过另一种方法来获取我们最终想要的数据。

ZEND_FUNCTION(sample_fwrite)
{
    FILE *fp;
    zval *file_resource;
    char *data;
    int data_len, rsrc_type;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "rs",&file_resource, &data, &data_len) == FAILURE ) {
        RETURN_NULL();
    }
    fp = (FILE*)zend_list_find(Z_RESVAL_P(file_resource),&rsrc_type);
    if (!fp || rsrc_type != le_sample_descriptor) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING,"Invalid resource provided");
        RETURN_FALSE;
    }
    RETURN_LONG(fwrite(data, 1, data_len, fp));
}
可以根据自己习惯来选择到底使用哪一种形式,不过推荐使用ZEND_FETCH_RESOURCE()宏函数。

Forcing Destruction
在上面我们还有个疑问没有解决,就是类似于我们上面实现的unset($fp)真的是万能的么?当然不是,看一下下面的代码:
<?php
  $fp = sample_fopen("/home/jdoe/world_domination.log", "a");
  $evil_log = $fp;
  unset($fp);
?>
这次,$fp和$evil_log共用一个zval,虽然$fp被释放了,但是它的zval并不会被释放,因为$evil_log还在用着。也就是说,现在$evil_log代表的文件句柄仍然是可以写入的!所以为了避免这种错误,真的需要我们手动来close it!sample_close()函数是必须存在的!
PHP_FUNCTION(sample_fclose)
{
    FILE *fp;
    zval *file_resource;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "r",&file_resource) == FAILURE ) {
        RETURN_NULL();
    }
    
    /* While it's not necessary to actually fetch the
     * FILE* resource, performing the fetch provides
     * an opportunity to verify that we are closing
     * the correct resource type. */
    ZEND_FETCH_RESOURCE(fp, FILE*, &file_resource, -1,PHP_SAMPLE_DESCRIPTOR_RES_NAME, le_sample_descriptor);
    
    /* Force the resource into self-destruct mode */
    zend_hash_index_del(&EG(regular_list),Z_RESVAL_P(file_resource));
    RETURN_TRUE;
}
这个删除操作也再次说明了资源数据是保存在HashTable中的。虽然我们可以通过zend_hash_index_find()或者zend_hash_next_index_insert()之类的函数操作这个储存资源的HashTable,但这绝不是一个好主意,因为在后续的版本中,PHP可能会修改有关这一部分的实现方式,到那时上述方法便不起作用了,所以为了更好的兼容性,请使用标准的宏函数或者api函数。 当我们在EG(regular_list)这个HashTable中删除数据的时候,回调用一个dtor函数,它根据资源变量的类别来调用相应的dtor函数实现,就是我们调用zend_register_list_destructors_ex()函数时的第一个参数。

在很多地方,我们都会看到一个专门用来删除的zend_list_delete()宏函数,因为它考虑了资源数据自己的引用计数。
分享到:
评论

相关推荐

    php扩展开发资源

    ### PHP扩展开发资源知识点概述 #### 一、项目背景与目的 该项目名为“PHP扩展开发及内核应用”,旨在提供一套全面且系统的资料,帮助读者深入理解和掌握PHP扩展开发及内核相关的知识。作者提到该项目是以Sara ...

    php intl扩展文件

    - 使用资源文件存储可翻译的文本,避免硬编码,提高代码可维护性。 - 了解目标地区的文化差异,确保本地化的准确性和尊重。 通过理解和充分利用php intl扩展,开发者可以构建出更加国际化和用户友好的应用程序,...

    php扩展开发与内核应用

    9. 面向对象编程:在PHP扩展中实现面向对象编程,包括zend_class_entry结构体的使用,定义类和接口,以及类的继承和接口的实现。 10. 启动与终止的那点事:介绍扩展在启动和终止时要做的一些工作,如生命周期管理,...

    php扩展学习PDF

    3. **资源管理**:讨论如何管理和释放PHP扩展中使用的内存,避免内存泄漏和其他资源管理问题。 4. **类型系统**:介绍PHP的弱类型系统以及如何在C语言中处理PHP的变量和数据类型。 5. **对象模型**:深入理解PHP的...

    PHP扩展开发.pdf

    这意味着在PHP扩展开发过程中,开发者需要注意不要在扩展中使用全局变量,或者使用锁机制来避免数据访问冲突。 最后,文章提及了在PHP扩展开发中需要注意的一些最佳实践,比如如何利用SAPI提供的宏函数来简化扩展...

    PHP环境安装swoole-loader扩展

    Swoole是一个开源的PHP扩展,它为PHP提供了原生的异步多线程服务器,使得PHP可以进行高性能的网络编程。 首先,Swoole支持多种操作系统,包括Windows和Linux。这意味着无论你的开发环境是哪个平台,都有相应的版本...

    php5.6 php7.0 php7.1 memcache.dll扩展

    描述中提到,“该资源包含php5.6、php7.0及php7.1的memcache扩展”,这意味着用户可以根据自己的PHP版本选择合适的.dll文件来启用或安装memcache扩展。用户只需要将这些.dll文件复制到PHP的安装目录下的“ext”子...

    php扩展开发文档

    本文档详细介绍了PHP扩展开发中的一些关键概念和技术细节,包括Zval操作、字符串处理、资源管理、第三方扩展封装以及一些实用的技巧。希望这些知识点能够帮助开发者更好地理解和掌握PHP扩展开发的技术栈。

    PHP扩展开发中文教程

    理解如何使用ZEND引擎提供的优化特性,如哈希表操作、快速路径执行、以及如何避免不必要的内存分配,都能显著提升PHP扩展的运行效率。例如,通过使用`ZEND_FAST_CALL`宏,可以在函数调用时减少开销。 此外,本书还...

    php队列+php-redis队列+php-redis扩展

    本文将详细探讨PHP中的队列技术,包括如何使用PHP-Redis扩展来实现高效的数据处理。 首先,PHP队列是PHP应用程序中用于处理大量任务的一种机制。它遵循先进先出(FIFO)原则,即最早进入队列的任务最先被处理。队列...

    php的python扩展

    通常,这样的文件会包含编译好的二进制文件、配置脚本、文档和其他必要的资源,使得用户能够在自己的PHP环境中安装和使用这个扩展。 在实际应用中,使用PHP的Python扩展(如ppython)可能涉及到以下几个核心知识点...

    php扩展yaf 3.3.3 for Windows-php7.3-php8.0-7.4.zip

    在Windows环境下,我们通常会使用PECL(PHP Extension Community Library)来安装PHP扩展。但Yaf不直接支持PECL安装,因此我们需要下载预编译好的dll文件。在"php扩展yaf 3.3.3 for Windows-php7.3-php8.0-7.4.zip"...

    memcached1.4.12的32,64版本以及php7的32,64扩展

    1. 调整Memcached的内存分配,以适应服务器资源和应用需求。 2. 使用适当的缓存策略,如设置过期时间,避免数据过期导致的无效请求。 3. 分散数据到多个Memcached实例,以实现负载均衡和高可用性。 4. 监控Memcached...

    PHP扩展安装与使用系列课程php测试源码.zip

    本课程聚焦于PHP扩展的安装与使用,通过"PHP扩展安装与使用系列课程php测试源码.zip"这个压缩包,我们可以深入学习和实践PHP扩展的相关知识。 首先,我们来了解一下什么是PHP扩展。PHP扩展是PHP语言的功能模块,...

    PHP扩展开发及内核应用

    《PHP扩展开发及内核应用》是一本深入探讨PHP扩展编程和PHP内核机制的书籍,基于Sara Golemon的《Extending and Embedding PHP》进行翻译和修订,主要面向那些希望深入了解PHP并可能想要为其开发自定义扩展的开发者...

    PHP安装SG11扩展下载

    3. **PHP中国社区**: 一些PHP爱好者和开发者在国内建立的社区论坛,如PHPChina,也会分享各种PHP扩展的下载链接和安装教程,你可以在这里寻找帮助。 4. **PaaS提供商**: 部分云服务提供商,如阿里云、腾讯云,可能...

    Thinkphp整合php5.5_phpRedis扩展安装使用教程内附说明和工具包

    本资源包含php5.5环境在windows和linux系统中安装phpredis扩展资源和文档,帮助你完成php程序对redis的支持,其中分别包含在linux和windows系统中如何一步步完成php_redis扩展安装的步骤和所需资源,最后我再结合...

    PHP 扩展说明与需要的文件

    1. **PHP 扩展.doc**:这份文档应该是PHP扩展的使用和安装指南,可能涵盖了如何启用或禁用扩展,如何编译自定义扩展,以及针对不同操作系统和PHP版本的兼容性信息。它可能还会提供有关配置文件`php.ini`中相关设置的...

    php sg11扩展 各个版本

    PHP Sg11扩展是PHP编程中用于处理图像和视频编码的一个重要组件,尤其是在处理H.264编码格式时非常关键。Sg11扩展提供了与FFmpeg库的接口,使得PHP开发者能够直接在PHP代码中进行多媒体处理,如视频转码、截图、流...

Global site tag (gtag.js) - Google Analytics