【问题标题】:Limiting Parallel/Simultaneous Downloads - How to know if download was cancelled?限制并行/同时下载 - 如何知道下载是否被取消?
【发布时间】:2011-10-23 04:47:41
【问题描述】:

我有一个简单的文件上传服务,用 PHP 编写,它还包括一个脚本,当用户请求从该站点下载时,它通过发送有限大小的数据包来控制下载速度。

如果他们不是高级会员,我想实施一个系统,将每个用户的并行/同时下载限制为 1 个。在上面的下载脚本中,我可以使用 MySQL 数据库来存储具有以下内容的记录: (1) 用户 ID; (2) 档案编号; (3) 何时开始下载; (4) 发送最后一个数据包的时间,每次完成时都会更新(如果 DL 速度限制为 150 kB/秒,那么在每 150 kB 之后,更新此记录,等等)。

然而,到目前为止,数据库记录只有在下载成功完成后才会被删除——在脚本结束时,下载完成后,记录会从表中删除:

insert DB record;
while (download is being served) {
    serve packet of data;
    update DB record with current date/time;
}
// Download is now complete
delete DB record;

我如何能够检测到下载何时被取消?我是否只需要有一个 Cron 作业(或类似的东西)来检测现有下载记录是否超过 X 分钟/小时?或者还有什么我可以做但我想念的事情吗?

我希望我已经解释得足够清楚了。我认为不需要发布特定代码;我对如何/是否可以做到这一点的后勤工作更感兴趣。如果需要具体的,我很乐意提供。

注意:我知道如何检测文件是否成功下载;我需要知道如何检测它是否被取消、中止或以其他方式停止(而不仅仅是暂停)。 这将有助于停止并行下载,以及防止用户取消下载 #1 并尝试启动下载 #2,却发现网站声称他仍在下载文件 #1。强>

编辑:你可以在这里找到我的下载脚本:http://codetidy.com/1319/——它已经支持多部分下载和下载恢复。

【问题讨论】:

    标签: php mysql download


    【解决方案1】:
    <?php
    
    class DownloadObserver
    {
      protected $file;
      public function __construct($file) {
        $this->file = $file;
      }
    
      public function send() {
        // -> note in DB you've started
        readfile($this->file);
      }
    
      public function __destruct() {
        // download is done, either completed or aborted
        $aborted = connection_aborted();
        // -> note in DB
      }
    }
    
    $dl = new DownloadObserver("/tmp/whatever");
    $dl->send();
    

    应该可以正常工作。不需要 shutdown_function 或任何时髦的自建连接观察。

    【讨论】:

    • 我假设public function send() 还将包含限制下载大小、处理恢复下载等的脚本?在__destruct() 函数中,我可以比较$bytes_sent$bytes_total,看看它是被中止/取消还是成功完成,对吧?这看起来干净多了。
    • 你猜对了。由于您已经解决了其他问题,我只是指出了面向对象的方法(特别是 __destruct())来解决您的“抓住事物的结局”问题。
    • 我相信这是我应该采取的最佳方向。它是面向对象的、整洁的、易于阅读的。这些天我正在尝试使用尽可能多的OO代码,所以这是最好的答案。谢谢大家!
    • 这是我从 WebDAV 实现中提取的一些内容:gist.github.com/1307754(不,我没有测试代码,而且我已经很久没有使用它了。它可能会给你更多提示,通过)
    【解决方案2】:

    您需要检查以下函数:connection_status()、connection_aborted() 和 ignore_user_abort()(有关详细信息,请参阅 PHP 手册的 connection handling 部分)。

    虽然我不能保证可靠性(我已经有一段时间没有使用它了),但通过正确的组合,你应该能够完成你想要的。不过,在处理这些问题时有一些注意事项,最重要的是,如果出现问题,您最终可能会在服务器上运行滞留的 PHP 脚本,要求您杀死 Apache 以阻止它们。

    以下内容应该让您对如何做有一个很好的了解(改编自 PHP 代码示例和几个 cmets):

    <?php
    //Set PHP not to cancel execution if the connection is aborted
    //and drop the time limit to allow for big file downloads
    ignore_user_abort(true);
    set_time_limit(0);
    
    while(true){
        //See the ignore_user_abort() docs re having to send data
        echo chr(0);
    
        //Make sure the data gets flushed properly or the connection check won't work
        flush();
        ob_flush();
    
        //Check then connection status and exit loop if aborted
        if(connection_status() != CONNECTION_NORMAL || connection_aborted()) break;
    
        //Just to provide some spacing in this example
        sleep(1);
    }
    
    file_put_contents("abort.txt", "aborted\n", FILE_APPEND);
    
    //Never hurts to ensure that the script halts execution
    die();
    

    显然,对于您将如何使用它,发送的数据只是下载数据块(只需确保正确刷新缓冲区以确保实际发送数据)。据我所知,没有办法区分暂停和中止/停止。暂停/恢复功能(和多部分下载 - 即下载管理器如何加速下载)依赖于“范围”标头,基本上请求文件的字节 x 到字节 y。因此,如果您想允许可恢复下载,您也必须处理这个问题。

    【讨论】:

    • register_shutdown_function 在这方面有用吗?在我当前的代码中,我有一个注释掉的部分,我必须从我获得代码的站点复制它,它说在中止/完成下载时使用它来运行特定功能。你可以在这里找到我的下载脚本:codetidy.com/1319(它传递了一个“ID”GET参数)
    • 我想我能做的是在这个脚本中有两个全局 PHP 变量来存储 (1) 到目前为止已经提供了多少字节,以及 (2) 文件总共有多少字节。在关闭函数中,如果 bytes_served dl.php 在向用户提供下载时仍在运行,即使页面本身没有在选项卡中打开。
    • die() 并退出 可以 受伤。例如,当 PHP 在 fcgid 中运行时 - 每次退出都会杀死原本可缓存的进程。浪费性能和填充日志。
    • 我的download_file 函数末尾有一个die() 函数。我假设这将调用register_shutdown_function() 函数。我已经支持恢复下载,其中发送的字节数和总字节数都被跟踪。我假设我可以简单地注册一个检查这两个值的关闭函数,对吧?
    • 这样做可以让您更好地控制知道何时中止连接以及何时可以停止提供文件。另外,您将能够在此处处理中止的进程,而不必尝试使用 __destructs 或关闭函数来捕获它。如果在 while 循环之后您只是添加了 if(connection_aborted()){//do cleanup},您发布的脚本会不会完全正常工作?
    【解决方案3】:

    默认情况下不发送 HTTP“取消”信号。所以,看起来你需要决定一个超时,一个连接可以在不发送/接收另一个数据包的情况下停留的时间长度。如果您要发送相当小的数据包(我猜您是这样),请保持较短的超时时间以达到最佳效果。

    在您的 while 条件下,您需要检查上次时间戳更新的年龄,如果太旧,请停止发送文件。

    【讨论】:

    • 嗯,我还必须检查记录是否仍然存在(即检查过期下载记录的 cron 作业尚未删除它)。如果它不存在,请结束下载(和脚本),因为它被视为已过期。听起来对吗?
    猜你喜欢
    • 2021-10-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-10-18
    • 2016-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多