php中stream(流)的用法


Posted in PHP onMarch 25, 2014

在Java里,流是一个很重要的概念。

流(stream)的概念源于UNIX中管道(pipe)的概念。在UNIX中,管道是一条不间断的字节流,用来实现程序或进程间的通信,或读写外围设备、外部文件等。根据流的方向又可以分为输入流和输出流,同时可以在其外围再套上其它流,比如缓冲流,这样就可以得到更多流处理方法。

PHP里的流和Java里的流实际上是同一个概念,只是简单了一点。由于PHP主要用于Web开发,所以“流”这块的概念被提到的较少。如果有Java基础,对于PHP里的流就更容易理解了。其实PHP里的许多高级特性,比如SPL,异常,过滤器等都参考了Java的实现,在理念和原理上同出一辙。

比如下面是一段PHP SPL标准库的用法(遍历目录,查找固定条件的文件):

class RecursiveFileFilterIterator extends FilterIterator
{
    // 满足条件的扩展名
    protected $ext = array('jpg','gif');
    /**
     * 提供 $path 并生成对应的目录迭代器
     */
    public function __construct($path)
    {
        parent::__construct(new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path)));
    }
    /**
     * 检查文件扩展名是否满足条件
     */
    public function accept()
    {
        $item = $this->getInnerIterator();
        if ($item->isFile() && in_array(pathinfo($item->getFilename(), PATHINFO_EXTENSION), $this->ext))
        {
            return TRUE;
        }
    }
}
// 实例化
foreach (new RecursiveFileFilterIterator('D:/history') as $item)
{
    echo $item . PHP_EOL;
}

Java里也有和其同出一辙的代码:

public class DirectoryContents
{
    public static void main(String[] args) throws IOException
    {
        File f = new File("."); // current directory
        FilenameFilter textFilter = new FilenameFilter()
        {
            public boolean accept(File dir, String name)
            {
                String lowercaseName = name.toLowerCase();
                if (lowercaseName.endsWith(".txt"))
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        };
        File[] files = f.listFiles(textFilter);
        for (File file : files)
        {
            if (file.isDirectory())
            {
                System.out.print("directory:");
            }
            else
            {
                System.out.print("     file:");
            }
            System.out.println(file.getCanonicalPath());
        }
    }
}

举这个例子,一方面是说明PHP和Java在很多方面的概念是一样的,掌握一种语言对理解另外一门语言会有很大的帮助;另一方面,这个例子也有助于我们下面要提到的过滤器流-filter。其实也是一种设计模式的体现。

我们可以通过几个例子先来了解stream系列函数的使用。

下面是一个使用socket来抓取数据的例子:

$post_ =array (
 'author' => 'Gonn',
 'mail'=>'gonn@nowamagic.net',
 'url'=>'http://www.nowamagic.net/',
 'text'=>'欢迎访问简明现代魔法');
$data=http_build_query($post_);
$fp = fsockopen("nowamagic.net", 80, $errno, $errstr, 5);
$out="POST http://nowamagic.net/news/1/comment HTTP/1.1\r\n";
$out.="Host: typecho.org\r\n";
$out.="User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13"."\r\n";
$out.="Content-type: application/x-www-form-urlencoded\r\n";
$out.="PHPSESSID=082b0cc33cc7e6df1f87502c456c3eb0\r\n";
$out.="Content-Length: " . strlen($data) . "\r\n";
$out.="Connection: close\r\n\r\n";
$out.=$data."\r\n\r\n";
fwrite($fp, $out);
while (!feof($fp))
{
    echo fgets($fp, 1280);
}
fclose($fp);

我们也可以用stream_socket 实现,这很简单,只需要打开socket的代码换成下面的即可:

$fp = stream_socket_client("tcp://nowamagic.net:80", $errno, $errstr, 3);

再来看一个stream的例子:

file_get_contents函数一般常用来读取文件内容,但这个函数也可以用来抓取远程url,起到和curl类似的作用。

$opts = array (
 'http'=>array(
    'method' => 'POST',
    'header'=> "Content-type: application/x-www-form-urlencoded\r\n" .
      "Content-Length: " . strlen($data) . "\r\n",
    'content' => $data)
);
$context = stream_context_create($opts);
file_get_contents('https://3water.com/news', false, $context);

注意第三个参数,$context,即HTTP流上下文,可以理解为套在file_get_contents函数上的一根管道。同理,我们还可以创建FTP流,socket流,并把其套在对应的函数在。

更多关于 stream_context_create,可以参考:PHP函数补完:stream_context_create()模拟POST/GET。

上面提到的两个stream系列的函数都是类似包装器的流,作用在某种协议的输入输出流上。这样的使用方式和概念,其实和Java中的流并没有大的区别,比如Java中经常有这样的写法:

new DataOutputStream(new BufferedOutputStream(new FileOutputStream(new File(fileName))));

一层流嵌套着另外一层流,和PHP里有异曲同工之妙。

我们再来看个过滤器流的作用:

$fp = fopen('c:/test.txt', 'w+');
/* 把rot13过滤器作用在写入流上 */
stream_filter_append($fp, "string.rot13", STREAM_FILTER_WRITE);
/* 写入的数据经过rot13过滤器的处理*/
fwrite($fp, "This is a test\n");
rewind($fp);
/* 读取写入的数据,独到的自然是被处理过的字符了 */
fpassthru($fp);
fclose($fp);
// output:Guvf vf n grfg

在上面的例子中,如果我们把过滤器的类型设置为STREAM_FILTER_ALL,即同时作用在读写流上,那么读写的数据都将被rot13过滤器处理,我们读出的数据就和写入的原始数据是一致的。

你可能会奇怪stream_filter_append中的 "string.rot13"这个变量来的莫名其妙,这实际上是PHP内置的一个过滤器。

使用下面的方法即可打印出PHP内置的流:

streamlist = stream_get_filters();
print_r($streamlist);

输出:

Array
(
    [0] => convert.iconv.*
    [1] => mcrypt.*
    [2] => mdecrypt.*
    [3] => string.rot13
    [4] => string.toupper
    [5] => string.tolower
    [6] => string.strip_tags
    [7] => convert.*
    [8] => consumed
    [9] => dechunk
    [10] => zlib.*
    [11] => bzip2.*
)

自然而然,我们会想到定义自己的过滤器,这个也不难:

class md5_filter extends php_user_filter
{
    function filter($in, $out, &$consumed, $closing)
    {
        while ($bucket = stream_bucket_make_writeable($in))
        {
            $bucket->data = md5($bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        //数据处理成功,可供其它管道读取
        return PSFS_PASS_ON;
    }
}
stream_filter_register("string.md5", "md5_filter");

注意:过滤器名可以随意取。

之后就可以使用"string.md5"这个我们自定义的过滤器了。

这个过滤器的写法看起来很是有点摸不着头脑,事实上我们只需要看一下php_user_filter这个类的结构和内置方法即了解了。

过滤器流最适合做的就是文件格式转换了,包括压缩,编解码等,除了这些“偏门”的用法外,filter流更有用的一个地方在于调试和日志功能,比如说在socket开发中,注册一个过滤器流进行log记录。比如下面的例子:

class md5_filter extends php_user_filter
{
    public function filter($in, $out, &$consumed, $closing)
    {
        $data="";
        while ($bucket = stream_bucket_make_writeable($in))
        {
            $bucket->data = md5($bucket->data);
            $consumed += $bucket->datalen;
            stream_bucket_append($out, $bucket);
        }
        call_user_func($this->params, $data);
        return PSFS_PASS_ON;
    }
}
$callback = function($data)
{
    file_put_contents("c:\log.txt",date("Y-m-d H:i")."\r\n");
};

这个过滤器不仅可以对输入流进行处理,还能回调一个函数来进行日志记录。

可以这么使用:

stream_filter_prepend($fp, "string.md5", STREAM_FILTER_WRITE,$callback);

PHP中的stream流系列函数中还有一个很重要的流,就是包装类流 streamWrapper。使用包装流可以使得不同类型的协议使用相同的接口操纵数据。

PHP 相关文章推荐
discuz 首页四格:最新话题+最新回复+热门话题+精华文章插件
Aug 19 PHP
php下封装较好的数字分页方法
Nov 23 PHP
如何使用php绘制在图片上的正余弦曲线
Jun 08 PHP
destoon后台网站设置变成空白的解决方法
Jun 21 PHP
PHP动态编译出现Cannot find autoconf的解决方法
Nov 05 PHP
PHP实现算式验证码和汉字验证码实例
Mar 09 PHP
深入探究PHP的多进程编程方法
Aug 18 PHP
详解PHP中instanceof关键字及instanceof关键字有什么作用
Nov 05 PHP
Zend Framework教程之连接数据库并执行增删查的方法(附demo源码下载)
Mar 21 PHP
PHP的Laravel框架中使用消息队列queue及异步队列的方法
Mar 21 PHP
PHP 实现从数据库导出到.csv文件方法
Jul 06 PHP
php 使用html5 XHR2实现上传文件与进度显示功能示例
Mar 03 PHP
PHP对接微信公众平台消息接口开发流程教程
Mar 25 #PHP
php操作MongoDB基础教程(连接、新增、修改、删除、查询)
Mar 25 #PHP
php获取域名的google收录示例
Mar 24 #PHP
php 使用GD库为页面增加水印示例代码
Mar 24 #PHP
php检测useragent版本示例
Mar 24 #PHP
php断点续传之如何分割合并文件
Mar 22 #PHP
php 邮件发送问题解决
Mar 22 #PHP
You might like
php 获得汉字拼音首字母的函数
2009/08/01 PHP
PHP小程序自动提交到自助友情连接
2009/11/24 PHP
PHP实现微信公众号验证Token的示例代码
2019/12/16 PHP
网页里控制图片大小的相关代码
2006/06/25 Javascript
JavaScript 工具库 Cloudgamer JavaScript Library v0.1 发布
2009/10/29 Javascript
Asp.net下使用Jquery Ajax传送和接收DataTable的代码
2010/09/12 Javascript
jquery插件制作 手风琴Panel效果实现
2012/08/17 Javascript
JS 实现导航栏悬停效果(续2)
2013/09/24 Javascript
jquery toolbar与网页浮动工具条具体实现代码
2014/01/12 Javascript
jQuery 追加元素的方法如append、prepend、before
2014/01/16 Javascript
Jquery实现由下向上展开效果的例子
2014/12/08 Javascript
详解JavaScript ES6中的模板字符串
2015/07/28 Javascript
javascript中 try catch用法
2015/08/16 Javascript
基于JavaScript实现 网页切出 网站title变化代码
2016/04/03 Javascript
Bootstrap学习笔记之css组件(3)
2016/06/07 Javascript
浅析Bootstrap表格的使用
2016/06/23 Javascript
vue源码入口文件分析(推荐)
2018/01/30 Javascript
AngularJS日期格式化常见操作实例分析
2018/05/17 Javascript
vue watch关于对象内的属性监听
2019/04/22 Javascript
JavaScript数值类型知识汇总
2019/11/17 Javascript
es6数组的flat(),flatMap()函数用法实例分析
2020/04/18 Javascript
javascript设计模式 ? 观察者模式原理与用法实例分析
2020/04/22 Javascript
浅析我对JS延迟异步脚本的思考
2020/10/12 Javascript
[10:14]2018DOTA2国际邀请赛寻真——paiN Gaming不仅为自己而战
2018/08/14 DOTA
在Python中处理时间之clock()方法的使用
2015/05/22 Python
Python 执行矩阵与线性代数运算
2020/08/01 Python
详解如何在PyCharm控制台中输出彩色文字和背景
2020/08/17 Python
python matplotlib绘制三维图的示例
2020/09/24 Python
html5超简单的localStorage实现记住密码的功能实现
2017/09/07 HTML / CSS
The North Face官方旗舰店:美国著名户外品牌
2020/09/28 全球购物
建筑工程实习自我鉴定
2013/09/19 职场文书
大型活动组织方案
2014/05/10 职场文书
寒假社会实践个人总结
2015/03/06 职场文书
原料仓管员岗位职责
2015/04/01 职场文书
Nginx隐藏式跳转(浏览器URL跳转后保持不变)
2022/04/07 Servers
GO中sync包自由控制并发示例详解
2022/08/05 Golang