首页
统计
留言
Search
1
PHP中使用反射
995 阅读
2
phpstorm配置SFTP
940 阅读
3
Go语言——结构体
792 阅读
4
PhpStorm 使用 AI 代码生成工具 Codeium
779 阅读
5
关于PHP的垃圾回收机制
763 阅读
后端
PHP
Go
数据库
其他
前端
其他技术
生活杂谈
登录
Search
标签搜索
Laravel
Mysql
RPC
Redis
Liunx
PHP
CSS
ES
算法
开发工具
断点续传
反射
phpstorm
工具
防盗链
CURL
设计模式
面试
Nginx
搜索引擎
quhe.net
首页
栏目
后端
PHP
Go
数据库
其他
前端
其他技术
生活杂谈
页面
统计
留言
搜索到
43
篇与
后端
的结果
2018-04-05
关于PHP的垃圾回收机制
一、引用计数基础知识每个php变量存在一个叫 zval 的变量容器中。一个 zval 变量容器,除了包含变量的类型和值,还包括两个字节的额外信息。第一个是 is_ref,是个bool值,用来标识这个变量是否是属于引用集合。通过这个字节,php引擎才能把普通变量和引用变量区分开来,由于php允许用户通过使用&来使用自定义引用,zval变量容器中还有一个内部引用计数机制,来优化内存使用。第二个额外字节是 refcount,用以表示指向这个zval变量容器的变量个数。所有的符号存在一个符号表中,其中每个符号都有作用域(scope),那些主脚本(比如:通过浏览器请求的的脚本)和每个函数或者方法也都有作用域。二、生成zval容器当一个变量被赋常量值时,就会生成一个zval变量容器如果安装了Xdebug,则可以通过 xdebug_debug_zval() 查看这两个值 <?php $a = "new string"; xdebug_debug_zval('a'); //结果 a: (refcount=1, is_ref=0)='new string'三、增加zval的引用计数把一个变量赋值给另一变量将增加引用次数<?php $a = "new string"; $b = $a; xdebug_debug_zval( 'a' ); //结果 a: (refcount=2, is_ref=0)='new string'四、减少zval引用计数使用 unset() 可以减少引用次数包含类型和值的这个变量容器就会从内存中删除<?php $a = "new string"; $c = $b = $a; xdebug_debug_zval( 'a' ); unset( $b, $c ); xdebug_debug_zval( 'a' ); //结果 a: (refcount=3, is_ref=0)='new string' a: (refcount=1, is_ref=0)='new string'五、复合类型的zval容器与 标量(scalar)类型的值不同array和 object类型的变量把它们的成员或属性存在自己的符号表中这意味着下面的例子将生成三个zval变量容器这三个zval变量容器是: a,meaning和 number <?php $a = array( 'meaning' => 'life', 'number' => 42 ); xdebug_debug_zval( 'a' ); //结果 a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=1, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42 )六、增加复合类型的引用计数添加一个已经存在的元素到数组中 <?php $a = array( 'meaning' => 'life', 'number' => 42 ); $a['life'] = $a['meaning']; xdebug_debug_zval( 'a' ); //结果 a: (refcount=1, is_ref=0)=array ( 'meaning' => (refcount=2, is_ref=0)='life', 'number' => (refcount=1, is_ref=0)=42, 'life' => (refcount=2, is_ref=0)='life' )七、减少复合类型的引用计数删除数组中的一个元素就是类似于从作用域中删除一个变量.删除后,数组中的这个元素所在的容器的“refcount”值减少 <?php $a = array( 'meaning' => 'life', 'number' => 42 ); $a['life'] = $a['meaning']; unset( $a['meaning'], $a['number'] ); xdebug_debug_zval( 'a' ); //结果 a: (refcount=1, is_ref=0)=array ( 'life' => (refcount=1, is_ref=0)='life' )八、特殊情况当我们添加一个数组本身作为这个数组的元素时,事情就变得有趣同上,对一个变量调用unset,将删除这个符号,且它指向的变量容器中的引用次数也减1 <?php $a = array( 'one' ); $a[] = &$a; xdebug_debug_zval( 'a' ); //结果 a: (refcount=2, is_ref=1)=array ( 0 => (refcount=1, is_ref=0)='one', 1 => (refcount=2, is_ref=1)=... )九、清理变量容器的问题尽管不再有某个作用域中的任何符号指向这个结构(就是变量容器),由于数组元素“1”仍然指向数组本身,所以这个容器不能被清除 。因为没有另外的符号指向它,用户没有办法清除这个结构,结果就会导致内存泄漏。庆幸的是,php将在脚本执行结束时清除这个数据结构,但是在php清除之前,将耗费不少内存。如果上面的情况发生仅仅一两次倒没什么,但是如果出现几千次,甚至几十万次的内存泄漏,这显然是个大问题十、回收周期像以前的 php 用到的引用计数内存机制,无法处理循环的引用内存泄漏而在php 5.3.0 中使用同步算法,来处理这个内存泄漏问题如果一个引用计数增加,它将继续被使用,当然就不再在垃圾中。如果引用计数减少到零,所在变量容器将被清除(free)就是说,仅仅在引用计数减少到非零值时,才会产生垃圾周期在一个垃圾周期中,通过检查引用计数是否减1,并且检查哪些变量容器的引用次数是零,来发现哪部分是垃圾十一、回收算法分析为避免不得不检查所有引用计数可能减少的垃圾周期这个算法把所有可能根(possible roots 都是zval变量容器),放在根缓冲区(root buffer)中(用紫色来标记,称为疑似垃圾),这样可以同时确保每个可能的垃圾根(possible garbage root)在缓冲区中只出现一次。仅仅在根缓冲区满了时,才对缓冲区内部所有不同的变量容器执行垃圾回收操作。看上图的步骤 A。在步骤 B 中,模拟删除每个紫色变量。模拟删除时可能将不是紫色的普通变量引用数减"1",如果某个普通变量引用计数变成0了,就对这个普通变量再做一次模拟删除。每个变量只能被模拟删除一次,模拟删除后标记为灰在步骤 C 中,模拟恢复每个紫色变量。恢复是有条件的,当变量的引用计数大于0时才对其做模拟恢复。同样每个变量只能恢复一次,恢复后标记为黑,基本就是步骤 B 的逆运算。这样剩下的一堆没能恢复的就是该删除的蓝色节点了,在步骤 D 中遍历出来真的删除掉十二、性能考虑主要有两个领域对性能有影响第一个是内存占用空间的节省另一个是垃圾回收机制释放已泄漏的内存耗费的时间增加十三、垃圾回收机制的结论PHP中的垃圾回收机制,仅仅在循环回收算法确实运行时会有时间消耗上的增加。但是在平常的(更小的)脚本中应根本就没有性能影响。然而,在平常脚本中有循环回收机制运行的情况下,内存的节省将允许更多这种脚本同时运行在你的服务器上。因为总共使用的内存没达到上限。这种好处在长时间运行脚本中尤其明显,诸如长时间的测试套件或者daemon脚本此类。
2018年04月05日
763 阅读
0 评论
37 点赞
2018-02-22
用PHP做个图片防盗链,休想再盗我的图片
1、图片防盗链在一些大型网站中,比如百度贴吧,该站点的图片采用了防盗链的规则,以至于使用下面代码会发生错误。简单代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title></title> <link rel="stylesheet" href=""> </head> <body> <!--引用一张百度贴吧的图片--> <img src="http://imgsrc.baidu.com/forum/pic/item/03QZLa5sGTcdvn27jHpeNYPoGZtfB1JpDnPbd5d5.jpg"/> </body> </html>出现的问题: 出错的原因: 主要是该站点的图片采用了防盗链的规则,其实这个规则也比较简单, 和大家一说就知道啦,主要是该站点在得知有请求时,会先判断请求头中的信息,如果请求头中有Referer信息,然后根据自己的规则来判断Referer头信息是否符合要求,Referer 信息是请求该图片的来源地址。浏览器中的请求头信息:正常使用百度贴吧查看图片的请求头信息我的代码的头信息看到这,也就明白了,为什么我的代码不能访问到图片,而是显示一张警告盗链图片,因为我们的Referer头信息和百度贴吧的不同,当我的请求发出去时,该站点查看Referer头信息,一看来源不是本站,就重定向到另外一张图片了。给自己的站点配置图片防盗链APACHE为例在web服务器中开启mod_rewrite模块LoadModule rewrite_module modules/mod_rewrite.so,//将前面的#给去掉,然后重新启动服务器在需要防盗的网站或目录中,写.htaccess文件,并指定防盗链规则步骤:新建一个.htaccess文件,在windows中使用另存为的方式来新建此文件查找手册,在.htaccess文件中利用正则判断指定规则:如果是图片资源且referer头信息是来自于本站,则通过重写规则如下:假定我的服务器是localhost,规则的意思是,如果请求的是图片资源,但是请求来源不是本站的话,就重定向到当前目录的一张no.png的图片上RewriteEngine On RewriteCond %{SCRIPT_FILENAME} .*\.(jpg|jpeg|png|gif) [NC] RewriteCond %{HTTP_REFERER} !localhost [NC] RewriteRule .* no.pngNGINX配置如下: location /images { valid_referers none blocked www.baidu.com 192.168.200.222 *.example.com example.* www.example.org ~\.google\.; if ($invalid_referer){ return 403; } root /usr/local/nginx/html; }来自localhost的访问:来自于其他站点的访问:至此,关于防盗链的知识说完了,但是不急,既然是一个请求头,当然是可以伪造的,下面我们来说一下反防盗链的规则。2、反防盗链上面我的服务器配置了图片防盗链,现在以它来讲解反防盗链,如果我们在采集图片的时候,遇到使用防盗链技术的站点,我们可以在采集图片的时候伪造一个Referer头信息。下面的代码是从一个配置了图片防盗链的站点下载一张图片。<?php /** * 下载图片 * @author webbc */ require './Http.class.php';//这个类是我自己封装的一个用于HTTp请求的类 $http = new Http("http://localhost/booledu/http/apple.jpg"); //$http->setHeader('Referer:http://tieba.baidu.com/');//设置referer头 $res = $http->get(); $content = strstr($res,"\r\n\r\n"); file_put_contents('./toutupian.jpg',substr($content,4)); echo "ok"; ?>不加Referer头信息下载的结果加Referer头信息下载的结果大家看到这,应该也能看出来如何反防盗链吧,其实就是加上一个Referer头信息,那么,每个站点的Referer头信息从哪里找呢?这个应该抓包分析就可以得出来了!3、封装的Http请求类: <?php /** * Http请求类 * @author webbc */ class Http{ const CRTF = "\r\n"; private $errno = -1; private $errstr = ''; private $timeout = 5; private $url = null;//解析后的url数组 private $version = 'HTTP/1.1';//http版本 private $requestLine = array();//请求行信息 private $header = array();//请求头信息 private $body = array();//请求实体信息 private $fh = null;//连接端口后返回的资源 private $response = '';//返回的结果 //构造函数 public function __construct($url){ $this->connect($url); $this->setHeader('Host:'.$this->url['host']);//设置头信息 } //通过URL进行连接 public function connect($url){ $this->url = parse_url($url);//解析url if(!isset($this->url['port'])){ $this->url['port'] = 80; } $this->fh = fsockopen($this->url['host'],$this->url['port'],$this->errno,$this->errstr,$this->timeout); } //设置请求行信息 public function setRequestLine($method){ $this->requestLine[0] = $method.' '.$this->url['path'].' '.$this->version; } //设置请求头信息 public function setHeader($headerLine){ $this->header[] = $headerLine; } //设置请求实体信息 public function setBody($body){ $this->body[] = http_build_query($body); } //发送get请求 public function get(){ $this->setRequestLine('GET');//设置请求行 $this->request();//发送请求 $this->close();//关闭连接 return $this->response; } //发送请求 private function request(){ //拼接请求的全部信息 $reqestArr = array_merge($this->requestLine,$this->header,array(''),$this->body,array('')); $req = implode(self::CRTF,$reqestArr); //print_r($req);die; fwrite($this->fh,$req);//写入信息 //读取 while(!feof($this->fh)){ $this->response .= fread($this->fh,1024); } } //发送post请求 public function post($body = array()){ //设置请求行 $this->setRequestLine("POST"); //设置实体信息 $this->setBody($body); //设置Content-Type $this->setHeader('Content-Type:application/x-www-form-urlencoded'); //设置Content-Length $this->setHeader('Content-Length:'.strlen($this->body[0])); //请求 $this->request(); $this->close();//关闭连接 return $this->response; } //关闭连接 public function close(){ fclose($this->fh); } } //测试get // $http = new Http("http://news.163.com/16/0915/10/C10ES2HA00014PRF.html"); // $result = $http->get(); // echo $result; //测试post /*set_time_limit(0); $str = 'abcdefghijklmnopqrstuvwxyz0123456789'; while(true){ $http = new Http("http://211.70.176.138/yjhx/message.php"); $str = str_shuffle($str); $username = substr($str,0,5); $email = substr($str,5,10).'@qq.com'; $content = substr($str,10); $message = "发表"; $http->post(array('username'=>$username,'email'=>$email,'content'=>$content,'message'=>$message)); //sleep(0.1); }*/ ?>
2018年02月22日
70 阅读
0 评论
5 点赞
2018-02-07
PHP static 和 self 的区别
self :: 在哪个类里调用的就调用哪个类中的方法,不受重写的影响。static :: 调用的方法如果被子类重写,那调用的就是子类中的方法。//父类 class father { public static function name() { echo "xiaosan"; echo "<br />"; } public static function callself() { self::name(); //调用name方法 } public static function callstatic() { static::name(); //静态绑定 } } //继承类 class son extends father { //重写name方法 public static function name() { echo "gaojin"; echo "<br />"; } } son::callself(); // xiaosan son::callstatic(); // gaojin
2018年02月07日
534 阅读
0 评论
32 点赞
2017-12-30
PHP中使用反射
反射面向对象编程中对象被赋予了自省的能力,而这个自省的过程就是反射。反射,直观理解就是根据到达地找到出发地和来源。比如,一个光秃秃的对象,我们可以仅仅通过这个对象就能知道它所属的类、拥有哪些方法。反射是指在PHP运行状态中,扩展分析PHP程序,导出或提出关于类、方法、属性、参数等的详细信息,包括注释。这种动态获取信息以及动态调用对象方法的功能称为反射API。如何使用反射API?class person{ public $name; public $gender; public function say(){ echo $this->name," \tis ",$this->gender,"\r\n"; } public function set($name, $value) { echo "Setting $name to $value \r\n"; $this->$name= $value; } public function get($name) { if(!isset($this->$name)){ echo '未设置'; $this->$name="正在为你设置默认值"; } return $this->$name; } } $student=new person(); $student->name='Tom'; $student->gender='male'; $student->age=24;现在,要获取这个student对象的方法和属性列表该怎么做呢?如以下代码所示:// 获取对象属性列表 $reflect = new ReflectionObject($student); $props = $reflect->getProperties(); foreach ($props as $prop) { print $prop->getName() ."\n"; } // 获取对象方法列表 $m=$reflect->getMethods(); foreach ($m as $prop) { print $prop->getName() ."\n"; }也可以不用反射API,使用class函数,返回对象属性的关联数组以及更多的信息:// 返回对象属性的关联数组 var_dump(get_object_vars($student)); // 类属性 var_dump(get_class_vars(get_class($student))); // 返回由类的方法名组成的数组 var_dump(get_class_methods(get_class($student)));假如这个对象是从其他页面传过来的,怎么知道它属于哪个类呢?一句代码就可以搞定:// 获取对象属性列表所属的类 echo get_class($student);反射API的功能显然更强大,甚至能还原这个类的原型,包括方法的访问权限等,如:// 反射获取类的原型 $obj = new ReflectionClass('person'); $className = $obj->getName(); $Methods = $Properties = array(); foreach($obj->getProperties() as $v) { $Properties[$v->getName()] = $v; } foreach($obj->getMethods() as $v) { $Methods[$v->getName()] = $v; } echo "class {$className}\n{\n"; is_array($Properties)&&ksort($Properties); foreach($Properties as $k => $v) { echo "\t"; echo $v->isPublic() ? ' public' : '',$v->isPrivate() ? ' private' : '', $v->isProtected() ? ' protected' : '', $v->isStatic() ? ' static' : ''; echo "\t{$k}\n"; } echo "\n"; if(is_array($Methods)) ksort($Methods); foreach($Methods as $k => $v){ echo "\tfunction {$k}(){}\n"; } echo "}\n";输出如下:class person { public gender public name function get(){} function set(){} function say(){} }不仅如此,PHP手册中关于反射API更是有几十个,可以说,反射完整地描述了一个类或者对象的原型。反射不仅可以用于类和对象,还可以用于函数、扩展模块、异常等。反射有什么作用?反射可以用于文档生成。因此可以用它对文件里的类进行扫描,逐个生成描述文档。既然反射可以探知类的内部结构,那么是不是可以用它做hook实现插件功能呢?或者是做动态代理呢?例如:class mysql { function connect($db) { echo "连接到数据库${db[0]}\r\n"; } } class sqlproxy { private $target; function construct($tar) { $this->target[] = new $tar(); } function call($name, $args) { foreach ($this->target as $obj) { $r = new ReflectionClass($obj); if ($method = $r->getMethod($name)) { if ($method->isPublic() && !$method->isAbstract()) { echo "方法前拦截记录LOG\r\n"; $method->invoke($obj, $args); echo "方法后拦截\r\n"; } } } } } $obj = new sqlproxy('mysql'); $obj->connect('member');在平常开发中,用到反射的地方不多:一个是对对象进行调试,另一个是获取类的信息。在MVC和插件开发中,使用反射很常见,但是反射的消耗也很大,在可以找到替代方案的情况下,就不要滥用。很多时候,善用反射能保持代码的优雅和简洁,但反射也会破坏类的封装性,因为反射可以使本不应该暴露的方法或属性被强制暴露了出来,这既是优点也是缺点。
2017年12月30日
995 阅读
0 评论
61 点赞
2017-12-12
大文件断点续传,用PHP怎么实现
在现代网站应用中,上传文件是非常常见的。在任何语言中,通过使用一些工具,都可以实现文件上传的功能。但是,如果处理大文件上传的需求,还是有点麻烦的。假如你此时正在上传一个很大的文件,大约一个小时过去了,进度是 90%。突然断网了或者浏览器崩溃了,上传的程序退出,你要再全部重新来过。真的很不爽,对不对?还有更让人郁闷的是,如果你的网速很慢,那么,无论你重来多少次,你都不可能上传成功。在 PHP 中,我们可以尝试利用 tus 协议的断点续传功能来解决这个问题。什么是 tus?Tus 是一个基于 HTTP 的 文件断点续传开放协议。断点续传的意思是不管是用户自行中断,还是由于网络等原因的意外中断,都可以从中断的地方继续上传,而不用重新开始。Tus 协议是在 2017 年5月被 Vimeo 采用的。为什么用 tus?引用 Vimeo 的博客:我们之所以决定用 tus,是因为它能以简洁开放的形式,将文件上传的过程标准化。这种标准化有利于 API 的开发者更加专注于应用本身的逻辑,而非文件上传的过程。使用这种方式上传的另一个好处是,你可以在笔记本上开始上传文件,然后又转到手机或者其他设备继续上传同一个文件,这可以极大地提升用户体验。开始第一步,composer安装依赖。$ composer require ankitpokhrel/tus-phptus-php 是用于 tus 断点续传协议 v1.0.0 的一个的纯 PHP 框架,完美实现了 服务端与客户端的交互 。更新: 现在 Vimeo 官方 PHP 库 的 v3 用的是 TusPHP。创建一个处理请求的服务端你可以像下面这样创建一个服务端.// server.php $server = new \TusPhp\Tus\Server('redis'); $response = $server->serve(); $response->send(); exit(0); // 退出当前 PHP 进程你需要配置你的服务器以便能对特定的终端进行响应。如果使用 Nginx 的话你可以像下面这样配置:# nginx.conf location /files { try_files $uri $uri/ /path/to/server.php?$query_string; }假设我们服务端的 URL 是 http://server.tus.local. 因此,基于我们上面的 Nginx 配置,我们可以通过 http://server.tus.local/files. 来访问到我们的 tus 终端.如果你是用类似于 Laravel 的框架,那么你就不需要在配置文件里定义这些了, 可以直接定义路由来访问 tus 的基础端点。使用 tus-php 客户端处理上传服务器到位后,客户端可以块的形式上传文件。让我们首先创建一个简单的 HTML 表单来获取用户的输入。<form action="upload.php" method="post" enctype="multipart/form-data"> <input type="file" name="tus_file" id="tus-file" /> <input type="submit" value="Upload" /> </form>提交表单后,我们需要按照几个步骤来处理上传。创建一个 tus-php 客户端对象// Tus client $client = new \TusPhp\Tus\Client('http://server.tus.local');上面代码中的第一个参数是你的 tus 服务器地址。2. 使用文件元数据初始化客户端为了确保上传文件的唯一性,我们需要给每个上传的文件以唯一标识。这样在文件中断后续传的时候,服务器就可以很清晰地辨识出,哪几个片段是属于同一个文件得。这个标识码可以自己指定,也可以由系统生成。// 设置标识码和文件元数据 $client->setKey($uploadKey) ->file($_FILES['tus_file']['tmp_name'], 'your file name');如果不想指定标识码,可以这样写,由系统会自动生成:$client->file($_FILES['tus_file']['tmp_name'], 'your file name');$uploadKey = $client->getKey(); // Unique upload key3. 分块上传文件// $chunkSize 是以字节为单位的,例如 5000000 等于 5 MB $bytesUploaded = $client->upload($chunkSize);当你想要续传下一块的时候,就可以带上同样的标识码参数来续传。// 在下一个请求中续传文件 $bytesUploaded = $client->setKey($uploadKey)->upload($chunkSize);文件全部上传完成后,默认情况下,服务器会使用 sha256 来校验文件总和,以确保不会有丢失的文件。使用 tus-js-client 客户端处理文件上传tus 协议的团队还开发了一个模块化的文件上传插件 Uppy。这个插件可以在官方 tus-js-client 和 tus-php 服务器之间建立连接。也就是说我们可以使用 php 配合 js 来实现文件上传了。uppy.use(Tus, { endpoint: 'https://server.tus.local/files/', // 你的 tus 服务器 resume: true, autoRetry: true, retryDelays: [0, 1000, 3000, 5000]})分块上传tus-php 服务器支持 concatenation 扩展,可以把多次上传的文件合为一个文件。因此,我们可以在客户端支持并行上传以及非连续的分块文件上传。使用 tus-php 实现分块上传tus-partial-upload.php<?php // 文件唯一标识码 $uploadKey = uniqid(); $client->setKey($uploadKey)->file('/path/to/file', 'chunk_a.ext'); // 从第 1000 个字节开始上传 10000 字节 $bytesUploaded = $client->seek(1000)->upload(10000); $chunkAkey = $client->getKey(); // 从 第 0 个字节开始上传 10000 字节 $bytesUploaded = $client->setFileName('chunk_b.ext')->seek(0)->upload(1000); $chunkBkey = $client->getKey(); // 从第 11000 个字节 (10000 + 1000) 开始上传剩余的字节 $bytesUploaded = $client->setFileName('chunk_c.ext')->seek(11000)->upload(); $chunkCkey = $client->getKey(); // 把分块上传的文件组合起来 $client->setFileName('actual_file.ext')->concat($uploadKey, $chunkAkey, $chunkBkey, $chunkCkey);分块上传的完整例子https://github.com/ankitpokhrel/tus-php/tree/master/example/partial
2017年12月12日
582 阅读
0 评论
32 点赞
1
...
7
8
9