浅谈PHP中Stream(流)


Posted in PHP onJune 08, 2015

流(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('http://nowamagic.net/news/1/comment', 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 相关文章推荐
PHP实现分页的一个示例
Oct 09 PHP
PHP 已经成熟
Dec 04 PHP
劣质的PHP代码简化
Feb 08 PHP
php中通过curl模拟登陆discuz论坛的实现代码
Feb 16 PHP
PHP register_shutdown_function函数的深入解析
Jun 03 PHP
php socket实现的聊天室代码分享
Aug 16 PHP
CI分页类首页、尾页不显示的解决方法
Mar 28 PHP
php实现HTML实体编号与非ASCII字符串相互转换类实例
Nov 02 PHP
Centos 6.5下PHP 5.3安装ffmpeg扩展的步骤详解
Mar 02 PHP
Laravel框架Blade模板简介及模板继承用法分析
Dec 03 PHP
Yii使用EasyWechat实现小程序获取用户的openID的方法
Apr 29 PHP
PHP实现图片防盗链破解操作示例【解决图片防盗链问题/反向代理】
May 29 PHP
php实现有趣的人品测试程序实例
Jun 08 #PHP
老版本PHP转义Json里的特殊字符的函数
Jun 08 #PHP
php查询whois信息的方法
Jun 08 #PHP
Yii获取当前url和域名的方法
Jun 08 #PHP
在Linux系统的服务器上隐藏PHP版本号的方法
Jun 06 #PHP
phpcms手机内容页面添加上一篇和下一篇
Jun 05 #PHP
DEDECMS首页调用图片集里的多张图片
Jun 05 #PHP
You might like
php判断输入不超过mysql的varchar字段的长度范围
2011/06/24 PHP
xml文档转换工具,附图表例子(hta)
2010/11/17 Javascript
javascript中类的定义及其方式(《javascript高级程序设计》学习笔记)
2011/07/04 Javascript
自己动手制作jquery插件之自动添加删除行的实现
2011/10/13 Javascript
当jQuery1.7遇上focus方法的问题
2014/01/26 Javascript
jQuery实现设置、移除文本框默认值功能
2015/01/13 Javascript
JQuery通过AJAX从后台获取信息显示在表格上并支持行选中
2015/09/15 Javascript
js实现图片无缝滚动
2015/12/23 Javascript
jQuery获取当前点击的对象元素(实现代码)
2016/05/19 Javascript
第七篇Bootstrap表单布局实例代码详解(三种表单布局)
2016/06/21 Javascript
JavaScript 随机验证码的生成实例代码
2016/09/22 Javascript
利用fecha进行JS日期处理
2016/11/21 Javascript
React 子组件向父组件传值的方法
2017/07/24 Javascript
深入理解Node module模块
2018/03/26 Javascript
JS实现常见的查找、排序、去重算法示例
2018/05/21 Javascript
js代码规范之Eslint安装与配置详解
2018/09/08 Javascript
jQuery 获取除某指定对象外的其他对象 ( :not() 与.not())
2018/10/10 jQuery
vue响应式更新机制及不使用框架实现简单的数据双向绑定问题
2019/06/27 Javascript
js中Function引用类型常见有用的方法和属性详解
2019/12/11 Javascript
原生JS实现顶部导航栏显示按钮+搜索框功能
2019/12/25 Javascript
基于jquery实现彩色投票进度条代码解析
2020/08/26 jQuery
简单分析Python中用fork()函数生成的子进程
2015/05/04 Python
Python中线程编程之threading模块的使用详解
2015/06/23 Python
Python+MongoDB自增键值的简单实现
2016/11/04 Python
python自动化之Ansible的安装教程
2019/06/13 Python
python实现差分隐私Laplace机制详解
2019/11/25 Python
Python综合应用名片管理系统案例详解
2020/01/03 Python
pytorch VGG11识别cifar10数据集(训练+预测单张输入图片操作)
2020/06/24 Python
企业内部培训方案
2014/02/04 职场文书
十八大演讲稿
2014/05/22 职场文书
销售人员工作自我评价
2014/09/21 职场文书
单位工作证明
2014/10/07 职场文书
七年级生物教学反思
2016/02/20 职场文书
自考生自我评价
2019/06/21 职场文书
2019奶茶店创业计划书范本!
2019/07/15 职场文书
alibaba seata服务端具体实现
2022/02/24 Java/Android