大文件下载的实现
大文件下载的实现
1、大文件下载的基本思路
通过使用浏览器下载文件与打包为zip格式的方式来实现大文件下载的功能,其基本要求和建议如下:(1) 下载文件时,浏览器弹出的"文件下载"提示框要快,增加用户体验。
(2) 下载过程,不能占用较多的内存资源,提高资源的使用效率。
(3) 考虑支持大于2GB的文件,即使用64位的函数或操作。
(4) 推荐使用PHP实现,其实现过程相对简单,效率较高。
2、单文件下载: 使用浏览器下载的功能
<?php function file_download($file) { $file_name = basename($file); if (!file_exists($file)) { echo "$file is not exists."; return false; } else { $fd = fopen($file, "rb"); header("Content-type: application/octet-stream"); header("Accept-Ranges: bytes"); header("Accept-Length: " . filesize($file)); header("Content-Disposition: attachment; filename=" . $file_name); while (!feof($fd)) { ob_clean(); echo fread($fd, 4096); ob_flush(); } fclose($fd); } ob_end_flush(); return true; } ?>3、多文件打包下载
(1) 经过测试验证owncloud打包下载大文件的过程:
owncloud 能够正常打包下载3.2GB 和 10.6GB大小的文件,下载过程系统正常,没有出现内存占用过高或CPU使用过高的情况,浏览器弹出的提示信息很快,其他状态正常。
(2) 参考并使用owncloud download的源代码: https://owncloud.org/
(3) 分析owncloud download的源代码:
a. 下载文件时,浏览器快速的弹出"文件下载"提示框:
<?php $down_name = "demo.zip"; $type = "attachment"; $expires = 0; header("Content-Disposition: " . rawurlencode($type) . "; filename*=UTF-8""" . rawurlencode($down_name) . "; filename="" . rawurlencode($down_name) . """ ); header("Content-Transfer-Encoding: binary"); header("Pragma: public"); // enable caching in IE header("Expires: " . $expires); header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); header("Content-Type: application/zip"); ?>
b. 设置脚本运行时间为无限制:
<?php set_time_limit(0); ?>
c. 包含类库 & 实例化ZipStreamer:
<?php require("ZipStreamer.php"); $zip = new ZipStreamer(false); ?>d. 打包文件或文件夹为zip格式的下载包
<?php if (is_file($file)) { $fh = fopen($file, "r"); $zip_file = "demo.zip"; $zip->addFileFromStream($fh, $zip_file); fclose($fh); } else if (is_dir($file)) { zipAddDir($file, $zip); } ?>e. 添加zip文件的尾部内容
<?php $zip->finalize(); // End of zip file ?>
(4) zip文件格式的分析与说明: http://blog.csdn.net/wclxyn/article/details/7288994
(5) 参考ZipStreamer.php的代码如下:
<?php /** * Class to create zip files on the fly and stream directly to the HTTP client as the content is added. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * * Inspired by * CreateZipFile by Rochak Chauhan www.rochakchauhan.com (http://www.phpclasses.org/browse/package/2322.html) * and * ZipStream by A. Grandt https://github.com/Grandt/PHPZip (http://www.phpclasses.org/package/6116) * * Unix-File attributes according to * http://unix.stackexchange.com/questions/14705/the-zip-formats-external-file-attribute * * @author Nicolai Ehemann <en@enlightened.de> * @author André Rothe <arothe@zks.uni-leipzig.de> * @copyright Copyright (C) 2013-2014 Nicolai Ehemann and contributors * @license GNU GPL * @version 0.4 */ class ZipStreamer { const VERSION = "0.4"; const ZIP_LOCAL_FILE_HEADER = 0x04034b50; // local file header signature const ZIP_CENTRAL_FILE_HEADER = 0x02014b50; // central file header signature const ZIP_END_OF_CENTRAL_DIRECTORY = 0x06054b50; // end of central directory record const ZIP64_END_OF_CENTRAL_DIRECTORY = 0x06064b50; //zip64 end of central directory record const ZIP64_END_OF_CENTRAL_DIR_LOCATOR = 0x07064b50; // zip64 end of central directory locator //TODO: make this dynamic, depending on flags/compression methods const ATTR_VERSION_TO_EXTRACT = 0x2d; // version needed to extract (min. 4.5) const ATTR_MADE_BY_VERSION = 0x032d; // made by version (upper byte: UNIX, lower byte v4.5) const STREAM_CHUNK_SIZE = 1048576; // 1mb chunks const INT64_HIGH_MAP = 0xffffffff00000000; const INT64_LOW_MAP = 0x00000000ffffffff; private $extFileAttrFile; private $extFileAttrDir; /** @var array central directory record */ private $cdRec = array(); /** @var int offset of next file to be added */ private $offset; /** @var boolean indicates zip is finalized and sent to client; no further addition possible */ private $isFinalized = false; /** * Constructor. * * @param bool $sendHeaders Send suitable headers to the HTTP client (assumes nothing was sent yet) * @param string $archiveName Name to send to the HTTP client. Optional, defaults to "archive.zip". * @param string $contentType Content mime type. Optional, defaults to "application/zip". */ function __construct($sendHeaders = false, $archiveName = "archive.zip", $contentType = "application/zip") { //TODO: is this advisable/necessary? if (ini_get("zlib.output_compression")) { ini_set("zlib.output_compression", "Off"); } //usiNas_Log(__FILE__, __LINE__, "$sendHeaders = " . json_encode($sendHeaders, JSON_FORCE_OBJECT)); if ($sendHeaders) { $headerFile = null; $headerLine = null; if (!headers_sent($headerFile, $headerLine) or die("<p><strong>Error:</strong> Unable to send file " . "$archiveName. HTML Headers have already been sent from " . "<strong>$headerFile</strong> in line <strong>$headerLine" . "</strong></p>")) { if ((ob_get_contents() === false || ob_get_contents() == "") or die(" <p><strong>Error:</strong> Unable to send file " . "<strong>$archiveName.epub</strong>. Output buffer " . "already contains text (typically warnings or errors).</p>")) { header("Pragma: public"); header("Last-Modified: " . gmdate("D, d M Y H:i:s T")); header("Expires: 0"); header("Accept-Ranges: bytes"); header("Connection: Keep-Alive"); header("Content-Type: " . $contentType); header("Content-Disposition: attachment; filename="" . $archiveName . "";"); header("Content-Transfer-Encoding: binary"); } } flush(); // turn off output buffering ob_end_flush(); } // initialize default external file attributes $this->extFileAttrFile = UNIX::getExtFileAttr(UNIX::S_IFREG | UNIX::S_IRUSR | UNIX::S_IXUSR | UNIX::S_IRGRP | UNIX::S_IROTH); $this->extFileAttrDir = UNIX::getExtFileAttr(UNIX::S_IFDIR | UNIX::S_IRWXU | UNIX::S_IRGRP | UNIX::S_IXGRP | UNIX::S_IROTH | UNIX::S_IXOTH) | DOS::getExtFileAttr(DOS::DIR); $this->offset = Count64::construct(0); } function __destruct() { $this->isFinalized = true; $this->cdRec = null; exit; } /** * Add a file to the archive at the specified location and file name. * * @param string $stream Stream to read data from * @param string $filePath Filepath and name to be used in the archive. * @param int $timestamp (Optional) Timestamp for the added file, if omitted or set to 0, the current time will be used. * @param string $fileComment (Optional) Comment to be added to the archive for this file. To use fileComment, timestamp must be given. * @param bool $compress (Optional) Compress file, if set to false the file will only be stored. Default FALSE. * @return bool $success */ public function addFileFromStream($stream, $filePath, $timestamp = 0, $fileComment = null, $compress = false) { if ($this->isFinalized) { return false; } if (!is_resource($stream) || get_resource_type($stream) != "stream") { return false; } $filePath = self::normalizeFilePath($filePath); //usiNas_Log(__FILE__, __LINE__, "$filePath = " . $filePath); $gpFlags = GPFLAGS::ADD; if ($compress) { $gzMethod = GZMETHOD::DEFLATE; } else { $gzMethod = GZMETHOD::STORE; } //usiNas_Log(__FILE__, __LINE__, "$gzMethod = " . $gzMethod); list($gpFlags, $lfhLength) = $this->beginFile($filePath, $fileComment, $timestamp, $gpFlags, $gzMethod); list($dataLength, $gzLength, $dataCRC32) = $this->streamFileData($stream, $compress); // build cdRec $this->cdRec[] = $this->buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $dataCRC32, $this->extFileAttrFile); // calc offset $this->offset->add($lfhLength)->add($gzLength); return true; } /** * Add an empty directory entry to the zip archive. * * @param string $directoryPath Directory Path and name to be added to the archive. * @param int $timestamp (Optional) Timestamp for the added directory, if omitted or set to 0, the current time will be used. * @param string $fileComment (Optional) Comment to be added to the archive for this directory. To use fileComment, timestamp must be given. * @return bool $success */ public function addEmptyDir($directoryPath, $timestamp = 0, $fileComment = null) { if ($this->isFinalized) { return false; } $directoryPath = self::normalizeFilePath($directoryPath) . "/"; if (strlen($directoryPath) > 0) { $gpFlags = 0x0000; // Compression type 0 = stored $gzMethod = GZMETHOD::STORE; // Compression type 0 = stored list($gpFlags, $lfhLength) = $this->beginFile($directoryPath, $fileComment, $timestamp, $gpFlags, $gzMethod); // build cdRec $this->cdRec[] = $this->buildCentralDirectoryHeader($directoryPath, $timestamp, $gpFlags, $gzMethod, 0, 0, 0, $this->extFileAttrDir); // calc offset $this->offset->add($lfhLength); return true; } return false; } /** * Close the archive. * A closed archive can no longer have new files added to it. * @return bool $success */ public function finalize() { if (!$this->isFinalized) { // print central directory $cd = implode("", $this->cdRec); echo $cd; // print the zip64 end of central directory record echo $this->buildZip64EndOfCentralDirectoryRecord(strlen($cd)); // print the zip64 end of central directory locator echo $this->buildZip64EndOfCentralDirectoryLocator(strlen($cd)); // print end of central directory record echo $this->buildEndOfCentralDirectoryRecord(); flush(); $this->isFinalized = true; $cd = null; $this->cdRec = null; return true; } return false; } private function beginFile($filePath, $fileComment, $timestamp, $gpFlags = 0x0000, $gzMethod = GZMETHOD::STORE, $dataLength = 0, $gzLength = 0, $dataCRC32 = 0) { $isFileUTF8 = mb_check_encoding($filePath, "UTF-8") && !mb_check_encoding($filePath, "ASCII"); $isCommentUTF8 = !empty($fileComment) && mb_check_encoding($fileComment, "UTF-8") && !mb_check_encoding($fileComment, "ASCII"); if ($isFileUTF8 || $isCommentUTF8) { $gpFlags |= GPFLAGS::EFS; } $localFileHeader = $this->buildLocalFileHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $dataCRC32); echo $localFileHeader; return array($gpFlags, strlen($localFileHeader)); } private function streamFileData($stream, $compress) { $dataLength = Count64::construct(0); $gzLength = Count64::construct(0); $hashCtx = hash_init("crc32b"); //usiNas_Log(__FILE__, __LINE__, "$compress = " . json_encode($compress, JSON_FORCE_OBJECT)); while (!feof($stream)) { $data = fread($stream, self::STREAM_CHUNK_SIZE); $dataLength->add(strlen($data)); hash_update($hashCtx, $data); if ($compress) { //TODO: this is broken. $data = gzdeflate($data); } $gzLength->add(strlen($data)); echo $data; flush(); } $crc = unpack("N", hash_final($hashCtx, true)); return array($dataLength, $gzLength, $crc[1]); } private function buildZip64ExtendedInformationField($dataLength = 0, $gzLength = 0) { return "" . $this->pack16le(0x0001) // tag for this "extra" block type (ZIP64) 2 bytes (0x0001) . $this->pack16le(28) // size of this "extra" block 2 bytes . $this->pack64le($dataLength) // original uncompressed file size 8 bytes . $this->pack64le($gzLength) // size of compressed data 8 bytes . $this->pack64le($this->offset) // offset of local header record 8 bytes . $this->pack32le(0); // number of the disk on which this file starts 4 bytes } private function buildLocalFileHeader($filePath, $timestamp, $gpFlags = 0x0000, $gzMethod = GZMETHOD::STORE, $dataLength, $gzLength, $dataCRC32 = 0) { $dosTime = self::getDosTime($timestamp); $zip64Ext = $this->buildZip64ExtendedInformationField($dataLength, $gzLength); return "" . $this->pack32le(self::ZIP_LOCAL_FILE_HEADER) // local file header signature 4 bytes (0x04034b50) . $this->pack16le(self::ATTR_VERSION_TO_EXTRACT) // version needed to extract 2 bytes . $this->pack16le($gpFlags) // general purpose bit flag 2 bytes . $this->pack16le($gzMethod) // compression method 2 bytes . $this->pack32le($dosTime) // last mod file time 2 bytes // last mod file date 2 bytes . $this->pack32le($dataCRC32) // crc-32 4 bytes . $this->pack32le(-1) // compressed size 4 bytes . $this->pack32le(-1) // uncompressed size 4 bytes . $this->pack16le(strlen($filePath)) // file name length 2 bytes . $this->pack16le(strlen($zip64Ext)) // extra field length 2 bytes . $filePath // file name (variable size) . $zip64Ext; // extra field (variable size) } private function buildZip64EndOfCentralDirectoryRecord($cdRecLength) { $cdRecCount = sizeof($this->cdRec); return "" . $this->pack32le(self::ZIP64_END_OF_CENTRAL_DIRECTORY) // zip64 end of central dir signature 4 bytes (0x06064b50) . $this->pack64le(44) // size of zip64 end of central directory // record 8 bytes . $this->pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes . $this->pack16le(self::ATTR_VERSION_TO_EXTRACT) //version needed to extract 2 bytes . $this->pack32le(0) // number of this disk 4 bytes . $this->pack32le(0) // number of the disk with the start of the // central directory 4 bytes . $this->pack64le($cdRecCount) // total number of entries in the central // directory on this disk 8 bytes . $this->pack64le($cdRecCount) // total number of entries in the // central directory 8 bytes . $this->pack64le($cdRecLength) // size of the central directory 8 bytes . $this->pack64le($this->offset) // offset of start of central directory // with respect to the starting disk number 8 bytes . ""; // zip64 extensible data sector (variable size) } private function buildZip64EndOfCentralDirectoryLocator($cdRecLength) { $zip64RecStart = Count64::construct($this->offset)->add($cdRecLength); return "" . $this->pack32le(self::ZIP64_END_OF_CENTRAL_DIR_LOCATOR) // zip64 end of central dir locator signature 4 bytes (0x07064b50) . $this->pack32le(0) // number of the disk with the start of the // zip64 end of central directory 4 bytes . $this->pack64le($zip64RecStart) // relative offset of the zip64 end of // central directory record 8 bytes . $this->pack32le(1); // total number of disks 4 bytes } private function buildCentralDirectoryHeader($filePath, $timestamp, $gpFlags, $gzMethod, $dataLength, $gzLength, $dataCRC32, $extFileAttr) { $dosTime = self::getDosTime($timestamp); $zip64Ext = $this->buildZip64ExtendedInformationField($dataLength, $gzLength); return "" . $this->pack32le(self::ZIP_CENTRAL_FILE_HEADER) //central file header signature 4 bytes (0x02014b50) . $this->pack16le(self::ATTR_MADE_BY_VERSION) //version made by 2 bytes . $this->pack16le(self::ATTR_VERSION_TO_EXTRACT) //version needed to extract 2 bytes . $this->pack16le($gpFlags) //general purpose bit flag 2 bytes . $this->pack16le($gzMethod) //compression method 2 bytes . $this->pack32le($dosTime) //last mod file time 2 bytes //last mod file date 2 bytes . $this->pack32le($dataCRC32) //crc-32 4 bytes . $this->pack32le(-1) //compressed size 4 bytes . $this->pack32le(-1) //uncompressed size 4 bytes . $this->pack16le(strlen($filePath)) //file name length 2 bytes . $this->pack16le(strlen($zip64Ext)) //extra field length 2 bytes . $this->pack16le(0) //file comment length 2 bytes . $this->pack16le(-1) //disk number start 2 bytes . $this->pack16le(0) //internal file attributes 2 bytes . $this->pack32le($extFileAttr) //external file attributes 4 bytes . $this->pack32le(-1) //relative offset of local header 4 bytes . $filePath //file name (variable size) . $zip64Ext //extra field (variable size) //TODO: implement? . ""; //file comment (variable size) } private function buildEndOfCentralDirectoryRecord() { return "" . $this->pack32le(self::ZIP_END_OF_CENTRAL_DIRECTORY) // end of central dir signature 4 bytes (0x06064b50) . $this->pack16le(-1) // number of this disk 2 bytes . $this->pack16le(-1) // number of the disk with the // start of the central directory 2 bytes . $this->pack16le(-1) // total number of entries in the // central directory on this disk 2 bytes . $this->pack16le(-1) // total number of entries in the // central directory 2 bytes . $this->pack32le(-1) // size of the central directory 4 bytes . $this->pack32le(-1) // offset of start of central // directory with respect to the // starting disk number 4 bytes . $this->pack16le(0) // .ZIP file comment length 2 bytes //TODO: implement? . ""; // .ZIP file comment (variable size) } // Utility methods //////////////////////////////////////////////////////// private static function normalizeFilePath($filePath) { return trim(str_replace("\", "/", $filePath), "/"); } /** * Calculate the 2 byte dostime used in the zip entries. * * @param int $timestamp * @return 2-byte encoded DOS Date */ private static function getDosTime($timestamp = 0) { $timestamp = (int) $timestamp; $oldTZ = @date_default_timezone_get(); date_default_timezone_set("UTC"); $date = ($timestamp == 0 ? getdate() : getdate($timestamp)); date_default_timezone_set($oldTZ); if ($date["year"] >= 1980) { return (($date["mday"] + ($date["mon"] << 5) + (($date["year"] - 1980) << 9)) << 16) | (($date["seconds"] >> 1) + ($date["minutes"] << 5) + ($date["hours"] << 11)); } return 0x0000; } /** * Pack 2 byte data into binary string, little endian format * * @param mixed $data data * @return string 2 byte binary string */ private static function pack16le($data) { return pack("v", $data); } /** * Pack 4 byte data into binary string, little endian format * * @param mixed $data data * @return 4 byte binary string */ private static function pack32le($data) { return pack("V", $data); } /** * Pack 8 byte data into binary string, little endian format * * @param mixed $data data * @return string 8 byte binary string */ private static function pack64le($data) { if (is_object($data)) { if ("Count64_32" == get_class($data)) { $value = $data->_getValue(); $hiBytess = $value[0]; $loBytess = $value[1]; } else { $hiBytess = ($data->_getValue() & self::INT64_HIGH_MAP) >> 32; $loBytess = $data->_getValue() & self::INT64_LOW_MAP; } } else { $hiBytess = ($data & self::INT64_HIGH_MAP) >> 32; $loBytess = $data & self::INT64_LOW_MAP; } return pack("VV", $loBytess, $hiBytess); } } abstract class Count64Base { function __construct($value = 0) { $this->set($value); } abstract public function set($value); abstract public function add($value); abstract public function _getValue(); const EXCEPTION_SET_INVALID_ARGUMENT = "Count64 object can only be set() to integer or Count64 values"; const EXCEPTION_ADD_INVALID_ARGUMENT = "Count64 object can only be add()ed integer or Count64 values"; } class Count64_32 extends Count64Base{ private $loBytes; private $hiBytes; public function _getValue() { return array($this->hiBytes, $this->loBytes); } public function set($value) { if (is_int($value)) { $this->loBytes = $value; $this->hiBytes = 0; } else if (is_object($value) && __CLASS__ == get_class($value)) { $value = $value->_getValue(); $this->hiBytes = $value[0]; $this->loBytes = $value[1]; } else { throw Exception(self::EXCEPTION_SET_INVALID_ARGUMENT); } return $this; } public function add($value) { if (is_int($value)) { $sum = (int)($this->loBytes + $value); // overflow! if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1) || ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) { $this->hiBytes = (int)($this->hiBytes + 1); } $this->loBytes = $sum; } else if (is_object($value) && __CLASS__ == get_class($value)) { $value = $value->_getValue(); $sum = (int)($this->loBytes + $value[1]); if (($this->loBytes > -1 && $sum < $this->loBytes && $sum > -1) || ($this->loBytes < 0 && ($sum < $this->loBytes || $sum > -1))) { $this->hiBytes = (int)($this->hiBytes + 1); } $this->loBytes = $sum; $this->hiBytes = (int)($this->hiBytes + $value[0]); } else { throw Exception(self::EXCEPTION_ADD_INVALID_ARGUMENT); } return $this; } } class Count64_64 extends Count64Base { private $value; public function _getValue() { return $this->value; } public function set($value) { if (is_int($value)) { $this->value = $value; } else if (is_object($value) && __CLASS__ == get_class($value)) { $this->value = $value->_getValue(); } else { throw Exception(self::EXCEPTION_SET_INVALID_ARGUMENT); } return $this; } public function add($value) { if (is_int($value)) { $this->value = (int)($this->value + $value); } else if (is_object($value) && __CLASS__ == get_class($value)) { $this->value = (int)($this->value + $value->_getValue()); } else { throw Exception(self::EXCEPTION_ADD_INVALID_ARGUMENT); } return $this; } } abstract class Count64 { public static function construct($value = 0) { //usiNas_Log(__FILE__, __LINE__, "PHP_INT_SIZE = " . PHP_INT_SIZE); if (4 == PHP_INT_SIZE) { return new Count64_32($value); } else { return new Count64_64($value); } } } abstract class ExtFileAttr { # ZIP external file attributes layout # TTTTsstrwxrwxrwx0000000000ADVSHR # ^^^^____________________________ UNIX file type # ^^^_________________________ UNIX setuid, setgid, sticky # ^^^^^^^^^________________ UNIX permissions # ^^^^^^^^________ "lower-middle byte" (TODO: what is this?) # ^^^^^^^^ DOS attributes (reserved, reserved, archived, directory, volume, system, hidden, read-only public static function getExtFileAttr($attr) { return $attr; } } class UNIX extends ExtFileAttr { # Octal const S_IFIFO = 0010000; /* named pipe (fifo) */ const S_IFCHR = 0020000; /* character special */ const S_IFDIR = 0040000; /* directory */ const S_IFBLK = 0060000; /* block special */ const S_IFREG = 0100000; /* regular */ const S_IFLNK = 0120000; /* symbolic link */ const S_IFSOCK = 0140000; /* socket */ const S_ISUID = 0004000; /* set user id on execution */ const S_ISGID = 0002000; /* set group id on execution */ const S_ISTXT = 0001000; /* sticky bit */ const S_IRWXU = 0000700; /* RWX mask for owner */ const S_IRUSR = 0000400; /* R for owner */ const S_IWUSR = 0000200; /* W for owner */ const S_IXUSR = 0000100; /* X for owner */ const S_IRWXG = 0000070; /* RWX mask for group */ const S_IRGRP = 0000040; /* R for group */ const S_IWGRP = 0000020; /* W for group */ const S_IXGRP = 0000010; /* X for group */ const S_IRWXO = 0000007; /* RWX mask for other */ const S_IROTH = 0000004; /* R for other */ const S_IWOTH = 0000002; /* W for other */ const S_IXOTH = 0000001; /* X for other */ const S_ISVTX = 0001000; /* save swapped text even after use */ public static function getExtFileAttr($attr) { return parent::getExtFileAttr($attr) << 16; } } class DOS extends ExtFileAttr { const READ_ONLY = 0x1; const HIDDEN = 0x2; const SYSTEM = 0x4; const VOLUME = 0x8; const DIR = 0x10; const ARCHIVE = 0x20; const RESERVED1 = 0x40; const RESERVED2 = 0x80; } class GPFLAGS { const ADD = 0x0008; // ADD flag (sizes and crc32 are append in data descriptor) const EFS = 0x0800; // EFS flag (UTF-8 encoded filename and/or comment) } class GZMETHOD { const STORE = 0x0000; // 0 - The file is stored (no compression) const DEFLATE = 0x0008; // 8 - The file is Deflated }
声明:该文观点仅代表作者本人,牛骨文系教育信息发布平台,牛骨文仅提供信息存储空间服务。
- 上一篇: PHP超大文件下载,断点续传下载
- 下一篇: PHP 读取 大文件 解决方法