简体   繁体   English

PHP和Ajax的进度条

[英]Progress bar with PHP & Ajax

I am working on progress bar which updates progress using ajax requests and session variables. 我正在使用进度条,使用ajax请求和会话变量更新进度。 When my program performs time consuming operation such as sending many emails etc. it just set proper session variable (which contains progress value). 当我的程序执行耗时的操作,如发送许多电子邮件等时,它只是设置适当的会话变量(包含进度值)。 This operation is started by function post() in code below. 此操作由下面的代码中的函数post()启动。

In the meantime, the second function ask() is performed in loop every 500ms. 同时,第二个函数ask()每500ms循环执行一次。 It should return current progress in real time. 它应该实时返回当前进度。 And here is the problem: every request sent by ask() are waiting for request sent by post() function is finished. 这就是问题:ask()发送的每个请求都在等待post()函数发送的请求完成。 The funny part is that if I set some URL like google.com instead of url/to/progress it works just fine except that it is not what I want :). 有趣的是,如果我设置一些像google.com这样的URL而不是url / to / progress它可以正常工作,除非它不是我想要的:)。 Which means that problem is on the server side. 这意味着问题出在服务器端。

Not sure if it's important but I use Yii Framework. 不确定它是否重要,但我使用的是Yii Framework。

All the code below is only scratch (but working) and its only purpose is to show what I meant. 下面的所有代码只是划痕(但有效),其唯一目的是展示我的意思。

Thanks in advance. 提前致谢。

Sorry for my poor english :) 抱歉我的英语不好:)

View part: 查看部分:

<script type="text/javascript">
function ask() {
  var d = new Date();
  var time = d.getTime();
  $.ajax({

    type: 'get',
    url: '/url/to/progress' + '?time=' + time,
    success: function(data) {
      $("#progress").html(data);
    }
  })
}

function post() {
  var d = new Date();
  var time = d.getTime();

  $.ajax({
      type: 'post',
      url: '/url/to/post' + '?time=' + time,
      data: {"some": "data"},
      success: function(data) {alert(data)}
    });
}

$("#test").click(
  function() {
    post();
    var progress = setInterval("ask();", 500);
  }
);
</script>

Controller part: 控制器部分:

public function actionPost($time) {
  sleep(5); // time consuming operation
  echo $time . ' : ' . microtime();
  exit;
}

public function actionProgress($time) {
  echo $time . ' : ' . microtime();
  exit;
}

I think your problem here is session related. 我认为你的问题与会话有关。

When a script has an open session, it has a lock on the session file. 当脚本具有打开的会话时,它会锁定会话文件。 This means that any subsequent requests which use the same session ID will be queued until the first script has released it's lock on the session file. 这意味着任何使用相同会话ID的后续请求都将排队,直到第一个脚本释放它对会话文件的锁定为止。 You can force this with session_write_close() - but this won't really help you here, as you are trying to share the progress info with the session file so post script would need to keep the session data open and writable. 您可以使用session_write_close()强制执行此操作 - 但这不会对您有所帮助,因为您尝试与会话文件共享进度信息,因此post脚本需要保持会话数据的开放性和可写性。

You will need to come up with another way of sharing data between the post and progress scripts - if post has the session data open throughout it's execution, progress will never be able to access the session data until after post has finished executing. 您需要提出另一种在postprogress脚本之间共享数据的方法 - 如果post在整个执行期间打开了会话数据,那么在post完成执行之后, progress将永远无法访问会话数据。 Maybe you could use the session ID to create a temporary file which post has write access to, in which you put the progress indicator data. 也许你可以使用会话ID来创建该临时文件post已至,在你把进度指示器数据写入访问。 The progress can check the file and return that data. progress可以检查文件并返回该数据。 There are many options for IPC (inter-process communication) - this is not a particularly beautiful one but it does have the advantage of maximum portability. IPC(进程间通信)有很多选择 - 这不是一个特别漂亮的选项,但它确实具有最大的可移植性。

As a side note - please don't pass strings to setInterval() , pass functions. 作为旁注 - 请不要将字符串传递给setInterval() ,传递函数。 So your line should actually read: 所以你的行实际上应该是:

var progress = setInterval(ask, 500);

But - it would be better to use setTimeout() in the success / error handlers of the ask() ajax function. 但是 - 最好在ask() ajax函数的success / error处理程序中使用setTimeout() This is because by using setInterval() , a new request will be initiated regardless of the state of the previous one. 这是因为通过使用setInterval() ,无论前一个请求的状态如何,都将启动一个新请求。 It would be more efficient to wait until the previous request has finished before initiating the next one. 在开始下一个请求之前等待上一个请求完成会更有效。 So I would do something more like this: 所以我会做更像这样的事情:

<script type="text/javascript">

  // We'll set this to true when the initail POST request is complete, so we
  // can easily know when to stop polling the server for progress updates
  var postComplete = false;

  var ask = function() {

    var time = new Date().getTime();

    $.ajax({

      type: 'get',
      url: '/url/to/progress' + '?time=' + time,

      success: function(data) {
        $("#progress").html(data);
        if (!postComplete)
          setTimeout(ask, 500);
        }
      },
      error: function() {
        // We need an error handler as well, to ensure another attempt gets scheduled
        if (!postComplete)
          setTimeout(ask, 500);
        }
      }

    });

  }

  $("#test").click(function() {

    // Since you only ever call post() once, you don't need a seperate function.
    // You can just put all the post() code here.

    var time = new Date().getTime();

    $.ajax({

      type: 'post',
      url: '/url/to/post' + '?time=' + time,
      data: {
        "some": "data"
      },

      success: function(data) {
        postComplete = true;
        alert(data);
      }
      error: function() {
        postComplete = true;
      }

    });

    if (!postComplete)
      setTimeout(ask, 500);
    }

  });

</script>

...although this still doesn't fix the session problem. ...虽然这仍然无法解决会话问题。

@DaveRandom above correctly points out that you are a victim of a session storage lock. 上面的@DaveRandom正确地指出你是会话存储锁的受害者。

The workaround is quite simple. 解决方法非常简单。 You want to make the script that processes post() release the lock on the session data so that the script that handles ask() can access this session data. 您希望使处理post()的脚本释放对会话数据的锁定,以便处理ask()的脚本可以访问此会话数据。 You can do that with session_write_close . 你可以使用session_write_close来做到这一点。

The fine print here is that after calling session_write_close you will not have access to session variables, so you need to structure the script for post accordingly: 这里的精细打印是,在调用session_write_close您将无法访问会话变量,因此您需要相应地构建脚本以进行post

  1. Read all data you will need from $_SESSION and save a copy of it. $_SESSION读取您需要的所有数据并保存它的副本。
  2. Call session_write_close to release the session lock. 调用session_write_close以释放会话锁定。
  3. Continue your lengthy operation. 继续你的冗长操作。 If you need session data, get it out of your own copy and not $_SESSION directly. 如果您需要会话数据,请将其从您自己的副本中取出,而不是直接从$_SESSION

Alternatively, you can toggle the lock on the session multiple times during the script's lifetime: 或者,您可以在脚本的生命周期内多次切换会话锁定:

session_start();
$_SESSION['name'] = 'Jon';

// Quick operation that requires session data
echo 'Hello '.$_SESSION['name'];

//  Release session lock
session_write_close();

// Long operation that does not require session data.
sleep(10);

// Need access to session again
session_start();
echo 'Hello again '.$_SESSION['name'];

This arrangement makes it so that while the script it sleeping, other scripts can access the session data without problems. 这种安排使得当它睡眠的脚本时,其他脚本可以毫无问题地访问会话数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM