你能弄清楚这个PHP计时问题吗?

时间:2021-09-07 01:01:50

Can anyone tell me why when I ran a script with the below contents and then stop it after 5 seconds that I need to divide the elapsed time by 2 to get the correct script execution time?

任何人都可以告诉我为什么当我运行具有以下内容的脚本然后在5秒后停止它,我需要将经过时间除以2以获得正确的脚本执行时间?

ignore_user_abort(true); set_time_limit(0); 

$begin_time = microtime(true);

$elapsed_time = 0;

while(!connection_aborted()) {
    echo ' ';
    flush();
    usleep(1000000);
}

$elapsed_time = microtime(true) - $begin_time;

$timer_seconds = $elapsed_time; //10 seconds

$timer_seconds = $elapsed_time / 2; //5 seconds


/*I am writing to a DB - but you can use this to test */
$fp = fopen('times.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fclose($fp);

Feel free to try the code as it has baffled me on why the $elapsed_time needs to be divided by two. Maybe I have misunderstood something?

随意尝试代码,因为它困扰我为什么$ elapsed_time需要除以2。也许我误会了什么?

Thanks all for any help

谢谢大家的帮助

Update

I have updated the code so that anyone can try this out and it will write to a text file to view the output.

我已更新代码,以便任何人都可以尝试这个,它将写入文本文件以查看输出。

6 个解决方案

#1


13  

Experiment:

Significant changes from original code:

原始代码发生重大变化:

1) Using implicit_flush and all buffers are flushed before doing anything.
2) Instead of outputting just a space, the code outputs the iteration number and 1023 bytes of other data to tell the browser that we have good amount of output to display. A normal known trick.
3) Along with saving the time in the output text file, it also saves the total iterations that the code ran.

1)使用implicit_flush并在执行任何操作之前刷新所有缓冲区。 2)代码不输出空格,而是输出迭代次数和1023字节的其他数据,告诉浏览器我们要显示的输出量很大。一个正常的已知技巧。 3)除了节省输出文本文件中的时间外,它还节省了代码运行的总迭代次数。

The code used:

使用的代码:

<?php
// Tricks to allow instant output
@ini_set('implicit_flush', 1);
for ($i = 0; $i < ob_get_level(); $i++)
    ob_end_flush();
ob_implicit_flush(1);

//Your Code starts here
ignore_user_abort(true);
set_time_limit(0); 

$begin_time = microtime(true);
$elapsed_time = 0;

while(!connection_aborted())
{
    //this I changed, so that a looooong string is outputted
    echo $i++.str_repeat(' ',1020).'<br/>';
    flush();
    usleep(1000000);
}

$elapsed_time = microtime(true) - $begin_time;
$timer_seconds = $elapsed_time; //10 seconds

//Writes to file the number of ITERATIONS too along with time
$fp = fopen('4765107.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fwrite($fp, "\nIterations: ".$i);
fclose($fp);
?>

Live Demo:


What I got:

1) When code is run for 10 iterations and STOP button on browser is clicked, the output file shows 13 iterations with ~ 13.01 seconds taken.

1)当代码运行10次迭代并且单击浏览器上的STOP按钮时,输出文件显示13次迭代,采用~13.01秒。

2) When code is run for 20 iterations and STOP button on browser is clicked, the output file shows 23 iterations with ~ 23.01 seconds taken.

2)当代码运行20次迭代并且单击浏览器上的STOP按钮时,输出文件显示23次迭代,采用~23.01秒。


Inferences & Conclusion:

1) The script actually does NOT stops when the STOP button is clicked but after 2-4 seconds of clicking it. So, there are more iterations that what appears in the browser.

1)当单击STOP按钮但单击2-4秒后,脚本实际上不会停止。因此,浏览器中会出现更多迭代。

2) The number of iterations is SAME as the number of seconds it takes to execute, as shown in output file.

2)迭代次数为SAME,即执行所需的秒数,如输出文件所示。

Therefore, there is no error and apparently no bugs, it's just the latency time between clicking the STOP button and the script actually stopping.

因此,没有错误,显然没有错误,只是单击STOP按钮和实际停止的脚本之间的延迟时间。


Notes:

1) Server: A Linux VPS.
2) Clients tested: Firefox and Chrome.
3) As the script ends 2-4 seconds after STOP is clicked, it takes around 3-4 seconds for the output file to be updated for the current test.

1)服务器:Linux VPS。 2)客户端测试:Firefox和Chrome。 3)当脚本在单击STOP后2-4秒结束时,输出文件需要大约3-4秒才能更新当前测试。

#2


9  

Summary: (this post became epic as I tested various avenues)

摘要:(这篇文章在我测试各种途径时变成了史诗)

PHP takes -typically two- while-loop iterations to detect a disconnect or to deliver output. This delay could come from the web server software, host computer, client computer, and client browser but then it should vary depending on the sleep per iteration. More likely the delay is coming from internal execution or output process of PHP (perhaps from a small internal buffer or the interrupt handling process).

PHP通常需要两次循环迭代来检测断开连接或提供输出。这种延迟可能来自Web服务器软件,主机,客户端计算机和客户端浏览器,但它应根据每次迭代的睡眠而变化。更可能的延迟来自PHP的内部执行或输出过程(可能来自一个小的内部缓冲区或中断处理过程)。

Epic Post:

Counting your execution time from [Refresh] or URL-submit isn't exactly an accurate starting point - any number of steps may be required first and could add to the delay:

从[Refresh]或URL-submit计算执行时间并不是一个准确的起点 - 可能需要先执行任意数量的步骤,这可能会增加延迟:

  1. DNS lookup required (with TCP overhead)
  2. 需要DNS查找(使用TCP开销)

  3. TCP connection established with server
  4. 与服务器建立TCP连接

  5. Web Server creates a thread or child
  6. Web Server创建一个线程或子级

  7. Web Server decides how to deal with the request
  8. Web Server决定如何处理请求

  9. PHP may need to start up
  10. PHP可能需要启动

  11. PHP may need to convert your source into opcode
  12. PHP可能需要将您的源转换为操作码

So rather than measuring [Refresh] -> [Stop] time and comparing it to the number recorded by PHP, I measured displayed output to recorded output - which reduces the delay measurement to mostly just within PHP (although Server/Browser will effect). The modified script can't run away (it terminates after a fixed number of iterations), clears the default php.ini buffering, and reports an iteration count on screen and in the timing file. I ran the script with various $sleep periods to see the effects. The final script:

因此,不是测量[刷新] - > [停止]时间并将其与PHP记录的数字进行比较,而是将显示的输出测量到记录的输出 - 这将延迟测量减少到大部分只在PHP内(尽管服务器/浏览器会起作用)。修改后的脚本无法运行(它在固定次数的迭代后终止),清除默认的php.ini缓冲,并在屏幕和计时文件中报告迭代计数。我用各种$ sleep周期运行脚本来查看效果。最后的剧本:

<?php
date_default_timezone_set('America/Los_Angeles'); //Used by ob apparently
ignore_user_abort(true); //Don't terminate script because user leaves
set_time_limit(0); //Allow runaway script !danger !danger
while (@ob_end_flush()) {}; //By default set on/4K in php.ini

$start=microtime(true);

$n=1000;
$i=0;
$sleep=100000;// 0.1s
while(!connection_aborted() && $i<$n) {
    echo "\n[".++$i."]";flush();
    usleep($sleep);
}

$end=microtime(true);

file_put_contents("timing.txt",
    "#\$sleep=".($sleep/1000000).
      "s\n/ s=$start / e=$end ($i) / d=".($end-$start)."\n",
    FILE_APPEND);
?>

Results: (multiple runs concatenated, run in Firefox)

结果:(连续多次运行,在Firefox中运行)

# On-screen $i / start utime / end utime (current $i) / delta utime

#$sleep=1s
2 / s=1296251342.5729 / e=1296251346.5721 (4) / d=3.999242067337
3 / s=1296251352.9094 / e=1296251357.91 (5) / d=5.000559091568
#$sleep=0.1s
11 / s=1296251157.982 / e=1296251159.2896 (13) / d=1.3075668811798
8 / s=1296251167.5659 / e=1296251168.5709 (10) / d=1.0050280094147
16 / s=1296251190.0493 / e=1296251191.8599 (18) / d=1.810576915741
4 / s=1296251202.7471 / e=1296251203.3505 (6) / d=0.60339689254761
16 / s=1296251724.5782 / e=1296251726.3882 (18) / d=1.8099851608276
#$sleep=0.01s
42 / s=1296251233.0498 / e=1296251233.5217 (44) / d=0.47195816040039
62 / s=1296251260.4463 / e=1296251261.1336 (64) / d=0.68735003471375
150 / s=1296251279.2656 / e=1296251280.901 (152) / d=1.6353850364685
379 / s=1296252444.7587 / e=1296252449.0108 (394) / d=4.2521529197693
#$sleep=0.001s
337 / s=1296251293.4823 / e=1296251294.1515 (341) / d=0.66925406455994
207 / s=1296251313.7312 / e=1296251314.1445 (211) / d=0.41328597068787
792 / s=1296251324.5233 / e=1296251326.0915 (795) / d=1.5682451725006

(Opera doesn't display numbers during, but displays final numbers which roughly match)
(Chrome doesn't display anything during/after the test)
(Safari doesn't display anything during/after the test)
(IE doesn't display anything during/after the test)

(Opera不显示数字,但显示大致匹配的最终数字)(Chrome在测试期间/之后不显示任何内容)(Safari在测试期间/之后不显示任何内容)(IE不显示任何内容)在测试期间/之后)

The first number on each line indicates the number displayed on screen once [stop] was pressed (manually recorded).

每行上的第一个数字表示按下[停止]后手动记录的屏幕上显示的数字。

A few points:

几点:

  • Your stop point is quantized to the nearest $sleep period (1/10s in the above script) because the script only checks at the beginning of each while iteration, there's some variation because the usleep method isn't a perfect delay.
  • 您的停止点被量化为最接近的$ sleep时段(上述脚本中的1 / 10s),因为脚本仅在每次迭代开始时检查,因为usleep方法不是完美的延迟,所以存在一些变化。

  • The browser and server you're using makes a difference. The flush manual page notes "may not be able to override the buffering scheme of your web server, and has no effect on any client-side buffering in the browser." Then goes into more detail about both server and client issues. [Server: WinXPsp3 / Apache 2.2.17 / PHP 5.3.3 Client: WinXPsp3 / FireFox 3.6.13]
  • 您正在使用的浏览器和服务器有所不同。刷新手册页注释“可能无法覆盖Web服务器的缓冲方案,并且不会影响浏览器中的任何客户端缓冲。”然后详细介绍服务器和客户端问题。 [服务器:WinXPsp3 / Apache 2.2.17 / PHP 5.3.3客户端:WinXPsp3 / FireFox 3.6.13]

Analysis:

In all but the 0.001s delay we're seeing a 2 iteration delay between [stop] and PHP catching it (or Firefox or Apache reporting). With a 0.001s delay it varies a bit, the average being ~4 iterations or 0.004s - this is probably getting close to detection speed threshold.

除了0.001s延迟之外,我们在[stop]和PHP捕获它(或Firefox或Apache报告)之间看到了2次迭代延迟。延迟为0.001s时,它会变化一点,平均值为~4次迭代或0.004s - 这可能接近检测速度阈值。

When the delay time is 0.1s or above we're seeing an execution time closely matching $sleep * {recorded iterations}.

当延迟时间为0.1秒或更长时,我们看到执行时间与$ sleep * {记录的迭代}紧密匹配。

When the delay time is below 0.1s we're seeing execution delays greater than the sleep time. This is likely from the cost of checking client-connection, incrementing $i, outputting text, and flushing the buffer per iteration. The discrepancy between the execution time and $i*$sleep is pretty linear suggesting it takes ~0.001s/iteration to complete these tasks (with a 0.01s sleep it's 0.0008, while a 0.001s sleep works out to 0.0010 - probably a result of increased MALLOC/output).

当延迟时间低于0.1s时,我们发现执行延迟大于睡眠时间。这可能来自检查客户端连接,增加$ i,输出文本以及每次迭代刷新缓冲区的成本。执行时间和$ i * $睡眠之间的差异是非常线性的,表明完成这些任务需要大约0.001秒/迭代(0.01s睡眠时为0.0008,而0.001s睡眠时间为0.0010 - 可能是增加MALLOC /输出)。

#3


6  

You're relying on connection_aborted() to be true the moment you hit the 'Stop' button in your browser, but have shown no evidence that you have verified that this is the case. In fact, it is not.

当您点击浏览器中的“停止”按钮时,您依赖connection_aborted()是真的,但没有证据表明您已经验证了这种情况。事实上,事实并非如此。

You've forgotten how "connection aborted" checking happens in networking. An application (php in this case) doesn't know what's happened until it tries to write to the pipe.

您忘记了网络中“连接中止”检查的方式。应用程序(在这种情况下为php)在尝试写入管道之前不知道发生了什么。

The first comment on the documentation for connection_abort() says: "In order to detect a disconnection inside the script we need to flush the buffer (it is only when the the server tries to send the buffer content that it will see that the connection is broken)."

关于connection_abort()文档的第一条评论说:“为了检测脚本内部的断开连接,我们需要刷新缓冲区(只有当服务器尝试发送缓冲区内容时才会看到连接是破碎)。”

So I do not believe that you could reliably use connection_abort() in this way.

所以我不相信你能以这种方式可靠地使用connection_abort()。

Rest assured, microtime() works properly.

请放心,microtime()正常工作。

#4


4  

connection_aborted() can only detect disconnection when the buffer is sent. But flush() does not necessarily sent the buffer. So the loop keeps iterating until the buffer is filled and indeed flushed.

connection_aborted()只能在发送缓冲区时检测断开连接。但flush()不一定发送缓冲区。因此循环不断迭代,直到缓冲区被填满并且确实被刷新。

For more details please refer to the manual pages of the named functions.

有关更多详细信息,请参阅指定功能的手册页。

#5


0  

Using your script out of the box, does not work properly on Ubuntu, using Chrome to access the page. The loop just continues, had to restart Apache. On the other end, adding ob_end_flush() at the top, resolves that issue, plus the timer is actually correct.

开箱即用使用您的脚本,在Ubuntu上无法正常运行,使用Chrome访问该页面。循环只是继续,不得不重启Apache。另一方面,在顶部添加ob_end_flush()可以解决该问题,而且计时器实际上是正确的。

ob_end_flush();
ignore_user_abort(true); 
set_time_limit(0);

$begin_time = microtime(true);

$elapsed_time = 0;

while(!connection_aborted()) {
    echo ' ';
    flush();
    usleep(1000000);
    error_log("looping");
}

$elapsed_time = microtime(true) - $begin_time;

$timer_seconds = $elapsed_time; 
error_log(var_export($timer_seconds, true));


$timer_seconds = $elapsed_time / 2; 
error_log(var_export($timer_seconds, true));

If you run this and look at the error log, you'll see that $elpased_time is correct on the first run, no need to divide by too. As per why your code behaves like that, i don't know as it doesn't even work on my machine.

如果你运行它并查看错误日志,你会发现$ elpased_time在第一次运行时是正确的,不需要除以。根据你的代码行为的原因,我不知道,因为它甚至不能在我的机器上工作。

#6


0  

This is not an "Issue" so much as "by design"

这不是“问题”,而是“按设计”

It is a function of the way http works.

它是http工作方式的一个功能。

When sending a web page to a client (browser) the server 'MUST' send a content-length header. Now it cannot know the length of the content until it has got it all from the script.

当向客户端(浏览器)发送网页时,服务器“必须”发送内容长度标题。现在,除非从脚本中获取所有内容,否则无法知道内容的长度。

So servers will buffer the script output until the script finishes.

因此服务器将缓冲脚本输出,直到脚本完成。

This is where the vagaries come in. depending upon the server and even different versions of the same server, it may or may not check whether the client has disconnected on a regular basis and if it does, those checks can be at differing time intervals. which may even change depending on how busy the server is.

这是变幻莫测的进入的地方。取决于服务器甚至同一服务器的不同版本,它可能会或可能不会检查客户端是否定期断开连接,如果是,则这些检查可以是不同的时间间隔。甚至可能会根据服务器的繁忙程度而改变。

PHP has no control over the connection to the client, it can only ask the server if the connection still exists. The server may or may not tell it the truth. So the script continues to run (at the time the server may not know).

PHP无法控制与客户端的连接,它只能询问服务器连接是否仍然存在。服务器可能会也可能不会说实话。因此脚本继续运行(在服务器可能不知道的时候)。

So why did Mikushi's work after adding the ob_end_flush() at the START of the script?

那么为什么Mikushi的工作是在脚本的START处添加ob_end_flush()之后的呢?

Well thats because it turned on another feature of http called chunk transfer. This allows data to be sent in chunks each with a special version of the content-length header (it doesn't actually say that, it just sends the next chunk length)

好吧,因为它打开了另一个称为块转移的http功能。这允许数据以块的形式发送,每个块都有一个特殊版本的内容长度标题(它实际上并没有说,它只是发送下一个块长度)

Try Mikushi's script with Wireshark and you will see the encoding, an example is shown here

尝试使用Wireshark的Mikushi脚本,您将看到编码,此处显示了一个示例

HTTP/1.1 200 OK
Date: Tue, 01 Feb 2011 11:52:35 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8


7 <== this is the content-length 
<pre>0

2 <== this is the content-length 
1

2 ditto ...
2

2
3

2
4

2
5

So this means its impossible (yes Tomalak you got me :) ) to know when the server is going to terminate the connection and therefore return true for connection_aborted() until you test the actual server involved. Because each one is different. Even the web browser may do some stuff that delays the actual close, which may further muddy the issue.

所以这意味着它是不可能的(是的Tomalak,你让我:))知道什么时候服务器将终止连接,因此为connection_aborted()返回true,直到你测试相关的实际服务器。因为每个人都不同。即使是网络浏览器也可能会做一些延迟实际关闭的事情,这可能会使问题更加混乱。

DC

#1


13  

Experiment:

Significant changes from original code:

原始代码发生重大变化:

1) Using implicit_flush and all buffers are flushed before doing anything.
2) Instead of outputting just a space, the code outputs the iteration number and 1023 bytes of other data to tell the browser that we have good amount of output to display. A normal known trick.
3) Along with saving the time in the output text file, it also saves the total iterations that the code ran.

1)使用implicit_flush并在执行任何操作之前刷新所有缓冲区。 2)代码不输出空格,而是输出迭代次数和1023字节的其他数据,告诉浏览器我们要显示的输出量很大。一个正常的已知技巧。 3)除了节省输出文本文件中的时间外,它还节省了代码运行的总迭代次数。

The code used:

使用的代码:

<?php
// Tricks to allow instant output
@ini_set('implicit_flush', 1);
for ($i = 0; $i < ob_get_level(); $i++)
    ob_end_flush();
ob_implicit_flush(1);

//Your Code starts here
ignore_user_abort(true);
set_time_limit(0); 

$begin_time = microtime(true);
$elapsed_time = 0;

while(!connection_aborted())
{
    //this I changed, so that a looooong string is outputted
    echo $i++.str_repeat(' ',1020).'<br/>';
    flush();
    usleep(1000000);
}

$elapsed_time = microtime(true) - $begin_time;
$timer_seconds = $elapsed_time; //10 seconds

//Writes to file the number of ITERATIONS too along with time
$fp = fopen('4765107.txt', 'w');
fwrite($fp, 'Time Elapsed: '.$timer_seconds);
fwrite($fp, "\nIterations: ".$i);
fclose($fp);
?>

Live Demo:


What I got:

1) When code is run for 10 iterations and STOP button on browser is clicked, the output file shows 13 iterations with ~ 13.01 seconds taken.

1)当代码运行10次迭代并且单击浏览器上的STOP按钮时,输出文件显示13次迭代,采用~13.01秒。

2) When code is run for 20 iterations and STOP button on browser is clicked, the output file shows 23 iterations with ~ 23.01 seconds taken.

2)当代码运行20次迭代并且单击浏览器上的STOP按钮时,输出文件显示23次迭代,采用~23.01秒。


Inferences & Conclusion:

1) The script actually does NOT stops when the STOP button is clicked but after 2-4 seconds of clicking it. So, there are more iterations that what appears in the browser.

1)当单击STOP按钮但单击2-4秒后,脚本实际上不会停止。因此,浏览器中会出现更多迭代。

2) The number of iterations is SAME as the number of seconds it takes to execute, as shown in output file.

2)迭代次数为SAME,即执行所需的秒数,如输出文件所示。

Therefore, there is no error and apparently no bugs, it's just the latency time between clicking the STOP button and the script actually stopping.

因此,没有错误,显然没有错误,只是单击STOP按钮和实际停止的脚本之间的延迟时间。


Notes:

1) Server: A Linux VPS.
2) Clients tested: Firefox and Chrome.
3) As the script ends 2-4 seconds after STOP is clicked, it takes around 3-4 seconds for the output file to be updated for the current test.

1)服务器:Linux VPS。 2)客户端测试:Firefox和Chrome。 3)当脚本在单击STOP后2-4秒结束时,输出文件需要大约3-4秒才能更新当前测试。

#2


9  

Summary: (this post became epic as I tested various avenues)

摘要:(这篇文章在我测试各种途径时变成了史诗)

PHP takes -typically two- while-loop iterations to detect a disconnect or to deliver output. This delay could come from the web server software, host computer, client computer, and client browser but then it should vary depending on the sleep per iteration. More likely the delay is coming from internal execution or output process of PHP (perhaps from a small internal buffer or the interrupt handling process).

PHP通常需要两次循环迭代来检测断开连接或提供输出。这种延迟可能来自Web服务器软件,主机,客户端计算机和客户端浏览器,但它应根据每次迭代的睡眠而变化。更可能的延迟来自PHP的内部执行或输出过程(可能来自一个小的内部缓冲区或中断处理过程)。

Epic Post:

Counting your execution time from [Refresh] or URL-submit isn't exactly an accurate starting point - any number of steps may be required first and could add to the delay:

从[Refresh]或URL-submit计算执行时间并不是一个准确的起点 - 可能需要先执行任意数量的步骤,这可能会增加延迟:

  1. DNS lookup required (with TCP overhead)
  2. 需要DNS查找(使用TCP开销)

  3. TCP connection established with server
  4. 与服务器建立TCP连接

  5. Web Server creates a thread or child
  6. Web Server创建一个线程或子级

  7. Web Server decides how to deal with the request
  8. Web Server决定如何处理请求

  9. PHP may need to start up
  10. PHP可能需要启动

  11. PHP may need to convert your source into opcode
  12. PHP可能需要将您的源转换为操作码

So rather than measuring [Refresh] -> [Stop] time and comparing it to the number recorded by PHP, I measured displayed output to recorded output - which reduces the delay measurement to mostly just within PHP (although Server/Browser will effect). The modified script can't run away (it terminates after a fixed number of iterations), clears the default php.ini buffering, and reports an iteration count on screen and in the timing file. I ran the script with various $sleep periods to see the effects. The final script:

因此,不是测量[刷新] - > [停止]时间并将其与PHP记录的数字进行比较,而是将显示的输出测量到记录的输出 - 这将延迟测量减少到大部分只在PHP内(尽管服务器/浏览器会起作用)。修改后的脚本无法运行(它在固定次数的迭代后终止),清除默认的php.ini缓冲,并在屏幕和计时文件中报告迭代计数。我用各种$ sleep周期运行脚本来查看效果。最后的剧本:

<?php
date_default_timezone_set('America/Los_Angeles'); //Used by ob apparently
ignore_user_abort(true); //Don't terminate script because user leaves
set_time_limit(0); //Allow runaway script !danger !danger
while (@ob_end_flush()) {}; //By default set on/4K in php.ini

$start=microtime(true);

$n=1000;
$i=0;
$sleep=100000;// 0.1s
while(!connection_aborted() && $i<$n) {
    echo "\n[".++$i."]";flush();
    usleep($sleep);
}

$end=microtime(true);

file_put_contents("timing.txt",
    "#\$sleep=".($sleep/1000000).
      "s\n/ s=$start / e=$end ($i) / d=".($end-$start)."\n",
    FILE_APPEND);
?>

Results: (multiple runs concatenated, run in Firefox)

结果:(连续多次运行,在Firefox中运行)

# On-screen $i / start utime / end utime (current $i) / delta utime

#$sleep=1s
2 / s=1296251342.5729 / e=1296251346.5721 (4) / d=3.999242067337
3 / s=1296251352.9094 / e=1296251357.91 (5) / d=5.000559091568
#$sleep=0.1s
11 / s=1296251157.982 / e=1296251159.2896 (13) / d=1.3075668811798
8 / s=1296251167.5659 / e=1296251168.5709 (10) / d=1.0050280094147
16 / s=1296251190.0493 / e=1296251191.8599 (18) / d=1.810576915741
4 / s=1296251202.7471 / e=1296251203.3505 (6) / d=0.60339689254761
16 / s=1296251724.5782 / e=1296251726.3882 (18) / d=1.8099851608276
#$sleep=0.01s
42 / s=1296251233.0498 / e=1296251233.5217 (44) / d=0.47195816040039
62 / s=1296251260.4463 / e=1296251261.1336 (64) / d=0.68735003471375
150 / s=1296251279.2656 / e=1296251280.901 (152) / d=1.6353850364685
379 / s=1296252444.7587 / e=1296252449.0108 (394) / d=4.2521529197693
#$sleep=0.001s
337 / s=1296251293.4823 / e=1296251294.1515 (341) / d=0.66925406455994
207 / s=1296251313.7312 / e=1296251314.1445 (211) / d=0.41328597068787
792 / s=1296251324.5233 / e=1296251326.0915 (795) / d=1.5682451725006

(Opera doesn't display numbers during, but displays final numbers which roughly match)
(Chrome doesn't display anything during/after the test)
(Safari doesn't display anything during/after the test)
(IE doesn't display anything during/after the test)

(Opera不显示数字,但显示大致匹配的最终数字)(Chrome在测试期间/之后不显示任何内容)(Safari在测试期间/之后不显示任何内容)(IE不显示任何内容)在测试期间/之后)

The first number on each line indicates the number displayed on screen once [stop] was pressed (manually recorded).

每行上的第一个数字表示按下[停止]后手动记录的屏幕上显示的数字。

A few points:

几点:

  • Your stop point is quantized to the nearest $sleep period (1/10s in the above script) because the script only checks at the beginning of each while iteration, there's some variation because the usleep method isn't a perfect delay.
  • 您的停止点被量化为最接近的$ sleep时段(上述脚本中的1 / 10s),因为脚本仅在每次迭代开始时检查,因为usleep方法不是完美的延迟,所以存在一些变化。

  • The browser and server you're using makes a difference. The flush manual page notes "may not be able to override the buffering scheme of your web server, and has no effect on any client-side buffering in the browser." Then goes into more detail about both server and client issues. [Server: WinXPsp3 / Apache 2.2.17 / PHP 5.3.3 Client: WinXPsp3 / FireFox 3.6.13]
  • 您正在使用的浏览器和服务器有所不同。刷新手册页注释“可能无法覆盖Web服务器的缓冲方案,并且不会影响浏览器中的任何客户端缓冲。”然后详细介绍服务器和客户端问题。 [服务器:WinXPsp3 / Apache 2.2.17 / PHP 5.3.3客户端:WinXPsp3 / FireFox 3.6.13]

Analysis:

In all but the 0.001s delay we're seeing a 2 iteration delay between [stop] and PHP catching it (or Firefox or Apache reporting). With a 0.001s delay it varies a bit, the average being ~4 iterations or 0.004s - this is probably getting close to detection speed threshold.

除了0.001s延迟之外,我们在[stop]和PHP捕获它(或Firefox或Apache报告)之间看到了2次迭代延迟。延迟为0.001s时,它会变化一点,平均值为~4次迭代或0.004s - 这可能接近检测速度阈值。

When the delay time is 0.1s or above we're seeing an execution time closely matching $sleep * {recorded iterations}.

当延迟时间为0.1秒或更长时,我们看到执行时间与$ sleep * {记录的迭代}紧密匹配。

When the delay time is below 0.1s we're seeing execution delays greater than the sleep time. This is likely from the cost of checking client-connection, incrementing $i, outputting text, and flushing the buffer per iteration. The discrepancy between the execution time and $i*$sleep is pretty linear suggesting it takes ~0.001s/iteration to complete these tasks (with a 0.01s sleep it's 0.0008, while a 0.001s sleep works out to 0.0010 - probably a result of increased MALLOC/output).

当延迟时间低于0.1s时,我们发现执行延迟大于睡眠时间。这可能来自检查客户端连接,增加$ i,输出文本以及每次迭代刷新缓冲区的成本。执行时间和$ i * $睡眠之间的差异是非常线性的,表明完成这些任务需要大约0.001秒/迭代(0.01s睡眠时为0.0008,而0.001s睡眠时间为0.0010 - 可能是增加MALLOC /输出)。

#3


6  

You're relying on connection_aborted() to be true the moment you hit the 'Stop' button in your browser, but have shown no evidence that you have verified that this is the case. In fact, it is not.

当您点击浏览器中的“停止”按钮时,您依赖connection_aborted()是真的,但没有证据表明您已经验证了这种情况。事实上,事实并非如此。

You've forgotten how "connection aborted" checking happens in networking. An application (php in this case) doesn't know what's happened until it tries to write to the pipe.

您忘记了网络中“连接中止”检查的方式。应用程序(在这种情况下为php)在尝试写入管道之前不知道发生了什么。

The first comment on the documentation for connection_abort() says: "In order to detect a disconnection inside the script we need to flush the buffer (it is only when the the server tries to send the buffer content that it will see that the connection is broken)."

关于connection_abort()文档的第一条评论说:“为了检测脚本内部的断开连接,我们需要刷新缓冲区(只有当服务器尝试发送缓冲区内容时才会看到连接是破碎)。”

So I do not believe that you could reliably use connection_abort() in this way.

所以我不相信你能以这种方式可靠地使用connection_abort()。

Rest assured, microtime() works properly.

请放心,microtime()正常工作。

#4


4  

connection_aborted() can only detect disconnection when the buffer is sent. But flush() does not necessarily sent the buffer. So the loop keeps iterating until the buffer is filled and indeed flushed.

connection_aborted()只能在发送缓冲区时检测断开连接。但flush()不一定发送缓冲区。因此循环不断迭代,直到缓冲区被填满并且确实被刷新。

For more details please refer to the manual pages of the named functions.

有关更多详细信息,请参阅指定功能的手册页。

#5


0  

Using your script out of the box, does not work properly on Ubuntu, using Chrome to access the page. The loop just continues, had to restart Apache. On the other end, adding ob_end_flush() at the top, resolves that issue, plus the timer is actually correct.

开箱即用使用您的脚本,在Ubuntu上无法正常运行,使用Chrome访问该页面。循环只是继续,不得不重启Apache。另一方面,在顶部添加ob_end_flush()可以解决该问题,而且计时器实际上是正确的。

ob_end_flush();
ignore_user_abort(true); 
set_time_limit(0);

$begin_time = microtime(true);

$elapsed_time = 0;

while(!connection_aborted()) {
    echo ' ';
    flush();
    usleep(1000000);
    error_log("looping");
}

$elapsed_time = microtime(true) - $begin_time;

$timer_seconds = $elapsed_time; 
error_log(var_export($timer_seconds, true));


$timer_seconds = $elapsed_time / 2; 
error_log(var_export($timer_seconds, true));

If you run this and look at the error log, you'll see that $elpased_time is correct on the first run, no need to divide by too. As per why your code behaves like that, i don't know as it doesn't even work on my machine.

如果你运行它并查看错误日志,你会发现$ elpased_time在第一次运行时是正确的,不需要除以。根据你的代码行为的原因,我不知道,因为它甚至不能在我的机器上工作。

#6


0  

This is not an "Issue" so much as "by design"

这不是“问题”,而是“按设计”

It is a function of the way http works.

它是http工作方式的一个功能。

When sending a web page to a client (browser) the server 'MUST' send a content-length header. Now it cannot know the length of the content until it has got it all from the script.

当向客户端(浏览器)发送网页时,服务器“必须”发送内容长度标题。现在,除非从脚本中获取所有内容,否则无法知道内容的长度。

So servers will buffer the script output until the script finishes.

因此服务器将缓冲脚本输出,直到脚本完成。

This is where the vagaries come in. depending upon the server and even different versions of the same server, it may or may not check whether the client has disconnected on a regular basis and if it does, those checks can be at differing time intervals. which may even change depending on how busy the server is.

这是变幻莫测的进入的地方。取决于服务器甚至同一服务器的不同版本,它可能会或可能不会检查客户端是否定期断开连接,如果是,则这些检查可以是不同的时间间隔。甚至可能会根据服务器的繁忙程度而改变。

PHP has no control over the connection to the client, it can only ask the server if the connection still exists. The server may or may not tell it the truth. So the script continues to run (at the time the server may not know).

PHP无法控制与客户端的连接,它只能询问服务器连接是否仍然存在。服务器可能会也可能不会说实话。因此脚本继续运行(在服务器可能不知道的时候)。

So why did Mikushi's work after adding the ob_end_flush() at the START of the script?

那么为什么Mikushi的工作是在脚本的START处添加ob_end_flush()之后的呢?

Well thats because it turned on another feature of http called chunk transfer. This allows data to be sent in chunks each with a special version of the content-length header (it doesn't actually say that, it just sends the next chunk length)

好吧,因为它打开了另一个称为块转移的http功能。这允许数据以块的形式发送,每个块都有一个特殊版本的内容长度标题(它实际上并没有说,它只是发送下一个块长度)

Try Mikushi's script with Wireshark and you will see the encoding, an example is shown here

尝试使用Wireshark的Mikushi脚本,您将看到编码,此处显示了一个示例

HTTP/1.1 200 OK
Date: Tue, 01 Feb 2011 11:52:35 GMT
Server: Apache/2.2.3 (CentOS)
X-Powered-By: PHP/5.1.6
Connection: close
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8


7 <== this is the content-length 
<pre>0

2 <== this is the content-length 
1

2 ditto ...
2

2
3

2
4

2
5

So this means its impossible (yes Tomalak you got me :) ) to know when the server is going to terminate the connection and therefore return true for connection_aborted() until you test the actual server involved. Because each one is different. Even the web browser may do some stuff that delays the actual close, which may further muddy the issue.

所以这意味着它是不可能的(是的Tomalak,你让我:))知道什么时候服务器将终止连接,因此为connection_aborted()返回true,直到你测试相关的实际服务器。因为每个人都不同。即使是网络浏览器也可能会做一些延迟实际关闭的事情,这可能会使问题更加混乱。

DC