14.1 流的概览
本章将介绍内部基于流工作的基础API. 后面到第16章”有趣的流”中, 我们将看到诸如应用过滤器, 使用上下文选项和参数等高级概念.
尽管是一个统一的API, 但实际上依赖于所需的流的类型, 有四种不同的路径去打开一个流. 从用户空间角度来看, 这四种不同的类别如下(函数列表只代表示例, 不是完整列表):
无论你打开的是什么类型的流, 它们都存储在一个公共的结构体php_stream中.
fopen包装
我们首先从实现fopen()函数开始. 现在你应该已经对创建扩展骨架很熟悉了, 如果还不熟悉, 请回到第5章”你的第一个扩展”复习一下, 下面是我们实现的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()的目的应该是完全绕过底层. path指定要读写文件名或URL, 读写行为依赖于mode的值.
最后的NULL参数是char **类型, 它最初是用来设置匹配路径, 如果path指向普通文件URL, 则去掉file://部分, 保留直接的文件路径用于传统的文件名操作. 这个参数仅仅是以前引擎内部处理使用的.
此外, 还有php_stream_open_wrapper()的一个扩展版本:
最后一个参数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 =
int flags = STREAM_XPORT_CLIENT | STREAM_XPORT_CONNECT;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|l",
&host, &host_len, &port) == FAILURE) {
}
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'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) {
efree(errstr);
}
if (!stream) {
RETURN_FALSE;
}
php_stream_to_zval(stream, return_value);
}
每个参数的含义如下:
目录访问
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_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可以使用到这个持久化资源.
links
- 14 流式访问