Press "Enter" to skip to content

使用fscok实现异步调用PHP

     Web 服务器执行一个脚本,可能几毫秒就完成,也可能几分钟都完不成。如果程序执行缓慢,用户可能没有耐心等下去,就关闭浏览器了。
    而有的时候,我们更本不关心这些耗时的脚本的执行结果,但却还要等他执行完返回,才能继续下一步。  
    那么有没有什么办法,只是简单的触发调用这些耗时的脚本然后就继续下一步,让这些耗时的脚本在服务端慢慢执行?
    
    接下来,我将使用fscokopen来实现这一功能。
   
    PHP是支持socket编程的,就是fsockopen, 在以前做CMS的时候,我也曾经用过它做过smtp发信。
    fscokopen返回一个到远程主机连接的句柄。你可以像使用fopen返回的句柄一样,对她进行写fwrite,读取fgets, fread等操作。
   
    我们的异步PHP,主要想要的效果就是,触发一个PHP脚本,然后立即返回,留它在服务器端慢慢执行。前面我也写过一篇文章讨论过这个问题。

    那么,我们就可以使用fsockopen连接到本地服务器,触发脚本执行,然后立即返回,不等待脚本执行完成。
  

function triggerRequest($url, $post_data = array(), $cookie = array())...{
        
$method = "GET";  //可以通过POST或者GET传递一些参数给要触发的脚本
        $url_array = parse_url($url); //获取URL信息,以便平凑HTTP HEADER
        $port = isset($url_array['port'])? $url_array['port': 80
      
        
$fp = fsockopen($url_array['host'], $port, $errno, $errstr, 30); 
        
if (!$fp...{
                
return FALSE;
        }
        
$getPath = $url_array['path'."?". $url_array['query'];
        
if(!empty($post_data))...{
                
$method = "POST";
        }
        
$header = $method . " " . $getPath;
        
$header .= " HTTP/1.1\r\n";
        
$header .= "Host: ". $url_array['host'. "\r\n "//HTTP 1.1 Host域不能省略
        /**//*以下头信息域可以省略
        $header .= "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13 \r\n";
        $header .= "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,q=0.5 \r\n";
        $header .= "Accept-Language: en-us,en;q=0.5 ";
        $header .= "Accept-Encoding: gzip,deflate\r\n";
         
*/

        
$header .= "Connection:Close\r\n";
        
if(!empty($cookie))...{
                
$_cookie = strval(NULL);
                
foreach($cookie as $k => $v)...{
                        
$_cookie .= $k."=".$v."";
                }
                
$cookie_str =  "Cookie: " . base64_encode($_cookie." \r\n";//传递Cookie
                $header .= $cookie_str;
        }
        
if(!empty($post_data))...{
                
$_post = strval(NULL);
                
foreach($post_data as $k => $v)...{
                        
$_post .= $k."=".$v."&";
                }
                
$post_str  = "Content-Type: application/x-www-form-urlencoded\r\n";//POST数据
                $post_str .= "Content-Length: ". strlen($_post." \r\n";//POST数据的长度
                $post_str .= $_post."\r\n\r\n "//传递POST数据
                $header .= $post_str;
        }
        
fwrite($fp, $header);
        
//echo fread($fp, 1024); //我们不关心服务器返回
        fclose($fp);
        
return true;
}

   

 

    现在,就可以通过这个函数来触发一个PHP脚本的执行,然后函数就会返回。 我们就可以接着执行下一步操作了。

 

   还有一个问题就是,当客户端断开连接以后。也就是triggerRequest发送请求后,立即关闭了连接,那么可能会引起服务器端正在执行的脚本退出。

 

   在 PHP 内部,系统维护着连接状态,其状态有三种可能的情况:

 

    * 0 - NORMAL(正常)

 

    * 1 - ABORTED(异常退出)

 

    * 2 - TIMEOUT(超时)

 

     当 PHP 脚本正常地运行 NORMAL 状态时,连接为有效。当客户端中断连接时,ABORTED 状态的标记将会被打开。远程客户端连接的中断通常是由用户点击 STOP 按钮导致的。当连接时间超过 PHP 的时限(请参阅 set_time_limit() 函数)时,TIMEOUT 状态的标记将被打开。

 

     可以决定脚本是否需要在客户端中断连接时退出。有时候让脚本完整地运行会带来很多方便,即使没有远程浏览器接受脚本的输出。默认的情况是当远程客户端连接 中断时脚本将会退出。该处理过程可由 php.ini 的 ignore_user_abort 或由 Apache .conf 设置中对应的“php_value ignore_user_abort”以及 ignore_user_abort() 函数来控制。如果没有告诉 PHP 忽略用户的中断,脚本将会被中断,除非通过 register_shutdown_function() 设置了关闭触发函数。通过该关闭触发函数,当远程用户点击 STOP 按钮后,脚本再次尝试输出数据时,PHP 将会检测到连接已被中断,并调用关闭触发函数。

 

     脚本也有可能被内置的脚本计时器中断。默认的超时限制为 30 秒。这个值可以通过设置 php.ini 的 max_execution_time 或 Apache .conf 设置中对应的“php_value max_execution_time”参数或者 set_time_limit() 函数来更改。当计数器超时的时候,脚本将会类似于以上连接中断的情况退出,先前被注册过的关闭触发函数也将在这时被执行。在该关闭触发函数中,可以通过调 用 connection_status() 函数来检查超时是否导致关闭触发函数被调用。如果超时导致了关闭触发函数的调用,该函数将返回 2。

 

     需要注意的一点是 ABORTED 和 TIMEOUT 状态可以同时有效。这在告诉 PHP 忽略用户的退出操作时是可能的。PHP 将仍然注意用户已经中断了连接但脚本仍然在运行的情况。如果到了运行的时间限制,脚本将被退出,设置过的关闭触发函数也将被执行。在这时会发现函数 connection_status() 返回 3。

 

      所以还在要触发的脚本中指明:

  

ignore_user_abort(TRUE); //如果客户端断开连接,不会引起脚本abort.
set_time_limit(0);//取消脚本执行延时上限

     或者,也可以使用:

register_shutdown_function(callback fuction[, parameters]);//注册脚本退出时执行的函数

36 Comments

  1. danielbro
    danielbro March 8, 2023

    Thank you for your helpful post, There is no need to go to the future, the future is coming to with you friv 1001

  2. riya Fashion
    riya Fashion October 6, 2017

    This article includes a brief history of movie posters and
    movie poster collecting. You will also find information aboutthe types and sizes of movie posters…
    For more,…
    Motivational video

  3. Raksha Bandhan Quotes
    Raksha Bandhan Quotes August 1, 2017

    Rakhi is the traditional Indian festival where a sister ties Rakhi string around her brother’s wrist.
    Like many other Indian festivals, this too is a gift-giving occasion when brother and sisters exchange their token of love.
    There are many quotes are available for sibling in our article……
    For More….
    Plz visit:- Raksha Bandhan Quotes

  4. PHP技术群464681192
    PHP技术群464681192 July 11, 2017

    多谢lz分享,建了个技术群,欢迎各位加群

  5. gleeman
    gleeman July 5, 2017

    拼接http请求头最后应该需要一行空白行,否则请求不成功!!

  6. gleeman
    gleeman July 5, 2017

    拼接http请求头最后应该需要一行空白行,否则请求不成功!!

  7. Eller
    Eller April 12, 2017

    经测试,发现这一行代码尾部多增加了一个空格,导致请求失败。
    $header .= “Host: “. $url_array[‘host’] . “\r\n “; //HTTP 1.1 Host域不能省略
    \r\n后面到引号中间

  8. felix
    felix December 26, 2016

    按照鸟哥的代码测试了一下发现调用的脚本并不能执行,看了下官方的demo,在write后循环fget,脚本启动成功,不知道是什么原因

  9. 乌鸦
    乌鸦 August 5, 2015

    使用niginx的童鞋注意:
    fastcgi_ignore_client_abort on;
    // 客户端主动断掉连接之后,Nginx 会等待后端处理完(或者超时);
    (另外get请求,是否需要在header之后再加一个\r\n,忘记了~~~)

  10. PHPJungle
    PHPJungle June 24, 2015

    楼主这篇文章实现异步的核心思路是“向服务端发送了一个只写不读的请求”,同时还提到了ignore_user_abort 和 set_time_limit两个细节问题,很不错 :)
    我是用的curl_multi_X实现的
    地址:https://github.com/phpjungle/iHttp/

  11. Jarod
    Jarod May 29, 2015

    使用成功的。
    nginx 1.8.0中会返回 499的status code
    127.0.0.1 – – [29/May/2015:07:07:14 +0800] “GET /app_dev.php/my/timeout?ver=y&a=1&b=2 HTTP/1.1” 499 0 “-” “-“

  12. The monarchies sold monopoly rights in order to raise revenue.
    In addition, you should be focusing on yourself during the
    no contact period. At the first sign of trouble, we were banished to a sick room where we were to remain in quarantine until Mom declared
    us healthy again.

  13. What i do not understood is if truth be told how you’re no longer really much more well-appreciated than you may be now.
    You are very intelligent. You already know therefore considerably with regards to this matter, made me for my part
    imagine it from a lot of various angles. Its like women and men are not interested until it is
    something to do with Woman gaga! Your own stuffs outstanding.
    Always deal with it up!

  14. 周梦康
    周梦康 March 24, 2014

    代码学习了,我只初步试了下没有传cookie和post数据的情况似乎不能正常运行。发现别人代码中$header 都是 “Connection:Close\r\n\r\n”;这样。

  15. 周梦康
    周梦康 March 24, 2014

    代码已带走,楼主标题中的单词是不是写错了,大神的标题不敢随便怀疑……

  16. 惠下柳
    惠下柳 September 2, 2013

    愽主有在嘛?能否稍微给一点提示啊?

  17. 惠下柳
    惠下柳 August 27, 2013

    冒昧打扰一下,使用你介绍的triggerRequest异步执行php功能,系统运行一段时间后很完美,可是在一次服务器升级后这个功能就不能用了,必须fread才能响应,我用的是nginx+php5.2.17。请问你知道是什么原因嘛?

  18. allen
    allen August 15, 2013

    在apache上是可以使用的,可是移植到nginx上,必须使用fread才可以执行,求解释呀?
    另外下面一句话中的“立即关闭了连接”是关闭浏览器还是页面重定向的意思呢
    也就是triggerRequest发送请求后,立即关闭了连接,那么可能会引起服务器端正在执行的脚本退出。

  19. liji
    liji July 11, 2013

    请问你上面贴出的代码你试过吗? 我试了试不行呀。
    求解 1. http1.1 与 http1.0 在这里有什么区别
    2. $post_str .= $_post.”\r\n\r\n “; 在这行代码前面是不是还要在加上一个 “\r\n”
    3. 为什么当我获取返回值的时候请求就可以 注释掉获取返回值就不代码就无效呢?

  20. ChaoGuo
    ChaoGuo April 7, 2013

    $socket=fsockopen(“localhost”,8080,$errno,$errstr,15);
    $http =”POST /a.php HTTP/1.1\r\n”;


    fwrite($socket,$http);
    fclose($socket);
    这个是触发了另一个脚本的执行,但这个时候另一个脚本机器上的web server会一直保诗了一个连接吧。

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

  22. phpufo
    phpufo December 5, 2012

    经常用到第一种方法,第二种没用过!

  23. Ckai
    Ckai August 9, 2011

    还是有点不明白,用ajax不是更好吗?求解释..

  24. dulao5
    dulao5 March 24, 2011

    * 用原始的socket api做HTTP client有点原始了, curl等成熟库更好
    * 关键是将异步消息发到后端, 所以重的解决方案是拿一个成熟的消息队列服务器
    * 我曾经有个土办法, 就用apache log作为消息队列来用, 前端调用一个apache空文件, 留下log立即返回

  25. 小蔡
    小蔡 February 22, 2011

    $header .= “Connection:Close\r\n”;
    测试发现,这个要改成:
    $header .= “Connection:Close\r\n\r\n”;

  26. 雪候鸟
    雪候鸟 May 13, 2010

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

  27. MoontoC
    MoontoC May 12, 2010

    我只想知道使用的场合,一般情况下,要做一件很耗时而不得不做的事情,都是先flush之后再set_time_limit就好了,我只是想知道什么样的需求才用到这种方法

  28. zvaly
    zvaly October 25, 2008

    COOL!
    直接打开socket连接向远端服务器写HTTP请求,这和正常的web请求一样吗?是不是打开socket这个动作和write动作,是一个请求发出时,浏览器所做的动作?

  29. Anonymous
    Anonymous August 27, 2008

    nice

Comments are closed.