[번역][php 확장 개발 및 삽입] 14장 - PHP에서 스트림에 접근

黄舟
풀어 주다: 2023-03-05 16:32:01
원래의
1527명이 탐색했습니다.


액세스 흐름

PHP 사용자 공간의 모든 파일 I/O 처리는 PHP를 통해 이루어집니다. 4.3에 도입된 PHP 스트림 래퍼. 내부적으로 확장 코드는 stdio 또는 posix 파일 처리를 사용하여 로컬 파일 시스템 또는 Berkeley 도메인 소켓과 통신하거나 사용자 공간 스트림 I/O와 동일한 API를 호출할 수 있습니다.

스트림 개요

일반적으로 직접 파일 설명자는 스트림 래퍼 계층 CPU 및 메모리를 호출하는 것보다 비용이 저렴합니다. 그러나 이는 특정 프로토콜을 구현하는 모든 작업을 확장 개발자에게 맡기게 됩니다. 스트림 래퍼 계층에 연결하면 확장 코드가 HTTP, FTP 및 그와 같은 다양한 내장 스트림 래퍼를 투명하게 사용할 수 있습니다. SSL 대응물은 물론, 특정 PEAR 또는 PECL 모듈을 포함함으로써 코드가 SSH2, WebDav, 심지어 Gopher와 같은 다른 프로토콜에도 액세스할 수 있습니다! 이 장에서는 스트림을 기반으로 내부적으로 작동하는 기본 API를 소개하고 나중에 16장 "흥미로운 스트림"에서 필터 적용, 상황별 옵션 및 매개 변수 사용 등과 같은 내용을 살펴보겠습니다.

스트림 열기

통합 API이지만 실제로는 필요한 스트림 유형에 따라 다르지만 스트림을 여는 데는 네 가지 경로가 있습니다. 사용자 공간 관점에서 볼 때 이 네 가지 범주는 다음과 같습니다(함수 목록은 전체 목록이 아닌 예만 나타냄).

<?php
    /* fopen包装
     * 操作文件/URI方式指定远程文件类资源 */
    $fp = fopen($url, $mode);
    $data = file_get_contents($url);
    file_put_contents($url, $data);
    $lines = file($url);

    /* 传输
     * 基于套接字的顺序I/O */
    $fp = fsockopen($host, $port);
    $fp = stream_socket_client($uri);
    $fp = stream_socket_server($uri, $options);

    /* 目录流 */
    $dir = opendir($url);
    $files = scandir($url);
    $obj = dir($url);

    /* "特殊"的流 */
    $fp = tmpfile();
    $fp = popen($cmd);
    proc_open($cmd, $pipes);
?>
로그인 후 복사

어떤 유형의 스트림을 열든 관계없이 모두 공통 구조 php_stream에 저장됩니다.

fopen 래퍼

먼저 fopen() 구현부터 시작합니다. 이제 확장 뼈대 생성에 익숙해졌을 것입니다. 그렇지 않다면 5장 "첫 번째 확장"으로 돌아가서 검토해 보세요. 이제 우리가 구현한 fopen() 함수는 다음과 같습니다.

PHP_FUNCTION(sample5_fopen)
{
    php_stream *stream;
    char *path, *mode;
    int path_len, mode_len;
    int options = ENFORCE_SAFE_MODE | REPORT_ERRORS;

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss",
            &path, &path_len, &mode, &mode_len) == FAILURE) {
        return;
    }
    stream = php_stream_open_wrapper(path, mode, options, NULL);
    if (!stream) {
        RETURN_FALSE;
    }
    php_stream_to_zval(stream, return_value);
}
로그인 후 복사

php_stream_open_wrapper()는 하위 계층을 완전히 우회하도록 설계되어야 합니다. 경로는 읽고 쓸 파일 이름이나 URL을 지정하며, 읽기 및 쓰기 동작은 모드 값에 따라 다릅니다. >options는 비트 필드의 플래그 값 모음이며, 여기서는 아래에 소개된 고정 값 세트로 설정됩니다.

USE_PATH

php.ini 파일의 include_path를 상대 경로

에 적용하면 내장 함수 fopen()이 세 번째로 지정하는 옵션이 설정됩니다. 매개변수가 TRUE인 경우

.

STREAM_USE_URL이 After 옵션을 설정하세요.

php://, file://, zlib://, bzip2://의 경우 이러한 URL 래퍼는 고려되지 않습니다.

이 상수의 이름은 다음과 같지만 실제로는 이 옵션을 설정합니다.

은 안전 모드를 활성화하기 위한 필수 확인 사항입니다(php.ini 파일의

safe_mode 지시어). . 이

옵션을 설정하지 않으면 safe_mode 검사가 건너뜁니다(INI 설정

). 에서 safe_mode를 설정하는 방법)

REPORT_ERRORS지정된 리소스를 여는 동안 오류가 발생한 경우 를 설정하면

이 옵션을 사용하면 오류 보고서가 생성됩니다.

STREAM_MUST_SEEK일부 스트림의 경우 , 소켓과 같은 탐색은 불가능합니다(무작위

액세스). 이러한 유형의 파일 핸들은 특정 상황에서만 사용할 수 있습니다

seek. 호출 범위가 이 옵션을 지정하고 래퍼

가 이를 보장할 수 없음을 감지한 경우 검색할 수 있으면

스트림 열기를 거부합니다.

STREAM_WILL_CAST 호출 범위에서 요구하는 경우 스트림을 stdio로 캐스팅할 수 있습니다.

posix 파일 설명자인 경우 이 옵션을 open_wrapper 함수에 전달해야 합니다

I/O 작업이 발생하기 전에 실패를 보장하기 위해

STREAM_ONLY_GET_HEADERS는 필요한 메타데이터만 식별합니다. 실제로 이것은

http 래퍼에 사용되며 http_response_headers 전역 변수를 가져옵니다

실제로 원격 파일 Content를 가져오지 않고

STREAM_DISABLE_OPEN_BASEDIR은 safe_mode 검사와 유사합니다. 이 옵션이 설정되지 않은 경우 <를 확인합니다. 🎜>

INI는 open_basedir을 설정합니다. 이 옵션을 지정하면

이 기본값을 무시할 수 있습니다. check

STREAM_OPEN_PERSISTENT 내부적으로 할당된 모든 공간이 영구적으로 할당되고 관련 리소스가 지속성 목록에 등록되었음을 스트림 래퍼 계층에 알립니다.

IGNORE_PATH지정하지 않으면 기본 포함 경로가 검색됩니다. 대부분의 URL

래퍼 모든 브라우저는 이를 무시합니다. 옵션입니다.

IGNORE_URL提供这个选项时, 流包装层只打开本地文件. 所

有的is_url包装器都将被忽略.

最后的NULL参数是char **类型, 它最初是用来设置匹配路径, 如果path指向普通文件URL, 则去掉file://部分, 保留直接的文件路径用于传统的文件名操作. 这个参数仅仅是以前引擎内部处理使用的.

此外, 还有php_stream_open_wrapper()的一个扩展版本:

php_stream *php_stream_open_wrapper_ex(char *path,
            char *mode, int options, char **opened_path,
            php_stream_context *context);
로그인 후 복사

最后一个参数context允许附加的控制, 并可以得到包装器内的通知. 你将在第16章看到这个参数的细节.

传输层包装

尽管传输流和fopen包装流是相同的组件组成的, 但它的注册策略和其他的流不同. 从某种程度上来说, 这是因为用户空间对它们的访问方式的不同造成的, 它们需要实现基于套接字的其他因子.

从扩展开发者角度来看, 打开传输流的过程是相同的. 下面是对fsockopen()的实现:

PHP_FUNCTION(sample5_fsockopen)
{
    php_stream *stream;
    char *host, *transport, *errstr = NULL;
    int host_len, transport_len, implicit_tcp = 1, errcode = 0;
    long port = 0;
    int options = ENFORCE_SAFE_MODE;
int flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l",
                        &host, &host_len, &port) == FAILURE) {
        return;
    }
    if (port) {
        int implicit_tcp = 1;
        if (strstr(host, "://")) {
                /* A protocol was specified,
                 * no need to fall back on tcp:// */
            implicit_tcp = 0;
        }
        transport_len = spprintf(&transport, 0, "%s%s:%d",
                implicit_tcp ? "tcp://" : "", host, port);
    } else {
        /* When port isn&#39;t specified
         * we can safely assume that a protocol was
         * (e.g. unix:// or udg://) */
        transport = host;
        transport_len = host_len;
    }
    stream = php_stream_xport_create(transport, transport_len,
                              options, flags,
                              NULL, NULL, NULL, &errstr, &errcode);
    if (transport != host) {
        efree(transport);
    }
    if (errstr) {
        php_error_docref(NULL TSRMLS_CC, E_WARNING, "[%d] %s",
                                errcode, errstr);
        efree(errstr);
    }
    if (!stream) {
        RETURN_FALSE;
    }
    php_stream_to_zval(stream, return_value);
}
로그인 후 복사

这个函数的基础构造和前面的fopen示例是一样的. 不同在于host和端口号使用不同的参数指定, 接着为了给出一个传输流URL就必须将它们合并到一起. 在产生了一个有意义的"路径:后, 将它传递给php_stream_xport_create()函数, 方式和fopen()使用的php_stream_open_wrapper()API一样. php_stream_xport_create()的原型如下:

php_stream *php_stream_xport_create(char *xport, int xport_len,
                    int options, int flags,
                    const char *persistent_id,
                                     struct timeval *timeout,
                                     php_stream_context *context,
                                     char **errstr, int *errcode);
로그인 후 복사

每个参数的含义如下:

xport基于URI的传输描述符. 对于基于inet的套接字流, 它可以是

tcp://127.0.0.1:80, udp://10.0.0.1:53, ssl://169.254.13.24:445等. 此

外, UNIX域传输协议unix:///path/to/socket,

udg:///path/to/dgramsocket等都是合法的. xport_len指定了xport的长

度, 因此xport是二进制安全的.

options这个值是由前面php_stream_open_wrapper()中介绍的选项通过按位

或组成的值.

flags由STREAM_XPORT_CLIENT或STREAM_XPORT_SERVER之一

与下面另外一张表中将列出的STREAM_XPORT_*常量通过按位或

组合得到的值.

persist_id요청된 전송 스트림이 요청 간에 유지되어야 하는 경우 호출 범위는

키를 제공할 수 있습니다. 연결을 설명하는 이름입니다. 이 값을 NULL로 지정하면 비지속적 연결이 생성됩니다. 고유한 문자열 값을 지정하면 먼저 지속성 풀에서 조회를 시도합니다. 기존 전송 스트림이 있거나

이 없는 경우

발견되면 새 지속성 스트림을 생성합니다.

시간 초과 이 값을 NULL로 전달하면

가 php.ini 시간 초과 값에 지정된 기본값을 사용하게 됩니다. 이 매개변수는 서버 측 전송 스트림에 의미가 없습니다.

errstr선택한 소켓에서 생성, 연결, 바인딩 또는 수신 대기하는 경우 오류가 발생하면 여기에 전달된 char * 참조 값

은 처음에 오류 이유를 설명하는 문자열로 설정됩니다.

은 처음에 NULL을 가리켜야 합니다. 반환될 때 값으로 설정되면 호출 범위에는

이 문자열과 관련된 메모리를 해제할 책임이 있습니다.

errcodeerrstr이 반환한 오류 메시지에 해당하는 숫자 오류 코드.

php_stream_xport_create의 플래그 매개 변수에 사용되는 STREAM_XPORT_* 상수 계열( )은 다음과 같이 정의됩니다:

STREAM_XPORT_CLIENT

로컬 끝은 전송 계층을 통해 원격 리소스와의 연결을 설정합니다.

태그는 일반적으로 STREAM_XPORT_CONNECT 또는

STREAM_XPORT_CONNECT_ASYNC

와 함께 사용됩니다. .

STREAM_XPORT_SERVER를 사용합니다. 로컬 측에서는 일반적으로 전송 계층을 통한 연결을 허용합니다.

은 STREAM_XPORT_BIND 및

STREAM_XPORT_LISTEN

STREAM_XPORT_CONNECT<와 함께 사용됩니다. 🎜>은 원격 리소스 연결 설정이 전송 스트림 생성의 일부임을 설명하는 데 사용됩니다.

, 그러나 그렇게 하려면

php_stream_xport_connect().를 수동으로 호출해야 합니다.

STREAM_XPORT_CONNECT_ASYNC尝试连接到远程资源, 但不阻塞.

STREAM_XPORT_BIND将传输流绑定到本地资源. 用在服务端传输流时,

这将使得accept连接的传输流准备端口, 路径或

特定的端点标识符等信息.

STREAM_XPORT_LISTEN在已绑定的传输流端点上监听到来的连接. 这通

常用于基于流的传输协议, 比如: tcp://, ssl://,

unix://.

目录访问

fopen包装器支持目录访问, 比如file://和ftp://, 还有第三种流打开函数也可以用于目录访问, 下面是对opendir()的实现:

PHP_FUNCTION(sample5_opendir)
{
    php_stream *stream;
    char *path;
    int path_len, options = ENFORCE_SAFE_MODE | REPORT_ERRORS;
    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s",
                                   &path, &path_len) == FAILURE) {
        return;
    }
    stream = php_stream_opendir(path, options, NULL);
    if (!stream) {
        RETURN_FALSE;
    }
    php_stream_to_zval(stream, return_value);
}
로그인 후 복사

同样的, 也可以为某个特定目录打开一个流, 比如本地文件系统的目录名或支持目录访问的URL格式资源. 这里我们又看到了options参数, 它和原来的含义一样, 第三个参数NULL原型是php_stream_context类型.

在目录流打开后, 和文件以及传输流一样, 返回给用户空间.

特殊流

还有一些特殊类型的流不能归类到fopen/transport/directory中. 它们中每一个都有自己独有的API:

php_stream *php_stream_fopen_tmpfile(void);
php_stream *php_stream_fopen_temporary_file(const char *dir,
                            const char *pfx, char **opened_path);
로그인 후 복사

创建一个可seek的缓冲区流用于读写. 在关闭时, 这个流使用的所有临时资源, 包括所有的缓冲区(无论是在内存还是磁盘), 都将被释放. 使用这一组API中的后一个函数, 允许临时文件被以特定的格式命名放到指定路径. 这些内部API调用被用户空间的tmpfile()函数隐藏.

php_stream *php_stream_fopen_from_fd(int fd,
                const char *mode, const char *persistent_id);
php_stream *php_stream_fopen_from_file(FILE *file,
                const char *mode);
php_stream *php_stream_fopen_from_pipe(FILE *file,
            const char *mode);
로그인 후 복사

这3个API方法接受已经打开的FILE *资源或文件描述符ID, 使用流API的某种操作包装. fd格式的接口不会搜索匹配你前面看到过的fopen函数打开的资源, 但是它会注册持久化的资源, 后续的fopen可以使用到这个持久化资源.

访问流

在你打开一个流之后, 就可以在它上面执行I/O操作了. 使用那种协议包装API创建了流并不重要, 它们都使用相同的访问API.

流的读写可以使用下面的API函数组合完成, 它们多数都是遵循POSIX I/O中对应的API规范的:

int php_stream_getc(php_stream *stream);
로그인 후 복사

从数据流中接收一个字符. 如果流上再没有数据, 则返回EOF.

size_t php_stream_read(php_stream *stream, char *buf, size_t count);
로그인 후 복사

从指定流中读取指定字节的数据. buf必须预分配至少count字节的内存空间. 这个函数将返回从数据流实际读到缓冲区中的数据字节数.

php_stream_read()不同于其他的流读取函数. 如果使用的流不是普通文件流, 哪怕数据流中有超过请求字节数的数据, 并且当前也可以返回, 它也只会调用过一次底层流实现的read函数. 这是为了兼容基于包(比如UDP)的协议的这种做法.

char *php_stream_get_line(php_stream *stream, char *buf,
                             size_t maxlen, size_t *returned_len);
char *php_stream_gets(php_stream *stream, char *buf,
                             size_t maxlen);
로그인 후 복사

这两个函数从stream中读取最多maxlen个字符, 直到碰到换行符或流结束. buf可以是一个指向预分配的至少maxlen字节的内存空间的指针, 也可以是NULL, 当它是NULL时, 则会自动的创建一个动态大小的缓冲区, 用从流中实际读出的数据填充, 成功后函数返回指向缓冲区的指针, 失败则返回NULL. 如果returned_len传递了非NULL值, 则在返回时它将被设置为实际从流中读取的字节数.

char *php_stream_get_record(php_stream *stream,
        size_t maxlen, size_t *returned_len,
        char *delim, size_t delim_len
        TSRMLS_DC);
로그인 후 복사

和php_stream_get_line()类似, 这个函数将读取最多maxlen, 或到达EOF/行结束第一次出现的位置. 但是它也有和php_stream_get_line()的不同指出, 这个函数允许指定任意的停止读取标记.

读取目录项

从php流中读取目录项和上面从普通文件中读取普通数据相同. 这些数据放到了固定大小的dirents块中. 内部的php_stream_dirent结构体如下, 它与POSIX定义的dirent结构体一致:

typedef struct _php_stream_dirent {
    char d_name[MAXPATHLEN];
} php_stream_dirent;
로그인 후 복사

实际上你可以直接使用php_stream_read()函数读取数据到这个结构体中:

{
   struct dirent entry;
   if (php_stream_read(stream, (char*)&entry, sizeof(entry))
                               == sizeof(entry)) {
       /* 成功从目录流中读取到一项 */
       php_printf("File: %s\n", entry.d_name);
   }
}
로그인 후 복사

由于从目录流中读取是很常见的操作, php流包装层暴露了一个API, 它将记录大小的检查和类型转换处理封装到了一次调用中:

php_stream_dirent *php_stream_readdir(php_stream *dirstream,
                            php_stream_dirent *entry);
로그인 후 복사

如果成功读取到目录项, 则传入的entry指针将被返回, 否则返回NULL标识错误. 使用这个为目录流特殊构建的函数而不是直接从目录流读取非常重要, 这样做未来流API改变时就不至于和你的代码冲突.

和读类似, 向流中写数据只需要传递一个缓冲区和缓冲区长度给流.

size_t php_stream_write(php_stream *stream, char *buf,
                                                         size_t count);
size_t php_stream_write_string(php_stream *stream, char *stf);
로그인 후 복사

write_string的版本实际上是一个提供便利的宏, 它允许写一个NULL终止的字符串,而不用显式的提供长度. 返回的是实际写到流中的字节数. 要特别小心的是尝试写大数据的时候可能导致流阻塞, 比如套接字流, 而如果流被标记为非阻塞, 则实际写入的数据量可能会小于传递给函数的期望大小.

int php_stream_putc(php_stream *stream, int c);
int php_stream_puts(php_string *stream, char *buf);
로그인 후 복사

还有一种选择是, 使用php_stream_putc()和php_stream_puts()写入一个字符或一个字符串到流中. 要注意, php_stream_puts()不同于php_stream_write_string(), 虽然它们的原型看起来是一样的, 但是php_stream_puts()会在写出buf中的数据后自动的追加一个换行符.

size_t php_stream_printf(php_stream *stream TSRMLS_DC,
                                        const char *format, ...);
로그인 후 복사

功能和格式上都类似于fprintf(), 这个API调用允许在写的同时构造字符串而不用去创建临时缓冲区构造数据. 这里我们能够看到的一个明显的不同是它需要TSRMLS_CC宏来保证线程安全.

随机访问, 查看文件偏移量以及缓存的flush

基于文件的流, 以及另外几种流是可以随机访问的. 也就是说, 在流的一个位置读取了一些数据之后, 文件指针可以向前或向后移动, 以非线性顺序读取其他部分.

如果你的流应用代码预测到底层的流支持随机访问, 在打开的时候就应该传递STREAM_MUST_SEEK选项. 对于那些原本就可随机访问的流来说, 这通常不会有什么影响, 因为流本身就是可随机访问的. 而对于那些原本不可随机访问的流, 比如网络I/O或线性访问文件比如FIFO管道, 这个暗示可以让调用程序有机会在流的数据被消耗掉之前, 优雅的失败.

在可随机访问的流资源上工作时, 下面的函数可用来将文件指针移动到任意位置:

int php_stream_seek(php_stream *stream, off_t offset, int whence);
int php_stream_rewind(php_stream *stream);
로그인 후 복사

offset是相对于whence表示的流位置的偏移字节数, whence的可选值及含义如下:

SEEK_SEToffset相对于文件开始位置. php_stream_rewind()API调用实际上是一个宏,

展开后是php_stream_seek(stream, 0, SEEK_SET), 表示移动到文件开始

位置偏移0字节处. 当使用SEEK_SET时, 如果offset传递负值被认为是错误

的, 将会导致未定义行为. 指定的位置超过流的末尾也是未定义的, 不过结果

通常是一个错误或文件被扩大以满足指定的偏移量.

SEEK_CURoffset相对于文件的当前偏移量. 调用php_stream_seek(steram, offset,

SEEK_CUR)一般来说等价于php_stream_seek(stream, php_stream_tell()

+ offset, SEEK_SET);

SEEK_ENDoffset是相对于当前的EOF位置的. 负值的offset表示在EOF之前的位置, 正

值和SEEK_SET中描述的是相同的语义, 可能在某些流实现上可以工作.

int php_stream_rewinddir(php_stream *dirstream);
로그인 후 복사

在目录流上随机访问时, 只有php_stream_rewinddir()函数可用. 使用php_stream_seek()函数将导致未定义行为. 所有的随机访问一族函数返回0标识成功或者-1标识失败.

off_t php_stream_tell(php_stream *stream);
로그인 후 복사

如你之前所见, php_stream_tell()将返回当前的文件偏移量.

int php_stream_flush(php_stream *stream);
로그인 후 복사

调用flush()函数将强制将流过滤器此类内部缓冲区中的数据输出到最终的资源中. 在流被关闭时, flush()函数将自动调用, 并且大多数无过滤流资源虽然不进行任何内部缓冲, 但也需要flush. 显式的调用这个函数很少见, 并且通常也是不需要的.

int php_stream_stat(php_stream *stream, php_stream_statbuf *ssb);
로그인 후 복사

调用php_stream_stat()可以获取到流实例的其他信息, 它的行为类似于fstat()函数. 实际上, php_stream_statbuf结构体现在仅包含一个元素: struct statbuf sb; 因此, php_stream_stat()调用可以如下面例子一样, 直接用传统的fstat()操作替代, 它只是将posix的stat操作翻译成流兼容的:

int php_sample4_fd_is_fifo(int fd)
{
    struct statbuf sb;
    fstat(fd, &sb);
    return S_ISFIFO(sb.st_mode);
}
int php_sample4_stream_is_fifo(php_stream *stream)
{
    php_stream_statbuf ssb;
    php_stream_stat(stream, &ssb);
    return S_ISFIFO(ssb.sb.st_mode);
}
로그인 후 복사

关闭

所有流的关闭都是通过php_stream_free()函数处理的, 它的原型如下:

int php_stream_free(php_stream *stream, int options);
로그인 후 복사

这个函数中的options参数允许的值是PHP_STREAM_FREE_xxx一族常量的按位或的结果, 这一族常量定义如下(下面省略PHP_STREAM_FREE_前缀):

CALL_DTOR流实现的析构器应该被调用. 这里提供了一个时机对特定的流

进行显式释放.

RELEASE_STREAM释放为php_stream结构体分配的内存

PRESERVE_HANDLE指示流的析构器不要关闭它的底层描述符句柄

RSRC_DTOR流包装层内部管理资源列表的垃圾回收

PERSISTENT作用在持久化流上时, 它的行为将是永久的而不局限于当前请

求.

CLOSECALL_DTOR和RELEASE_STREAM的联合. 这是关闭

非持久化流的一般选项.

CLOSE_CASTEDCLOSE和PRESERVE_HANDLE的联合.

CLOSE_PERSISTENTCLOSE和PERSISTENT的联合. 这是永久关闭持久化流的一

般选项.

实际上, 你并不需要直接调用php_stream_free()函数. 而是在关闭流时使用下面两个宏的某个替代:

#define php_stream_close(stream) \
    php_stream_free((stream), PHP_STREAM_FREE_CLOSE)
#define php_stream_pclose(stream) \
    php_stream_free((stream), PHP_STREAM_FREE_CLOSE_PERSISTENT)
로그인 후 복사

通过zval交换流

因为流通常映射到zval上, 反之亦然, 因此提供了一组宏用来简化操作, 并统一编码(格式):

#define php_stream_to_zval(stream, pzval) \
    ZVAL_RESOURCE((pzval), (stream)->rsrc_id);
로그인 후 복사

要注意, 这里并没有调用ZEND_REGISTER_RESOURCE(). 这是因为当流打开的时候, 已经自动的注册为资源了, 这样就可以利用到引擎内建的垃圾回收和shutdown系统的优点. 使用这个宏而不是尝试手动的将流注册为新的资源ID是非常重要的; 这样做的最终结果是导致流被关闭两次以及引擎崩溃.

#define php_stream_from_zval(stream, ppzval) \
    ZEND_FETCH_RESOURCE2((stream), php_stream*, (ppzval), \
    -1, "stream", php_file_le_stream(), php_file_le_pstream())
#define php_stream_from_zval_no_verify(stream, ppzval) \
    (stream) = (php_stream*)zend_fetch_resource((ppzval) \
    TSRMLS_CC, -1, "stream", NULL, 2, \
    php_file_le_stream(), php_file_le_pstream())
로그인 후 복사

从传入的zval *中取回php_stream *有一个类似的宏. 可以看出, 这个宏只是对资源获取函数(第9章"资源数据类型")的一个简单封装. 请回顾ZEND_FETCH_RESOURCE2()宏, 第一个宏php_stream_from_zval()就是对它的包装, 如果资源类型不匹配, 它将抛出一个警告并尝试从函数实现中返回. 如果你只是想从传入的zval *中获取一个php_stream *, 而不希望有自动的错误处理, 就需要使用php_stream_from_zval_no_verify()并且需要手动的检查结果值.

静态资源操作

一个基于流的原子操作并不需要实际的实例. 下面这些API仅仅使用URL执行这样的操作:

int php_stream_stat_path(char *path, php_stream_statbuf *ssb);
로그인 후 복사

和前面的php_stream_stat()类似, 这个函数提供了一个对POSIX的stat()函数协议依赖的包装. 要注意, 并不是所有的协议都支持URL记法, 并且即便支持也可能不能报告出statbuf结构体中的所有成员值. 一定要检查php_stream_stat_path()失败时的返回值, 0标识成功, 要知道, 不支持的元素返回时其值将是默认的0.

int php_stream_stat_path_ex(char *path, int flags,
        php_stream_statbuf *ssb, php_stream_context *context);
로그인 후 복사

这个php_stream_url_stat()的扩展版本允许传递另外两个参数. 第一个是flags, 它的值可以是下面的PHP_STERAM_URL_STAT_*(下面省略PHP_STREAM_URL_STAT_前缀)一族常量的按位或的结果. 还有一个是context参数, 它在其他的一些流函数中也有出现, 我们将在第16章去详细学习.

LINK原始的php_stream_stat_path()对于符号链接或目录将会进行解析直到碰到

协议定义的结束资源. 传递PHP_STREAM_URL_STAT_LINK标记将导致

php_stream_stat_path()返回请求资源的信息而不会进行符号链接的解析.

(译注: 我们可以这样理解, 没有这个标记, 底层使用stat(), 如果有这个标记,

底层使用lstat(), 关于stat()和lstat()的区别, 请查看*nix手册)

QUIET默认情况下, 如果在执行URL的stat操作过程中碰到错误, 包括文件未找到错

误, 都将通过php的错误处理机制触发. 传递QUIET标记可以使得

php_stream_stat_path()返回而不报告错误.

int php_stream_mkdir(char *path, int mode, int options,
                            php_stream_context *context);
int php_stream_rmdir(char *path, int options,
                            php_stream_context *context);
로그인 후 복사

创建和删除目录也会如你期望的工作. 这里的options参数和前面的php_stream_open_wrapper()函数的同名参数含义一致. 对于php_stream_mkdir(), 还有一个参数mode用于指定一个八进制的值表明读写执行权限.

小结

本章中你接触了一些基于流的I/O的内部表象. 下一章将演示做呢样实现自己的协议包装, 甚至是定义自己的流类型.

以上就是[翻译][php扩展开发和嵌入式]第14章-php中流的访问的内容,更多相关内容请关注PHP中文网(www.php.cn)!


관련 라벨:
php
원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿