15.1 PHP Streams的本质

优质
小牛编辑
130浏览
2023-12-01

对于给定的流实例, 比如文件流和网络流, 它们的不同在于上一章你使用的流创建函数返回的php_stream结构体中的ops成员.

typedef struct _php_stream {
    ...
    php_stream_ops *ops;
    ...
} php_stream;

php_stream_ops结构体定义的是一个函数指针集合以及一个描述标记.

typedef struct _php_stream_ops {
    size_t (*write)(php_stream *stream, const char *buf,
                            size_t count TSRMLS_DC);
    size_t (*read)(php_stream *stream, char *buf,
                            size_t count TSRMLS_DC);
    int    (*close)(php_stream *stream, int close_handle
                            TSRMLS_DC);
    int    (*flush)(php_stream *stream TSRMLS_DC);

    const char *label;

    int (*seek)(php_stream *stream, off_t offset, int whence,
                            off_t *newoffset TSRMLS_DC);
    int (*cast)(php_stream *stream, int castas, void **ret
                            TSRMLS_DC);
    int (*stat)(php_stream *stream, php_stream_statbuf *ssb
                            TSRMLS_DC);
    int (*set_option)(php_stream *stream, int option,int value,
                            void *ptrparam TSRMLS_DC);
} php_stream_ops;

当流访问函数比如php_stream_read()被调用时, 流包装层实际上解析调用了stream->ops中对应的函数, 这样实际调用的就是当前流类型特有的read实现. 比如, 普通文件的流ops结构体中的read函数实现如下(实际的该实现比下面的示例复杂一点):

size_t php_stdio_read(php_stream *stream, char *buf,
                                size_t count TSRMLS_DC)
{
    php_stdio_stream_data *data =
                (php_stdio_stream_data*)stream->abstract;
    return read(data->fd, buf, count);
}

而compress.zlib流使用的ops结构体中则read则指向的是如下的函数:

size_t php_zlib_read(php_stream *stream, char *buf,
                                size_t count TSRMLS_DC)
{
    struct php_gz_stream_data_t *data =
            (struct php_gz_stream_data_t *) stream->abstract;

    return gzread(data->gz_file, buf, count);
}

这里第一点需要注意的是ops结构体指向的函数指针常常是对数据源真正的读取函数的一个瘦代理. 在上面两个例子中, 标准I/O流使用posix的read()函数, 而zlib流使用的是libz的gzread()函数.

你可能还注意到了, 这里使用了stream->abstract元素. 这是流实现的一个便利指针, 它可以被用于获取各种相关的捆绑信息. 在上面的例子中, 指向自定义结构体的指针, 用于存储底层read函数要使用的文件描述符.

还有一件你可能注意到的事情是php_stream_ops结构体中的每个函数都期望一个已有的流实例, 但是怎样得到实例呢? abstract成员是怎样设置的以及什么时候流指示使用哪个ops结构体? 答案就在你在上一章使用过的第一个打开流的函数(php_stream_open_wrapper())中.

当这个函数被调用时, php的流包装层尝试基于传递的URL中的scheme://部分确定请求的是什么协议. 这样它就可以在已注册的php包装器中查找对应的php_stream_wrapper项. 每个php_stream_wrapper结构体都可以取到自己的ops元素, 它指向一个php_stream_wrapper_ops结构体:

typedef struct _php_stream_wrapper_ops {
    php_stream *(*stream_opener)(php_stream_wrapper *wrapper,
                        char *filename, char *mode,
                        int options, char **opened_path,
                        php_stream_context *context
                        STREAMS_DC TSRMLS_DC);
    int (*stream_closer)(php_stream_wrapper *wrapper,
                        php_stream *stream TSRMLS_DC);
    int (*stream_stat)(php_stream_wrapper *wrapper,
                        php_stream *stream,
                        php_stream_statbuf *ssb
                        TSRMLS_DC);
    int (*url_stat)(php_stream_wrapper *wrapper,
                        char *url, int flags,
                        php_stream_statbuf *ssb,
                        php_stream_context *context
                        TSRMLS_DC);
    php_stream *(*dir_opener)(php_stream_wrapper *wrapper,
                        char *filename, char *mode,
                        int options, char **opened_path,
                        php_stream_context *context
                        STREAMS_DC TSRMLS_DC);

    const char *label;

    int (*unlink)(php_stream_wrapper *wrapper, char *url,
                        int options,
                        php_stream_context *context
                        TSRMLS_DC);

    int (*rename)(php_stream_wrapper *wrapper,
                        char *url_from, char *url_to,
                        int options,
                        php_stream_context *context
                        TSRMLS_DC);


    int (*stream_mkdir)(php_stream_wrapper *wrapper,
                        char *url, int mode, int options,
                        php_stream_context *context
                        TSRMLS_DC);
    int (*stream_rmdir)(php_stream_wrapper *wrapper, char *url,
                        int options,
                        php_stream_context *context
                        TSRMLS_DC);
} php_stream_wrapper_ops;

这里, 流包装层调用wrapper->ops->stream_opener(), 它将执行包装器特有的操作创建流实例, 赋值恰当的php_stream_ops结构体, 绑定相关的抽象数据.

dir_opener()函数和stream_opener()提供相同的基础服务; 不过, 它是对php_stream_opendir()这个API调用的响应, 并且通常会绑定一个不同的php_stream_ops结构体到返回的实例. stat()和close()函数在这一层上是重复的, 这样做是为了给包装器的这些操作增加协议特有的逻辑.

其他的函数则允许执行静态流操作而不用实际的创建流实例. 回顾这些流API调用, 它们并不实际返回php_stream对象, 你马上就会看到它们的细节.

尽管在php 4.3中引入流包装层时, url_stat在内部作为一个包装器的ops函数存在, 但直到php 5.0它才开始被使用. 此外, 最后的3个函数, rename(), stream_mkdir()以及stream_rmdir()一直到php 5.0才引入, 在这个版本之前, 它们并不在包装器的ops结构中.