欢迎各位兄弟 发布技术文章

这里的技术是共享的

You are here

php 不等待返回的实现方法(异步调用) fsockopen 有大用

shiping1 的头像
有时候执行某段php很耗时,但是用户又无需知道返回结果。

如果能模拟ajax那样的异步程序,就不需要用户等待程序执行完成,能大大提高体验。
比如发送通知邮件等!

通过curl能简单模拟异步程序,但是并不完全异步,因为最少需要1秒。
通过fsockopen时打开线程太多,造成apache挂掉。曾经就遇到过用foreach发送邮件把服务器挂掉。

需找一种比较好的异步程序!!

谢谢!!

 
 
该问题已被保护

保护原因:避免来自新用户不合宜或无意义的致谢、跟帖答案。

 
评论 (0) •  • 链接 • 2012-08-02 
 
8个答案
 

最好的办法是使用消息队列。用户请求后将请求插入消息队列,同时立即显示结果。

后台应该运行一个计划任务或者守护进程,执行队列请求。

缺点是消息队列排队时邮件发送会有延迟,这一点可以在显示结果时以文字告诉用户。

评论 (1) • 链接 • 2012-08-02
  • 0
    这个对于邮件和消息可以,但是有时间有个长脚本要执行,没必要搞队列。 – fengming 2012-08-03
 

PHP异步执行的常用方式常见的有以下几种,可以根据各自优缺点进行选择:

1.客户端页面采用AJAX技术请求服务器
优点:最简单,也最快,就是在返回给客户端的HTML代码中,嵌入AJAX调用,或者,嵌入一个img标签,src指向要执行的耗时脚本。
缺点:一般来说Ajax都应该在onLoad以后触发,也就是说,用户点开页面后,就关闭,那就不会触发我们的后台脚本了。
而使用img标签的话,这种方式不能称为严格意义上的异步执行。用户浏览器会长时间等待php脚本的执行完成,也就是用户浏览器的状态栏一直显示还在load。
当然,还可以使用其他的类似原理的方法,比如script标签等等。

2.popen()函数
该函数打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。
所以可以通过调用它,但忽略它的输出。使用代码如下:


  1. pclose(popen("/home/xinchen/backend.php &", 'r'));
 

优点:避免了第一个方法的缺点,并且也很快。
缺点:这种方法不能通过HTTP协议请求另外的一个WebService,只能执行本地的脚本文件。并且只能单向打开,无法穿大量参数给被调用脚本。并且如果,访问量很高的时候,会产生大量的进程。如果使用到了外部资源,还要自己考虑竞争。

3.CURL扩展
CURL是一个强大的HTTP命令行工具,可以模拟POST/GET等HTTP请求,然后得到和提取数据,显示在"标准输出"(stdout)上面。代码如下:


  1. $ch = curl_init();
  2.  
  3. $curl_opt = array(CURLOPT_URL, 'http://www.example.com/backend.php',
  4.                             CURLOPT_RETURNTRANSFER, 1,
  5.                             CURLOPT_TIMEOUT, 1,);
  6.  
  7. curl_setopt_array($ch, $curl_opt);
  8.  
  9. curl_exec($ch);
  10.  
  11. curl_close($ch);
 

缺点如你问题中描述的一样,由于使用CURL需要设置CUROPT_TIMEOUT为1(最小为1,郁闷)。也就是说,客户端至少必须等待1秒钟。

4.fscokopen()函数
fsockopen支持socket编程,可以使用fsockopen实现邮件发送等socket程序等等,使用fcockopen需要自己手动拼接出header部分
可以参考: http://cn.php.net/fsockopen/
使用示例如下:


  1. $fp = fsockopen("www.34ways.com", 80, $errno, $errstr, 30);
  2. if (!$fp) {
  3.     echo "$errstr ($errno)<br />\n";
  4. } else {
  5.     $out = "GET /index.php  / HTTP/1.1\r\n";
  6.     $out .= "Host: www.34ways.com\r\n";
  7.     $out .= "Connection: Close\r\n\r\n";
  8.  
  9.     fwrite($fp, $out);
  10.     /*忽略执行结果
  11.     while (!feof($fp)) {
  12.         echo fgets($fp, 128);
  13.     }*/
  14.     fclose($fp);
  15. }
 

所以总结来说,fscokopen()函数应该可以满足您的要求。可以尝试一下。

评论 (1) • 链接 • 2012-08-03
  • 0
    fscokopen的问题和popen 一样,并发非常多时会产生很多子进程,当达到apache的连接限制数时,就会挂掉,我问题已经说了这种情况。 – fengming 2012-08-03
 

PHP 本身没有多线程的东西,但可以曲线的办法来造就出同样的效果,比如多进程的方式来达到异步调用,只限于命令模式。还有一种更简单的方式,可用于 Web 程序中,那就是用fsockopen()、fputs() 来请求一个 URL 而无需等待返回,如果你在那个被请求的页面中做些事情就相当于异步了。

关键代码如下:


  1. $fp=fsockopen('localhost',80,&$errno,&$errstr,5);
  2. if(!$fp){
  3.     echo "$errstr ($errno)<br />\n";
  4. }
  5. fputs($fp,"GET another_page.php?flag=1\r\n");
  6. fclose($fp);
 

上面的代码向页面 another_page.php 发送完请求就不管了,用不着等待请求页面的响应数据,利用这一点就可以在被请求的页面 another_page.php 中异步的做些事情了。

比如,一个很切实的应用,某个 Blog 在每 Post 了一篇新日志后需要给所有它的订阅者发个邮件通知。如果按照通常的方式就是:

日志写完 -> 点提交按钮 -> 日志插入到数据库 -> 发送邮件通知 ->
告知撰写者发布成功

那么作者在点提交按钮到看到成功提示之间可能会等待很常时间,基本是在等邮件发送的过程,比如连接邮件服务异常、或器缓慢或是订阅者太多。而实际上是不管邮件发送成功与否,保证日志保存成功基本可接受的,所以等待邮件发送的过程是很不经济的,这个过程可异步来执行,并且邮件发送的结果不太关心或以日志形式记录备查。

改进后的流程就是:

日志写完 -> 点提交按钮 -> 日志插入到数据库 --->
告知撰写者发布成功
└ 发送邮件通知 -> [记下日志]

用个实际的程序来测试一下,有两个 php,分别是 write.php 和 sendmail.php,在 sendmail.php 用 sleep(seconds) 来模拟程序执行使用时间。

write.php,执行耗时 1 秒


  1. <?php
  2.  
  3. function asyn_sendmail() {
  4.     $fp=fsockopen('localhost',80,&$errno,&$errstr,5);
  5.     if(!$fp){
  6.         echo "$errstr ($errno)<br />\n";
  7.     }
  8.     sleep(1);
  9.     fputs($fp,"GET /sendmail.php?param=1\r\n"); #请求的资源 URL 一定要写对
  10.     fclose($fp);
  11. }
  12.  
  13. echo time().'<br>';
  14. echo 'call asyn_sendmail<br>';
  15. asyn_sendmail();
  16. echo time().'<br>';
  17. ?>
 

sendmail.php,执行耗时 10 秒


  1. <?php
  2. //sendmail();
  3. //sleep 10 seconds
  4. sleep(10);
  5. fopen('C:\'.time(),'w');
  6. ?>
 

通过页面访问 write.php,页面输出:

1272472697 call asyn_sendmail
1272472698

并且在 C:\ 生成文件:

1272472708

从上面的结果可知 sendmail.php 花费至少 10 秒,但不会阻塞到 write.php 的继续往下执行,表明这一过程是异步的。

评论 (0) • 链接 • 2012-08-03
 

可以把邮件信息、邮件地址先存到数据库,数据量大的话后台开多个后台进程程序来进行异步发送。

评论 (0) • 链接 • 2012-08-02
 

我说说这个吧,对于PHP的异步处理,其实也可以说是一种分布式计算,个人认为比较好的方式有两种,
一、就是采用队列机制来处理,这个我就不说了,你百度一下到处都是,

二、就是我们目前采用的的Gearman,这是一个分布式计算的工具,它可以开很多的worker和支持将多个job平均的分配到不同的机器上去异步处理,我们公司现在就是用它来处理用户上传图片的处理(推荐你使用它,个人感觉特别好的,处理速度还很快)。

评论 (0) • 链接 • 2012-08-03
 

最后是通过队列的形式解决,因为中间需要一些逻辑处理,比如失败了重新发送、定时发送等!!

分邮件形式的@令狐葱 的方法就很全了,补充个方法在5.3后有效。

libevent
http://www.ooso.net/archives/607

至尊宝
至尊宝
1119
编辑于 2012-08-08
 
评论 (0) • 链接 • 2012-08-08
 

像发邮件这样的比较大量的数据你可以考虑做队列,只要将要处理的信息压入队列就行了,然后用专门的程序处理队列数据就行了,这样也相当与是异步了

评论 (0) • 链接 • 2012-08-03
 

如果是fastcgi模式的话可以尝试用fastcgi_finish_request()函数。

评论 (0) • 链接 • 2012-08-08

来自  http://www.dewen.io/q/3970/



浏览器和服务器之间是通过 HTTP 协议进行连接通讯的。这是一种基于请求和响应模型的协议。浏览器通过 URL 向服务器发起请求,Web 服务器接收到请求,执行一段程序,然后做出响应,发送相应的html代码给客户端。

这就有了一个问题,Web 服务器执行一段程序,可能几毫秒就完成,也可能几分钟都完不成。如果程序执行缓慢,用户可能没有耐心等下去,就关闭浏览器了。

而有的时候,我们更本不关心这些耗时的脚本的返回结果,但却还要等他执行完返回,才能继续下一步。
那么有没有什么办法,只是简单的触发调用这些耗时的脚本然后就继续下一步,让这些耗时的脚本在服务端慢慢执行?

经过试验,总结出来几种方法,和大家share:
1. 最简单的办法,就是在返回给客户端的HTML代码中,嵌入AJAX调用,或者,嵌入一个img标签,src指向要执行的耗时脚本。
这种方法最简单,也最快。服务器端不用做任何的调用。
但是缺点是,一般来说Ajax都应该在onLoad以后触发,也就是说,用户点开页面后,就关闭,那就不会触发我们的后台脚本了。
而使用img标签的话,这种方式不能称为严格意义上的异步执行。用户浏览器会长时间等待php脚本的执行完成,也就是用户浏览器的状态栏一直显示还在load。
当然,还可以使用其他的类似原理的方法,比如script标签等等。

2. popen()

resource popen ( string command, string mode );
//打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。打开一个指向进程的管道,该进程由派生给定的 command 命令执行而产生。

所以可以通过调用它,但忽略它的输出。


  1. pclose(popen("/home/xinchen/backend.php &", 'r'));

这个方法避免了第一个方法的缺点,并且也很快。但是问题是,这种方法不能通过HTTP协议请求另外的一个WebService,只能执行本地的脚本文件。并且只能单向打开,无法穿大量参数给被调用脚本。
并且如果,访问量很高的时候,会产生大量的进程。如果使用到了外部资源,还要自己考虑竞争。

3. 使用CURL
这个方法,设置CUROPT_TIMEOUT为1(最小为1,郁闷)。也就是说,客户端至少必须等待1秒钟。


  1. $ch = curl_init();
  2.  
  3. $curl_opt = array(CURLOPT_URL, 'http://www.example.com/backend.php',
  4.                             CURLOPT_RETURNTRANSFER, 1,
  5.                             CURLOPT_TIMEOUT, 1,);
  6.  
  7. curl_setopt_array($ch, $curl_opt);
  8.  
  9. curl_exec($ch);
  10.  
  11. curl_close($ch);

4. 使用fsockopen
这个方法应该是最完美的,但是缺点是,你需要自己拼出HTTP的header部分。


  1. $fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
  2. if (!$fp) {
  3.     echo "$errstr ($errno)<br />\n";
  4. } else {
  5.     $out = "GET /backend.php / HTTP/1.1\r\n";
  6.     $out .= "Host: www.example.com\r\n";
  7.     $out .= "Connection: Close\r\n\r\n";
  8.  
  9.     fwrite($fp, $out);
  10.     /*忽略执行结果
  11. while (!feof($fp)) {
  12. echo fgets($fp, 128);
  13. }*/
  14.     fclose($fp);
  15. }

所以,总体来看,最好用,最简单的还是第一种方法。
最完美的应该是最后一种,但是比较复杂
如果有更好的办法,欢迎交流。

来自  http://www.laruence.com/2008/04/14/318.html



PHP 本身没有多线程的东西,但可以曲线的办法来造就出同样的效果,比如多进程的方式来达到异步调用,只限于命令模式。
另外还有一种更简单的方式可用于 Web 程序中,那就是用 fsockopen()、fputs() 来请求一个 URL, 而无需等待返回,如果你在那个被请求的页面(URL)中做些事情就相当于异步了。
关键代码如下:
复制代码代码如下:

<?php
  $fp = fsockopen('localhost',80,&$errno,&$errstr,5);  
  if(!$fp)
     {
         echo "$errstr ($errno)<br />/n"; 
     }
     fputs($fp,"GET another_page.php?flag=1/r/n"); 
     fclose($fp);

上面的代码向页面 another_page.php 发送完请求就不管了,用不着等待请求页面的响应数据,利用这一点就可以在被请求的页面 another_page.php 中异步的做些事情了。
比如,一个很切实的应用,我们每当发表了一篇新日志后需要给所有该日志的订阅者发个邮件通知, 如果按照通常的方式就是:
日志写完 -> 点提交按钮 -> 日志插入到数据库 -> 发送邮件通知 -> 告知撰写者发布成功
那么作者在点提交按钮到看到成功提示之间可能会等待很常时间,基本是在等邮件发送的过程,比如连接邮件服务异常、或器缓慢或是订阅者太多。而实际上是不管邮件发送成功与否,保证日志保存成功基本可接受的,所以等待邮件发送的过程是很不经济的,这个过程可异步来执行,并且邮件发送的结果不太关心或以日志形式记录备查。
改进后的流程就是:
日志写完 -> 点提交按钮 -> 日志插入到数据库 ---> 告知撰写者发布成功
                                                           └ 发送邮件通知 -> [记下日志]

写个实际的程序来测试一下,有两个文件,分别是 write.php 和 sendmail.php,在 sendmail.php 用 sleep(seconds) 来模拟程序执行所使用时间。
write.php,执行耗时 1 秒:
复制代码代码如下:

 <?php  
  function asyn_sendmail() 
     { 
         $fp = fsockopen('localhost',80,&$errno,&$errstr,5);  
      if(!$fp)
         {
            echo "$errstr ($errno)<br />/n";
         } 
     sleep(1); 
     fputs($fp,"GET /sendmail.php?param=1/r/n"); #请求的资源 URL 一定要写对 
     fclose($fp); 
    }  
 echo time().'<br>'; 
 echo 'call asyn_sendmail<br>'; 
 asyn_sendmail(); 
 echo time().'<br>'; 

sendmail.php,执行耗时 10 秒:
复制代码代码如下:

<?php 
sleep(10); 
fopen("C:/" . time(),  "w");  

通过页面访问 write.php,页面输出:
1272472697
call asyn_sendmail
1272472698
并且在 C:/ 生成文件:
1272472708
从上面的结果可以看出 sendmail.php 至少花费 10 秒,但不会阻塞 write.php 继续往下执行,表明这一过程是异步的。

来自  
http://www.jb51.net/article/37779.htm

普通分类: