[Traduction][Développement et intégration d'extensions php] Chapitre 14 - Accès aux flux en php

黄舟
Libérer: 2023-03-05 16:32:01
original
1499 Les gens l'ont consulté


Flux d'accès

Tous les traitements d'E/S de fichiers dans l'espace utilisateur PHP se font via php Traité par le wrapper de flux PHP introduit dans la version 4.3. En interne, le code d'extension peut choisir d'utiliser le traitement de fichiers stdio ou posix pour communiquer avec le système de fichiers local ou le socket de domaine Berkeley, ou il peut appeler la même API que le flux d'espace utilisateur I/O.

Aperçu des flux

En général, les descripteurs de fichiers directs sont moins coûteux que d'appeler le processeur et la mémoire de la couche wrapper de flux ; cependant, cela vous impose tout le travail d'implémentation d'un protocole spécifique en tant que développeur d'extension. En vous connectant à la couche wrapper de flux, votre code d'extension peut utiliser de manière transparente les différents wrappers de flux intégrés, tels que HTTP, FTP et leurs. Homologues SSL, ainsi que les wrappers de compression gzip et bzip2 En incluant des modules PEAR ou PECL spécifiques, votre code peut également accéder à d'autres protocoles, tels que SSH2, WebDav et même Gopher ! Ce chapitre présentera l'API de base qui fonctionne en interne sur la base des flux. Plus loin dans le chapitre 16 "Flux intéressants", nous verrons des choses comme l'application de filtres, l'utilisation d'options et de paramètres contextuels, etc. Concepts avancés.

Ouvrir un flux

Bien qu'il s'agisse d'une API unifiée, cela dépend en fait des types de flux requis, il existe quatre chemins différents pour ouvrir un flux. Du point de vue de l'espace utilisateur, ces quatre catégories différentes sont les suivantes (la liste des fonctions ne représente que des exemples, pas une liste complète) :

Peu importe le type de flux que vous ouvrez , ils sont tous stockés dans une structure commune php_stream.

<?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);
?>
Copier après la connexion

wopen wrapper

Nous commençons d'abord par implémenter le fopen (). Vous devriez maintenant être familier avec la création de squelettes d'extension. Sinon, veuillez revenir au chapitre 5 « Votre première extension » « Pour revoir, voici la fonction fopen() que nous avons implémentée :

<🎜. >

Le but de php_stream_open_wrapper() devrait être de contourner complètement la couche inférieure. path spécifie le nom du fichier ou l'URL à lire et à écrire, le comportement de lecture et d'écriture dépend de la valeur du mode .

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);
}
Copier après la connexion

options est une collection de valeurs de balise dans le champ de bits, ici elle est définie sur un ensemble de valeurs fixes présentées ci-dessous :

USE_PATHAppliquez le include_path dans le fichier php.ini au chemin relatif

Fonction intégrée fopen() Cette option sera. défini lors de la spécification du troisième paramètre comme TRUE

.

STREAM_USE_URLAprès avoir défini cette option , seules les URL distantes seront ouvertes pour

php://, file://, zlib://, bzip2 ://Ces wrappers d'URL le font. ne pas

les considérer comme des URL distantes.

ENFORCE_SAFE_MODE Bien que cette constante soit nommée de cette façon, il définit réellement cette option

est juste une vérification obligatoire pour activer le mode sans échec (la directive

safe_mode dans le fichier php.ini) . Si vous ne définissez pas cette option

, la vérification du mode sécurisé sera ignorée (quel que soit le paramètre INI

. Comment définir le mode sécurisé dans )

REPORT_ERRORSLorsqu'une erreur est rencontrée lors de l'ouverture de la ressource spécifiée, si vous définissez

Si cette option est utilisée, un rapport d'erreur sera généré.

STREAM_MUST_SEEKPour certains flux , comme les sockets, ce n'est pas une recherche possible (accès aléatoire

Ce type de descripteur de fichier ne peut être utilisé que dans des circonstances spécifiques <🎜) ; >

seek Si la portée appelante spécifie cette option et que le wrapper

détecte qu'il ne peut pas garantir cela. il peut chercher, il refusera d'ouvrir les

streams.

STREAM_WILL_CASTLe Le flux peut être converti en stdio si la portée appelante l'exige ou

descripteur de fichier posix, vous devez transmettre cette option à la fonction open_wrapper

, pour garantir l'échec avant que l'opération d'E/S ne se produise

STREAM_ONLY_GET_HEADERS identifie uniquement les métadonnées qui doivent être demandé à partir du flux. En fait, ceci est utilisé pour

le wrapper http, obtient la variable globale http_response_headers

<. 🎜> sans réellement récupérer le contenu du fichier distant.

STREAM_DISABLE_OPEN_BASEDIR

est similaire à la vérification safe_mode Si cette option n'est pas définie, elle vérifiera <. 🎜>

INI définit open_basedir Si vous spécifiez cette option, vous pouvez

contourner cette valeur par défaut. checkSTREAM_OPEN_PERSISTENT

Informe la couche wrapper de flux que tout l'espace alloué en interne est alloué de manière persistante et que les ressources associées sont enregistrées dans la liste de persistance.IGNORE_PATH

S'il n'est pas spécifié, le chemin d'inclusion par défaut est recherché. La plupart des URL

wrapper Tous les navigateurs l'ignorent. option.

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);
Copier après la connexion

最后一个参数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);
}
Copier après la connexion

这个函数的基础构造和前面的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);
Copier après la connexion

每个参数的含义如下:

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_*常量通过按位或

组合得到的值.

persistent_idSi le flux de transport demandé doit être conservé entre les requêtes, la portée appelante peut fournir une

clé Nom décrivant la connexion. Spécifier cette valeur comme NULL crée une connexion non persistante ; la spécification d'une valeur de chaîne unique tentera d'abord de rechercher dans le pool de persistance s'il existe un flux de transport existant ou s'il n'y a pas de

.

, créez un nouveau flux de persistance une fois trouvé

délai d'expiration Combien de temps faut-il tenter une connexion avant d'échouer avec un. timeout. Passer cette valeur comme NULL oblige

à utiliser la valeur par défaut spécifiée dans php.ini Timeout. Ce paramètre n'a aucune signification pour les flux de transport côté serveur.

errstrEn cas de création, de connexion, de liaison ou d'écoute sur le socket sélectionné Une erreur se produit, la valeur de référence char * passée ici

sera défini sur une chaîne décrivant la raison de l'erreur. errstr initialement

devrait initialement pointer vers NULL ; elle est définie sur une valeur lorsqu'elle est renvoyée, la portée appelante a

Responsable de libérer la mémoire liée à cette chaîne.

errcodeLe code d'erreur numérique correspondant au message d'erreur renvoyé par errstr.

La famille de constantes STREAM_XPORT_* utilisée dans le paramètre flags de php_stream_xport_create() sont définis comme suit :

STREAM_XPORT_CLIENT

L'extrémité locale établira une connexion avec la ressource distante via la couche de transport . La balise

est généralement utilisée en conjonction avec STREAM_XPORT_CONNECT ou

STREAM_XPORT_CONNECT_ASYNC

Utilisez .STREAM_XPORT_SERVER

Le côté local acceptera la connexion via la couche de transport. Cette balise est généralement <🎜. >

est utilisé avec STREAM_XPORT_BIND et

STREAM_XPORT_LISTEN.

STREAM_XPORT_CONNECT

est utilisé pour illustrer que l'établissement d'une connexion à une ressource distante fait partie de la création du flux de transport. Il est légal d'omettre cette balise

, mais cela nécessite d'appeler manuellement

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);
}
Copier après la connexion

同样的, 也可以为某个特定目录打开一个流, 比如本地文件系统的目录名或支持目录访问的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);
Copier après la connexion

创建一个可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);
Copier après la connexion

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

访问流

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

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

int php_stream_getc(php_stream *stream);
Copier après la connexion

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

size_t php_stream_read(php_stream *stream, char *buf, size_t count);
Copier après la connexion

从指定流中读取指定字节的数据. 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);
Copier après la connexion

这两个函数从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);
Copier après la connexion

和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;
Copier après la connexion

实际上你可以直接使用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);
   }
}
Copier après la connexion

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

php_stream_dirent *php_stream_readdir(php_stream *dirstream,
                            php_stream_dirent *entry);
Copier après la connexion

如果成功读取到目录项, 则传入的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);
Copier après la connexion

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

int php_stream_putc(php_stream *stream, int c);
int php_stream_puts(php_string *stream, char *buf);
Copier après la connexion

还有一种选择是, 使用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, ...);
Copier après la connexion

功能和格式上都类似于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);
Copier après la connexion

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);
Copier après la connexion

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

off_t php_stream_tell(php_stream *stream);
Copier après la connexion

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

int php_stream_flush(php_stream *stream);
Copier après la connexion

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

int php_stream_stat(php_stream *stream, php_stream_statbuf *ssb);
Copier après la connexion

调用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);
}
Copier après la connexion

关闭

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

int php_stream_free(php_stream *stream, int options);
Copier après la connexion

这个函数中的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)
Copier après la connexion

通过zval交换流

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

#define php_stream_to_zval(stream, pzval) \
    ZVAL_RESOURCE((pzval), (stream)->rsrc_id);
Copier après la connexion

要注意, 这里并没有调用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())
Copier après la connexion

从传入的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);
Copier après la connexion

和前面的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);
Copier après la connexion

这个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);
Copier après la connexion

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

小结

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

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


Étiquettes associées:
php
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal
À propos de nous Clause de non-responsabilité Sitemap
Site Web PHP chinois:Formation PHP en ligne sur le bien-être public,Aidez les apprenants PHP à grandir rapidement!