假设一个场景:

当用户需要通过我们提供的下载服务,来下载一个较大的文件(200M-2G)时,我们服务端应该如何来满足这个服务呢?

且当我们的服务端是采用nginx+php的架构时,该如何解决呢。

作为服务端接口层,我们需要从数据层(可能是云存储,可能是类似于亚马逊S3的存储服务)下载较大文件(200M--2G),然后将下载得到的文件,返回给请求客户端。

且当我们的服务端接口层是采用nginx+php的架构时,该如何解决呢。

如下图所示:

![](https://box.kancloud.cn/26bf25aecda7ea0a40f3609e74e2bf9f_518x190.png)

最初的解决方法(但无法彻底解决):

很多同学最初的解决办法,可能会直接采用curl函数,得到返回内容之后,echo 这个返回内容,结束php程序,然后nginx收到php的echo结果,再返回给请求端。

如下图所示:

![](https://box.kancloud.cn/dd1e53e83dc8186ff2716b732c3374e8_608x165.png)

这段下载代码很简单,如下:

$ch = \curl_init($url);

\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 获取数据返回

\curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); // 在启用 CURLOPT_RETURNTRANSFER 时候将获取数据返回

\curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArr);

\curl_setopt($ch, CURLOPT_HEADER, 1);

\curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);

\curl_setopt($ch, CURLOPT_TIMEOUT, 60);

$output = \curl_exec($ch);

echo $output;

这样做,对下载较小的文件,不会有啥问题。但是当需要下载的文教较大时,就会出现许多错误:

1、php进程内存溢出:

由于是将整个文件全部下载,并且放在$output变量里面,该变量是存在于内存中的,所以一旦文件过大,就会导致php内存溢出。(许多同学会想到更改php内存限制,但是这毕竟不是一个长久之计,且不说下载上G的文件,会给服务器带来多少压力;如果并发下载,可能会很快将系统内存消耗殆尽,导致服务挂掉)

2、下载时间过长,nginx没有得到响应,导致nginx向请求端报错(httpcode 504报错)

由于按照上述方法下载,只有当下载完成后,才会将结果返回响应给nginx,而由于下载文件较大,耗时过长,整个php进程的执行时间就会较大。nginx在接受到客户端请求之后,将请求发送给php-cgi(通过fast-cgi运行)程序,然后等待php进程返回,但nginx并不会一直等待,在nginx中有一个配置项,fastcgi_read_timeout,该配置项决定了nginx等待fast-cgi返回超时阈值,默认是60秒。当一个请求超过60秒没有处理完并且响应的话,nginx就会按照请求超时的逻辑报错(504 timeout报错 )。(当然同样也可以通过修改这个配置项的值来避免超时的出现,但是这仍然不是一种最优的选择,因为这样客户端会等待很久,并且在整个等待过程中,没有任何响应信息)

如图所示:

![](https://box.kancloud.cn/be96d96d0e7e1cecca56a4b6277a6744_609x165.png)

所以考虑到通过该方法下载,有至少两个较为致命的缺陷,我们给出了第二种解决办法。

第二种解决办法:

简单地说:通过curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch ,$str))方法,将文件一段一段返回。

具体如下:

curl_setopt()函数中,有一个配置叫做:CURLOPT_WRITEFUNCTION,通过这个配置,可以实现,一个curl回调方法:php官方手册是这样描述该配置的:A callback accepting two parameters. The first is the cURL resource, and the second is a string with the data to be written. The data must be saved by this callback. It must return the exact number of bytes written or the transfer will be aborted with an error(大致的中文意思就是:该回调接受两个参数。第一个是cURL资源,第二个是具有要写入的数据的字符串。数据必须通过此回调保持。它必须写入确切字节数,否则传输将中止并出现错误)

这样的描述可能会让读者觉得困惑,别急,我们通过代码进行分析:

$ch = \curl_init($url);

\curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

\curl_setopt($ch, CURLOPT_BINARYTRANSFER, true);

\curl_setopt($ch, CURLOPT_HTTPHEADER, $headerArr);

\curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);

\curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 60);

\curl_setopt($ch, CURLOPT_TIMEOUT, 3600);

curl_setopt($ch, CURLOPT_BUFFERSIZE, 20971520);

$flag=0;

curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch ,$str) use (&$flag){

$len = strlen($str);

$flag++;

$type = \curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

if($flag==1){

$size = \curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);

$type = \curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

$httpcode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);

\header("HTTP/1.1 ".$httpcode);

\header("Content-Type: ".$type);

\header("Content-Length: ".$size);

\header('Content-Transfer-Encoding: binary');

\header('Cache-Control:max-age=2592000');

}

echo $str;

return $len;

});

$output = \curl_exec($ch);

核心部分就在最后一个curl_setopt中,可以看到这里配置了CURLOPT_WRITEFUNCTION,紧接着是一个函数,

function($ch ,$str) use (&$flag){

$len = strlen($str);

$flag++;

if($flag==1){

$size = \curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);

$type = \curl_getinfo($ch, CURLINFO_CONTENT_TYPE);

$httpcode = \curl_getinfo($ch, CURLINFO_HTTP_CODE);

\header("HTTP/1.1 ".$httpcode);

\header("Content-Type: ".$type);

\header("Content-Length: ".$size);

\header('Content-Transfer-Encoding: binary');

\header('Cache-Control:max-age=2592000');

}

echo $str;

return $len;

}

这个函数就是之前讲到的回调函数。

该函数需要传入两个参数,也就是上面提到的,cURL资源,以及字符串。curl资源不必赘述,着重说一下字符串$str。

当程序执行到\curl_exec($ch);处时,整个curl网络请求就开始了,当请求端的数据到达我们服务端的那一刻,数据会被放入一个缓冲区,由于是大文件,所以数据会源源不断的持续到达,当缓冲区收到一定数量的返回值后,就会调用这个回调函数,并将返回得到的数据传入这里的$str变量。我们获取这个$str变量,并将它echo就可以瞬时发送给nginx响应,nginx拿到响应数据,立即会返回到客户端,这样就能很好的解决上面提到的两个致命缺陷,1、php进程内存消耗不会持续增加,2,nginx不会响应超时。回调函数的末尾有一个return $len; 这里的$len是$str的长度;我们需要返回这个长度,也就是之前提到的,必须写入确切的字符串。其实也很好理解,我们将字符串的长度抛出,下一次调用回调函数时,才能正确定位接下来该从哪里传值。

另外,我加入了一个flag作为标记,通过use(&flag)的方式(有点类似于c语言中的引用),可将flag作为变量,并在函数中不断累加,起到标记作用。当flag为1时,我们尝试去获取http的返回头,拿到响应码,文件类型,文件长度,然后进行拼接成我们想要返回给客户端的http响应头信息,这里还加上了缓存时间\header('Cache-Control:max-age=2592000');方便请求端做缓存过期策略。

简单总结:

整个大文件下载过程,策略就到此结束,该方法可以一举解决内存占用过大,nginx响应超时,客户端无响应时间过长三个问题。还需要注意一点,在设置CURLOPT_TIMEOUT,需要适当调大一些,这样对应耗时较长的超大文件下载,才不会在中途断开。至于中途断开该如何进行断点续传,我们下一篇章再接着讲述。

php curl 传输大文件,空白目录 · php下载大文件curl · 看云相关推荐

  1. Linux常用命令(本篇包括,Linux目录结构介绍、Linux Shell介绍、9个常见命令介绍、文件的概念、文件的操作(20个)、目录的操作、文件和目录的权限、文件压缩及解压缩)

    Linux常用命令(本篇包括,Linux目录结构介绍.Linux Shell介绍.9个常见命令介绍.文件的概念.文件的操作(20个).目录的操作.文件和目录的权限.文件压缩及解压缩)         ...

  2. linux各个文件夹作用是什么,我的世界游戏文件夹目录作用介绍 各个文件夹都是什么用...

    我的世界游戏文件夹目录作用介绍 各个文件夹都是什么用.那下面给大家介绍的则是我的世界游戏文件夹目录索引的内容哦~那到底在我的世界文件夹中各个文件夹都有设么作用呢?那下面一一介绍一下吧! 游戏园我的世界 ...

  3. Serv-U FTP Jail Break(越权遍历目录、下载任意文件)

    [*]----------------------------------------------------[*] Serv-U FTP Server Jail Break 0day Discove ...

  4. Python文件及目录操作(基本文件操作篇)

    ​ 活动地址:CSDN21天学习挑战赛 学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩:迟一天就多一天平庸的困扰. 学习日记 目录 学习日记 一.前言 二.基本文件操作 1.创建和打开文件 2. ...

  5. WebClient上传文件至服务器和下载服务器文件至客户端

    #region WebClient上传文件至服务器 /// <summary> /// WebClient上传文件至服务器 /// </summary> /// <par ...

  6. 更改 onedrive 同步文件夹;如何下载整个文件夹

    点赞和关注是我创作的最大动力~~ 之前 onedrive 的同步文件夹是在 C 盘 / 系统盘中,在加了 1T 的固态硬盘条后,想把同步文件夹放在新盘中. 如果之前电脑上安装有 onedrive,在小 ...

  7. linux文件和目录基本管理系统,Linux文件基本操作管理和系统目录结构

    1.cp 复制文件或目录 cp 源文件(文件夹) 目标文件(文件夹) 例如cp linuxcast linuxcast2 cp linuxcast linuxcast.net/ 往linuxcast. ...

  8. Windows 技术篇 - 无需确认快速删除包含大量文件的目录,cmd删除文件的rmdir、del、erase和rd四种命令使用方法

    一些非固态硬盘的机器,传统的删除大量的文件会非常耗时. 使用 rmdir /S /Q D:\delete 命令可以无需确认更快速的进行删除操作,其中 D:\delete 表示要删除的文件夹. rmdi ...

  9. python 删除文件、目录_python实现删除文件与目录的方法

    本文实例讲述了python实现删除文件与目录的方法.分享给大家供大家参考.具体实现方法如下: os.remove(path)删除文件 path. 如果path是一个目录, 抛出 OSError错误.如 ...

最新文章

  1. 浅谈浏览器多进程与JS线程
  2. Kotlin学习 PART 2:kotlin基础
  3. 面试常备题----数组总结篇(上)
  4. 用pandas填充时间序列缺失值
  5. 2014年JAVA省赛B组---第四题---大衍数列
  6. Bootstrap FileInput(文件上传)中文API整理
  7. 22岁何同学自制硬核AirDesk!苹果都做不来的超大充电桌,稚晖君点赞
  8. Java 面向对象:重写的理解
  9. word 的使用(六)—— 常用功能
  10. 通信原理基础概念概述
  11. 100部超级好电影,100组优秀的字体设计(不看后悔系列)
  12. 解决:Assign object to a variable before exporting as module default
  13. 前端三剑客---HTML
  14. php后台您没有权限访问该页面,登陆微信公共平台提示没有权限访问该页面请点击返回首页现象的解决办法...
  15. ubuntu14.04上安装Mist
  16. HTML基础——table标签
  17. 计算机教师结构化方式面试,“中学信息技术学科”题目如何答?教师资格结构化面试...
  18. ​分享|Tiktok小店入驻如何选择​
  19. 解决chrt: failed to set pid 0‘s policy: Operation not permitted
  20. Long monitor contention with owner

热门文章

  1. oracle 跨服务器推送视图_oracle跨数据库视图
  2. python中函数的调用_慢步python,编程中函数的概念,python中函数的声明和调用
  3. ajax id sort,ajax返回的json内容进行排序使用sort()方法实现
  4. 计算机三级网络技术知识点大纲,全国计算机等级考试三级网络技术考试大纲(2019年版)...
  5. java线程池深入讲解_死磕 java线程系列之线程池深入解析——生命周期
  6. loj2538 「PKUWC2018」Slay the Spire 【dp】
  7. vs2012 boost配置
  8. debian卸载vmware
  9. 统计SQL2005中数据库中的每张表的记录数
  10. BZOJ2212——线段树合并