Press "Enter" to skip to content

PHP实现异步调用方法研究

浏览器和服务器之间是通过 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 命令执行而产生。
所以可以通过调用它,但忽略它的输出。

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

这个方法避免了第一个方法的缺点,并且也很快。但是问题是,这种方法不能通过HTTP协议请求另外的一个WebService,只能执行本地的脚本文件。并且只能单向打开,无法穿大量参数给被调用脚本。
并且如果,访问量很高的时候,会产生大量的进程。如果使用到了外部资源,还要自己考虑竞争。
3. 使用CURL
这个方法,设置CUROPT_TIMEOUT为1(最小为1,郁闷)。也就是说,客户端至少必须等待1秒钟。

$ch = curl_init();
$curl_opt = array(CURLOPT_URL, 'http://www.example.com/backend.php',
                            CURLOPT_RETURNTRANSFER, 1,
                            CURLOPT_TIMEOUT, 1,);
curl_setopt_array($ch, $curl_opt);
curl_exec($ch);
curl_close($ch);

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

$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET /backend.php  / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    /*忽略执行结果
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }*/
    fclose($fp);
}

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

47 Comments

  1. […] PHP是世界上最好的语言,但是总被“同行们”吐槽不支持异步。其实我们要实现异步也非常简单,之前看到鸟哥的一篇写PHP异步执行的博文 PHP实现异步调用方法研究,这篇文章还是08年的,到今天PHP发展快10年了,对于异步调用也有了更多新的玩法。 […]

  2. debris - 终成王者
    debris - 终成王者 November 9, 2017

    […] fwrite($fp, $out); //忽略执行结果 /* while (!feof($fp)) { echo fgets($fp, 128).'<br/>'; }*/ fclose($fp); } } 更多异步方法 […]

  3. […] 在鸟哥的博客中有提出4种php实现异步的方式,具体可以看PHP实现异步调用方法研究 […]

  4. Anonymous
    Anonymous July 6, 2016

    fastcgi_finish_request() 这个是否可行?

  5. porno
    porno May 21, 2016

    I savour, result in I found just what I was taking a look for.
    You have ended my 4 day long hunt! God Bless you man. Have a great day.
    Bye

  6. 杨航
    杨航 December 22, 2015

    为什么我忽略执行结果后 接收方就接收不到请求呢 麻烦解答一下 谢谢了 在线等
    /*忽略执行结果
    while (!feof($fp)) {
    echo fgets($fp, 128);
    }*/

  7. Florene
    Florene November 10, 2015

    I’ve learn several good stuff here. Certainly price bookmarking for revisiting.
    I surprise how so mich effort you pput to create the srt of great informative site.

  8. online company
    online company October 1, 2015

    Hi there to all, it’s in fact a good for me to go to see this web site, it consists
    of priceless Information.

  9. świeczka
    świeczka September 15, 2015

    Right now it seems like WordPress is the top blogging platform
    available right now. (from what I’ve read) Is that what you
    are using on your blog?

  10. This is the right blog for everyone who wants to understand this topic.
    You understand a whole lot its almost tough to argue
    with you (not that I really will need to…HaHa). You definitely
    put a fresh spin on a subject that has been written about for a long time.
    Excellent stuff, just wonderful!

  11. weight loss Tampa
    weight loss Tampa September 5, 2015

    Hi my family member! I wish to say that this article
    is amazing, great writtten and come with almost all important infos.
    I’d like to peer extra posts like this .

  12. kacer 6 bulan
    kacer 6 bulan July 30, 2015

    This is the perfect webpage for anybody who would like
    tto find out aboiut this topic. You know a whole lot its almost tough to argue with you (not that I
    really would want to…HaHa). You deinitely puut a new spin on a topic that’s been discussed for years.
    Wonderful stuff, just excellent!
    Feel freee to surf tto my blog … kacer 6 bulan

  13. Anonymous
    Anonymous June 24, 2015

    可以用CURLOPT_TIMEOUT_MS 替代 CURLOPT_TIMEOUT

  14. www.zhiboju.cn
    www.zhiboju.cn April 1, 2015

    It’s in fact very difficult in this busy life to listen news on TV,
    therefore I just use web for that reason, and obtain the newest
    news.
    http://www.zhiboju.cn

  15. goedkoopste zonnepanelen
    goedkoopste zonnepanelen February 14, 2015

    It’s in fact very difficult in this busy life to listen news on TV,
    therefore I just use web for that reason, and obtain the newest
    news.

  16. เสื้อคู่
    เสื้อคู่ February 5, 2015

    I absolutely love your blog and find a lot of your post’s to be exactly I’m looking for.
    Would you offer guest writers to write content for
    you? I wouldn’t mind composing a post or elaborating on most of the subjects you
    write with regards to here. Again, awesome site!

  17. This is a good tip particularly to those new to the blogosphere.
    Short but very precise info… Many thanks for sharing this one.
    A must read post!

  18. QWang
    QWang March 3, 2014

    可以体验下这个PHP扩展,www.swoole.com,让PHP也像node.js一样写异步回调

  19. waming
    waming September 11, 2013

    facebook有个最新的架构叫Bigpipe或许是一个解决方案啊

  20. 森
    September 6, 2013

    第四种的fsockopen方法,如何实现传参呢?而且要传递的是很大的数组!

  21. bosiam
    bosiam May 17, 2013

    $out .= “Connection: Close\r\n\r\n”;
    请教,语句中的换行符,一个和两个有什么区别么?
    经测试发现,两个才能运行!

  22. […] 参考链接 使用fscok实现异步调用PHP PHP实现异步调用方法研究 说说php的异步请求 本条目发布于 2013 年 2 月 20 日。属于 未分类 […]

  23. Cupenoruler
    Cupenoruler January 31, 2013

    翻了一下文档~ 发现curl 有了毫秒级的超时设置~
    CURLOPT_CONNECTTIMEOUT_MS 这个选项
    备注: Added in cURL 7.16.2. Available since PHP 5.2.3
    我现在用的cURL 7.24 和 php5.4 ~ 暗爽~

  24. goosman.lei
    goosman.lei January 24, 2013

    我们现在对这种问题的处理:
    1. webserver处理必要的逻辑(需要响应给用户的), 将用户不关心结果的逻辑作为任务放入一个队列中(memcacheq)
    2. 后台使用pcntl以及自己编写的daemon扩展(暴露系统库中的daemon()库函数)实现一套任务处理系统, 从队列中读取任务, 异步执行任务.

  25. huyet
    huyet January 22, 2013

    呵呵,下一篇就提到了

  26. huyet
    huyet January 22, 2013

    第4种方法的话,接受端是否还要加这句:ignore_user_abort(true);
    不然请求端断开连接后,接受端的脚本就停止执行?

  27. drunkard
    drunkard April 7, 2012

    某些特殊的情况,还可以使用header(‘Location: ‘),执行用户请求后的必要逻辑即跳转到别的页面,在header之后可以继续执行其他的东西。

  28. alex
    alex January 12, 2012

    还有个方法,是通过数据库做个任务表保存任务
    然后做个shell轮询,执行程序完成后更新任务状态,这样不占用web server的资源

  29. 初学者
    初学者 July 14, 2011

    咨询一个问题,用curl的话,如果backend.php是一个死循环的话,如何才能杀掉这个进程呢?
    重启apache的话可以杀掉,还有其它的方法么?

  30. 懒虫
    懒虫 January 25, 2011

    不好意思 找到错误了 和 那个/没关系,是头我写错了。

  31. 懒虫
    懒虫 January 25, 2011

    我试了第4个 使用fsockopen
    $out = “GET /backend.php / HTTP/1.1\r\n”;
    变成
    $out = “GET /backend.php HTTP/1.1\r\n”;
    少了一个 /
    这样就会有 HTTP/1.1 400 Bad Request 这个错误
    如果加上就不会出现 400 Bad Request
    请问这个是为什么??

  32. laruence
    laruence January 22, 2011

    @TaoGOGO ignore只是针对服务端脚本的…

  33. TaoGOGO
    TaoGOGO January 22, 2011

    ignore_user_abort也行。。。

  34. 雪候鸟
    雪候鸟 August 28, 2008

    哦, 呵呵
    恩, 这样生成头部应该也行.;)

  35. sunceenjoy
    sunceenjoy August 28, 2008

    呵呵,我的意思是帮你补充一种方法啊,你看行不。

  36. 雪候鸟
    雪候鸟 August 27, 2008

    你想说什么呢?
    看来,我得设置回复,一定要输入名字了.呵呵

  37. Anonymous
    Anonymous August 27, 2008

    5.
    $opts = array(
    ‘http’=>array(‘method’=>”GET”,
    ‘header’=>”Accept-language: en\r\nCookie: “.$cookie.”\r\n”
    )
    );
    $context = stream_context_create($opts);
    $fp = fopen(‘http://www.example.com’, ‘r’, false, $context);
    //while(!feof($fp))
    //$html.=fgets($fp);
    fclose($fp);

Comments are closed.