访问流

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

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

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

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

    1. char *php_stream_get_line(php_stream *stream, char *buf, size_t maxlen, size_t *returned_len);
    2. char *php_stream_gets(php_stream *stream, char *buf, size_t maxlen);

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

    1. 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结构体一致:

    1. typedef struct _php_stream_dirent {
    2. char d_name[MAXPATHLEN];
    3. } php_stream_dirent;

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

    1. {
    2. struct dirent entry;
    3. if (php_stream_read(stream, (char*)&entry, sizeof(entry)) == sizeof(entry)) {
    4. /* 成功从目录流中读取到一项 */
    5. }
    6. }

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

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

    1. size_t php_stream_write(php_stream *stream, char *buf, size_t count);
    2. size_t php_stream_write_string(php_stream *stream, char *stf);

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

    1. 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中的数据后自动的追加一个换行符.

    1. size_t php_stream_printf(php_stream *stream TSRMLS_DC, const char *format, ...);

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

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

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

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

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

    1. int php_stream_seek(php_stream *stream, off_t offset, int whence);
    2. int php_stream_rewind(php_stream *stream);
    1. int php_stream_rewinddir(php_stream *dirstream);

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

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

    1. int php_stream_flush(php_stream *stream);

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

    1. 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操作翻译成流兼容的:

    1. int php_sample4_fd_is_fifo(int fd)
    2. {
    3. struct statbuf sb;
    4. fstat(fd, &sb);
    5. return S_ISFIFO(sb.st_mode);
    6. }
    7. {
    8. php_stream_stat(stream, &ssb);
    9. return S_ISFIFO(ssb.sb.st_mode);
    10. }

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

    1. int php_stream_free(php_stream *stream, int options);

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

    CALL_DTOR 流实现的析构器应该被调用. 这里提供了一个时机对特定的流进行显式释放.
    RELEASE_STREAM 释放为php_stream结构体分配的内存
    PRESERVE_HANDLE 指示流的析构器不要关闭它的底层描述符句柄
    RSRC_DTOR 流包装层内部管理资源列表的垃圾回收
    PERSISTENT 作用在持久化流上时, 它的行为将是永久的而不局限于当前请求.
    CLOSE CALL_DTOR和RELEASE_STREAM的联合. 这是关闭非持久化流的一般选项
    CLOSE_CASTED CLOSE和PRESERVE_HANDLE的联合.
    CLOSE_PERSISTENT CLOSE和PERSISTENT的联合. 这是永久关闭持久化流的一般选项.

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

    1. #define php_stream_close(stream) \
    2. php_stream_free((stream), PHP_STREAM_FREE_CLOSE)
    3. #define php_stream_pclose(stream) \
    4. php_stream_free((stream), PHP_STREAM_FREE_CLOSE_PERSISTENT)

    通过zval交换流

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

    1. #define php_stream_from_zval(stream, ppzval) \
    2. ZEND_FETCH_RESOURCE2((stream), php_stream*, (ppzval), \
    3. -1, "stream", php_file_le_stream(), php_file_le_pstream())
    4. #define php_stream_from_zval_no_verify(stream, ppzval) \
    5. (stream) = (php_stream*)zend_fetch_resource((ppzval) \
    6. TSRMLS_CC, -1, "stream", NULL, 2, \
    7. 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()并且需要手动的检查结果值.