PHP之文件的锁定、上传与下载

小结文件的锁定机制、上传和下载

1.文件锁定

现在都在讲究什么分布式、并发等,实际上文件的操作也是并发的,在网络环境下,多个用户在同一时刻访问页面,对同一服务器上的同一文件进行着读取,如果,这个用户刚好读到一半,另一个用户就写入了消息,那么前一个用户读到的就是错误数据,在数据库里面好像是称为脏数据,而如果某用户写到一半时,另一用户也对该文件进行写操作,那么就造成了写入数据的混乱和错误,因此才php有一个锁机制,类似于数据库的锁,当某用户在对文件操作时就加上某种锁,使得在同一时间其他用户不能对该文件进行操作或只能进行有限的操作,来保证在这些情况下的文件数据的正确性。

主要使用flock函数,原型:bool flock(resource $handle , int $operation [, int &$wouldblock ]),第一个参数是指向文件的句柄变量,第二个是加锁的方式,分别为

LOCK_SH:共享锁(share),在读取文件时加的锁,加锁后就其他用户不能再对该文件进行写,但可以读取该文件内容;

LOCK_EX:排他锁(exclude),或者叫独占锁,在写文件时使用,加了该锁后,只能是当前用户进行写操作,其他的用户不能读取和写入;

LOCK_NB:附加锁,在文件锁定短时间大量用户的访问操作可能会造成flock在锁定时堵塞,如果再加上该锁后可避免该情况(是不是这么一弄就能解决大量读写操作的问题,怕不行...);

LOCK_UN:释放锁,对前面的各种锁进行一次性释放,解锁。

如果容易堵塞,还可使用第三个参数wouldblock,如果把它设置为1,在锁定后就会阻挡其他进程来进行一些操作,但是windows上不支持,另外附加锁LOCK_NB,windows也是不支持的。

另外,关闭句柄变量的fclose操作也可以释放这些锁。

废话少说,看代码

<?php
     function readFileData($filename){
         if(true == ($handle = fopen($filename, ‘r‘))){
             if(flock($handle, LOCK_SH+LOCK_NB)){  // 加共享锁和附加锁,附加锁防止阻塞
                 $str = ‘‘;
                 while(!feof($handle)){
                     $str .= fread($handle, 128);
                  }
                  flock($handle, LOCK_UN); // 释放该锁
                      fclose($handle);
                      return $str;
              }
              else{
                 echo ‘add a share lock failed‘;
                 return ‘‘;
              }
          }
          else{
              return ‘‘;
          }
       }

注意使用多重锁的方式,是LOCK_SH+LOCK_NB,,在排他锁时也可这么用,它们都是枚举变量。对于写操作类似

<?php
    function writeInFile($filename, $data){
        if(true == ($handle = fopen($filename, ‘a‘))){
            if(flock($handle, LOCK_EX)){   //加上排他锁
                fwrite($handle, $data);
                flock($handle, LOCK_UN);  //释放锁
                fclose($handle);
            }
            else{
                echo ‘add exclusive lock failed‘;
                return;
            }
       }
    }

实际上这也是解决php来实现多进程/线程读取文件的方式,php没有多线程这么一说,但加锁机制可以来模拟这种方式,实现对文件某些操作的队列处理,面试时别说你不知道-_-

2.文件上传

文件上传就是将本地的文件上传到服务器上,我们在用度场的云、鹅场的云上传文件时均是如此,当然实际情况肯定比这简单程序复杂。HTTP协议实现了文件的上传机制,首先要在本地选择上传的文件,上传到服务器后,服务端又要做一些处理,为此客户端和服务端均要做一些设置。

1、客户端

文件上传最基本的方法是通过form表单进行POST传递文件,实际上通过PUT方法也可以上传文件,只不过这种方法不安全,需要配置一些安全验证机制,这里只写最常用的方式。form表单的input标签可以设置成文件上传按钮type="file",直接解决了如何选择文件的问题,接下来需要设置form的两个属性:enctype和method

enctype:设置成multipart/form-data

method:设置成post

关于enctype属性设置可参考W3School的解释

第一条是默认的值,在我们使用HTTP协议传递一般的表单数据时,实际上默认对数据进行了分块编码,比如默认的urlencode方式(空格转为+,其他非字母字符转为%加两个十六进制大写数);当enctype设置为第二个时,不会进行字符编码,使用上传控件(input标签type设为file时即是)上传文件,必须设定为这个值;第三个则是值对空格编码为+,但不对非字母字符进行编码。

我们知道GET方法一般用于获取数据,且传递数据大小有限,而且POST方法可以传递比GET大得多的数据。

form的属性设置完成后,还要传递一个值过去,使用隐藏域(<input type="hidden">),它的name属性设为MAX_FILE_SIZE,之所以要设定这个值,是先大概定一个文件尺寸值,避免在用户传一个大文件传了半天再告诉他:sorry,你的文件太大了-_-它的value属性值就是文件的size,以字节为单位。当然某些书上说,这个值只是作为参考,可轻易进行欺骗,这里只是象征性的表示,很可惜我这只菜鸟对安全了解甚少,只知道普通注入、XSS等,暂且用着吧。

那么就可以写一个简单得不能再简单的页面了,作为客户端用:

<form method="post" action="upload.php" enctype="multipart/form-data">
    <input type="hidden" name="MAX_FILE_SIZE" value="1000000" />
    选择文件: <input type="file" name="uploadFile" value="upload"/><br/>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <input type="submit" name="submit" value="上传" />
</form>

2、服务端

文件上传到了服务器上还要经过一些处理过程,就像网购派送快递,到了目的地也还得分个类,确认下目的地对错吧。到了目的地的后续处理需要php脚本,上面在提交表单时的action属性就指定了提交的处理脚本。我们知道在php中,$_POST保存的是post传递的数据,而上传文件的相关信息保存在$_FILES里边,假设服务端脚本是这样的:

<?php
    echo ‘_FILES: <pre>‘;
    print_r($_FILES);

    echo ‘_POST: <pre>‘;
    print_r($_POST);

不管服务端如何处理的,先看看这两个数组里面有什么:

看FILES数组的选项就猜得到,这些就是上传文件的名字、类型、尺寸、错误信息等等,还有这个FILES是二维数组。在弄清楚这些选项之前有必要了解几个php配置选项,打开php.ini文件,找到下面四项(其实看注释也明白了):

file_uploads:是否允许通过HTTP传递文件,默认是On允许;

upload_max_filesize:允许传递文件的最大大小,以M为单位,这是服务端配置文件设定的选项;

max_file_uploads:一次请求所允许传递的做多文件个数;

post_max_size:通过POST传递数据的最大大小,因为文件传递也是post方式,也算post传递,需要特别注意的是,它必须要大于upload_max_filesize选项,因为在一次post传递过程中不仅会上传文件,还会传递其他数值,比如上面的POST数组中的数据,必须考虑到,比如upload_max_filesize设为150M,这个就可以设为200M;

upload_tmp_dir:上传文件的临时目录,配置文件里面默认为空,会使用操作系统默认的临时目录,因此上面的FILES数组中的tmp_name中的眼熟的路径就可以解释了,使用windows默认的存放临时文件的目录,而且服务器默认对文件名作了修改。

那么FILES数组中的uploadFile哪里来的,为什么要用它做键名,这是因为在上传控件的name属性就是uploadFile,它标记的是这个控件的上传文件信息,因此我们可以放多个上传控件,设置不同的name,当然设置一样的name也可以,完全可以把它们全放在一个数组里边,如<input type="file" name="upload[]">。

现在回过头看FILE数组的键名代表的信息,type是MIME类型,以/分隔,前面是主要类型,后面是具体文件类型,error肯定表示错误,有这么几种情况,0:没有错误,上传成功; 1:文件超过了PHP配置指令中的upload_max_filesize规定的大小; 2:文件超过HTML表单中MAX_FILE_SIZE规定的大小,3:文件只有部分上传; 4:没有文件上传。现在关于FILES数组的问题全部明白了。

问题是,是不是上传成功就不做任何处理了,当然不是,总不能全堆在一个临时目录里面,上传多了必然就要将文件移到别的地方,而php提供了专门而安全的函数。is_uploaded_file函数,判断是否通过HTTP POST上传,可以确保恶意的用户去欺骗脚本而管理这些文件,例如/etc/pass(又是linux...),至于具体怎样,我还不清楚。move_uploaded_file函数,将上传文件移动到新位置,同时还可判断文件是否为合法上传,即通过HTTP POST方式,他们运行成功均返回布尔类型true。

扯了半天,上传文件大概要经过这样几个步骤:

1、客户端写好上传控件脚本,并传递一个限制文件大小的隐藏值;

2、服务端首先判断FILES数组error值,看是否出错;

3、判断是否为允许上传的类型(可以不判断);

4、判断在服务端脚本里边是否超过指定的文件大小;

5、上传到临时位置,生成新文件名(防止把已有同名文件覆盖掉),检查并移动到新目录下。

客户端准备工作刚已做,看服务端处理代码:

<?php
    $typeWhiteList = array(‘txt‘, ‘doc‘, ‘php‘, ‘zip‘, ‘exe‘);   // 类型白名单,过滤不允许上传的文件类型
    $max_size = 1000000;  // 大小限制 为1M
    $upload_path = ‘D:/WAMP/upload/‘;    // 指定移至的目录

    // 1、判断是否成功上传到服务器
    $error = $_FILES[‘uploadFile‘][‘error‘];
    if($error > 0){
         switch($error){
             case 1: exit(‘超过php配置的最大文件上传限制‘);
             case 2: exit(‘超过HTML表单的最大文件上传限制‘);
             case 3: exit(‘文件只有部分被上传‘);
             case 4: exit(‘没有上传任何文件‘);
             default: exit(‘未知类型错误‘);
         }
    }

    // 2、判断是否为允许上传的类型
    $extension = pathinfo($_FILES[‘uploadFile‘][‘name‘], PATHINFO_EXTENSION); // 获取扩展名
    if(!in_array($extension, $typeWhiteList)){
        if($extension == ‘‘)
           exit(‘不允许上传空类型文件‘);
         else
           exit(‘不允许上传‘.$extension.‘类型文件‘);
    } 

    // 3、判断是否为允许大小
    if($_FILES[‘uploadFile‘][‘size‘] > $max_size){
        exit(‘超过了允许上传到的‘.$max_size.‘字节‘);
    }

    // 4、已到指定位置
    $filename = date(‘Ymd‘).rand(1000, 9999);   // 生成一个新文件名,防止覆盖
    if(is_uploaded_file($_FILES[‘uploadFile‘][‘tmp_name‘])){   // 判断是否通过HTTP POST上传
        if(!move_uploaded_file($_FILES[‘uploadFile‘][‘tmp_name‘], $upload_path.$filename.‘.‘.$extension)){
            exit(‘无法移动到指定位置‘);
         }
         else{
            echo ‘文件上传成功<br/>‘;
            echo ‘文件名: ‘.$upload_path.$filename.‘.‘.$extension.‘<br>‘;
         }
    }
     else{
         exit(‘文件未通过合法途径上传‘);
     }

本想迅速体验一把,结果报了个Warning,说时间设置依赖系统...bug总是这么不期而遇,设置好时间后,再试,perfect!

         

3.文件下载

文件下载就比较简单了,简单的文件下载只需要用一个HTML链接就够了,使用<a>标签,href属性指定资源位置,一点就可。但这种方式只能处理浏览器默认无法识别的MIME类型,比如rar、7z等压缩的数据。

<html>
    <head>
             <title>donwload file</title>
             <meta http-equiv="Content-Type" content="text/html"; charset="utf-8" />
    </head>
    <body>
             <a href="resource/header.txt">header.txt</a><br/>
             <a href="resource/php.zip">php.zip</a><br/>
             <a href="resource/pic.ico">pic.ico</a>

    </body>
</html>

对于这些浏览器不认识的类型文件,点链接,它直接弹框让你下载,有的浏览器甚至直接就下了,那么对于文本txt、jpg等浏览器默认识别的类型的文件,一点击则会直接展现在页面上,比如上面header.txt、pic.ico。如何不展示在页面上而去下载它们呢,使用header函数。

header函数会通过发送头信息告知,请把该文件当成一个附件,这样点击的时候,就也会下载了。

<?php
    $filename = ‘header.txt‘;
    header(‘Content-Type: text/plain‘);   // 类型为普通文本
    header(‘Content-Disposition:attachment; filename="$filename"‘);  // Content-Disposition:attachment,告诉它这是附件
    header(‘Content-Length:‘.filesize($filename));   // 告知文件大小

    readfile($filename);  // 读取文件直接输出,便于下载

实际上不要最后一行也可。这样即便是文本文件,它也会弹出保存框让你保存,连下载框都没有!

涉及到头信息的东西,要补补HTTP协议的知识,最近事多人累,咽喉肿痛,就酱紫。

时间: 2024-10-09 21:14:35

PHP之文件的锁定、上传与下载的相关文章

Java实现FTP文件与文件夹的上传和下载

FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”.用于Internet上的控制文件的双向传输.同时,它也是一个应用程序(Application).基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件.在FTP的使用当中,用户经常遇到两个概念:"下载"(Download)和"上传"(Upload)."下载"文件就是从远程主机拷贝文件至自己的计算机上:&quo

Java实现FTP文件与文件夹的上传和下载1

Java实现FTP文件与文件夹的上传和下载 http://www.cnblogs.com/winorgohome/archive/2016/11/22/6088013.html FTP 是File Transfer Protocol(文件传输协议)的英文简称,而中文简称为“文传协议”.用于Internet上的控制文件的双向传输.同时,它也是一个应用程序(Application).基于不同的操作系统有不同的FTP应用程序,而所有这些应用程序都遵守同一种协议以传输文件.在FTP的使用当中,用户经常遇

利用putty实现文件在linux上传和下载

利用putty实现文件上传和下载:1.打开windows命令提示符窗口d:(putty在d盘下)cd putty(pscp.exe所在目录)2:上传(主要利用pscp程序)pscp d:/jdk-8u111-linux-x64.tar.gz [email protected]:/usr/javafiles/pscp d:/demo.war [email protected]:/usr/javafiles/3:下载(主要利用pscp程序)pscp [email protected]:/usr/ja

JS开发——文件夹的上传和下载

文件夹上传:从前端到后端 文件上传是 Web 开发肯定会碰到的问题,而文件夹上传则更加难缠.网上关于文件夹上传的资料多集中在前端,缺少对于后端的关注,然后讲某个后端框架文件上传的文章又不会涉及文件夹.今天研究了一下这个问题,在此记录. 先说两个问题: 是否所有后端框架都支持文件夹上传? 是否所有浏览器都支持文件夹上传? 第一个问题:YES,第二个问题:NO 只要后端框架对于表单的支持是完整的,那么必然支持文件夹上传.至于浏览器,截至目前,只有 Chrome 支持. 如果需要其它的浏览器支持则需要

基于commons-net实现ftp创建文件夹、上传、下载功能

原文:http://www.open-open.com/code/view/1420774470187 package com.demo.ftp; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.PrintWriter

asp.net web开发——文件夹的上传和下载

ASP.NET上传文件用FileUpLoad就可以,但是对文件夹的操作却不能用FileUpLoad来实现. 下面这个示例便是使用ASP.NET来实现上传文件夹并对文件夹进行压缩以及解压. ASP.NET页面设计:TextBox和Button按钮. TextBox中需要自己受到输入文件夹的路径(包含文件夹),通过Button实现选择文件夹的问题还没有解决,暂时只能手动输入. 两种方法:生成rar和zip. 1.生成rar using Microsoft.Win32; using System.Di

使用MFC WinInet进行FTP中文件的简单上传和下载功能

建立基于对话框的MFC应用程序CMfcFtpWinInetDlg: 1.首先Dlg类中包含头文件 #include "afxinet.h" 2.添加成员变量: C++ Code 123   private: CFtpConnection*     m_pFtpConnection; CInternetSession    m_Session; 3.在OnInitDialog中加入Ftp连接代码: C++ Code 123456789101112131415161718   // TOD

secureCRT上传、下载文件

secureCRT上传和下载命令分别为rz.sz 我的理解是rz,就是服务器接收(received),sz,就是服务器发送(send). secureCRT上的命令就是: 下载 sz filename(filename是你要下载的文件名,前提要进入那个文件所在的目录) 下载下来的文件存放的目录设置下图可见: 上传 rz再选择你要上传的文件. secureCRT上传.下载文件,布布扣,bubuko.com

ftp上传与下载

在企业级应用系统中,文件上传与下载是最常用的功能之一.当然,这些文件也是有存储周期,根据业务需要而定. 驾驶员培训管理系统(简称驾培系统)需要判断学员学时的有效性,通过学时记录仪,采集学员在教练车中里照片, 上传到web服务器中,由web服务器上传到文件服务器,,比便做人脸比对..为保证学时的实时性,这期间会产生多次比对,当 然也会产生许多学员人脸照片.为方便管理这些照片,我们会采用分层级目录来存放照片. 这里主要介绍基于commons-net实现ftp创建文件夹.上传.下载功能. 创建文件夹