php中使用fsockopen實現異步請求


php執行一段程序,有可能幾毫秒就執行完畢,也有可能耗時較長。例如,用戶下單這個事件,如果調用了些第三方服務進行發郵件、短信、推送等通知,可能導致前端一直在等待。而有的時候,我們並不關心這些耗時腳本的返回結果,只要執行就行了。這時候就需要采用異步的方式執行。

眾所周知,PHP沒有直接支持多線程這種東西。我們可以采用折衷的方式實現。這里主要說的就是fsockopen。

通過fsockopen發送請求並忽略返回結果,程序可以馬上返回。

示例代碼:

$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);
}

需要注意的是我們需要手動拼出header頭信息。通過打開注釋部分,可以查看請求返回結果,但這時候又變成同步的了,因為程序會等待返回結果才結束。

實際測試的時候發現,不忽略執行結果,調試的時候每次都會成功發送sock請求;但忽略執行結果,經常看到沒有成功發送sock請求。查看nginx日志,發現很多狀態碼為499的請求。

后來找到了原因:fwrite之后馬上執行fclose,nginx會直接返回499,不會把請求轉發給php處理。

客戶端主動端口請求連接時,NGINX 不會將該請求代理給上游服務(FastCGI PHP 進程),這個時候 access log 中會以 499 記錄這個請求。

解決方案:
1)nginx.conf增加配置

# 忽略客戶端中斷
fastcgi_ignore_client_abort on;

2)fwrite之后使用usleep函數休眠20毫秒:

usleep(20000);

后來測試就沒有發現失敗的情況了。

附上完整代碼:

<?php
/**
 * 工具類
 * */
class FsockService {
	
	public static function post($url, $param){

        $host = parse_url($url, PHP_URL_HOST);
        $port = 80;
        $errno = '';
        $errstr = '';
        $timeout = 30;

        $data = http_build_query($param);

        // create connect
        $fp = fsockopen($host, $port, $errno, $errstr, $timeout);

        if(!$fp){
            return false;
        }

        // send request
        $out = "POST ${url} HTTP/1.1\r\n";
        $out .= "Host:${host}\r\n";
        $out .= "Content-type:application/x-www-form-urlencoded\r\n";
        $out .= "Content-length:".strlen($data)."\r\n";
        $out .= "Connection:close\r\n\r\n";
        $out .= "${data}";

        fwrite($fp, $out);

        //忽略執行結果;否則等待返回結果
//        if(APP_DEBUG === true){
        if(false){
            $ret = '';
            while (!feof($fp)) {
                $ret .= fgets($fp, 128);
            }
        }

        usleep(20000); //fwrite之后馬上執行fclose,nginx會直接返回499

        fclose($fp);
    }

    public static function get($url, $param){
        $host = parse_url($url, PHP_URL_HOST);
        $port = 80;
        $errno = '';
        $errstr = '';
        $timeout = 30;

        $url = $url.'?'.http_build_query($param);

        // create connect
        $fp = fsockopen($host, $port, $errno, $errstr, $timeout);

        if(!$fp){
            return false;
        }

        // send request
        $out = "GET ${url} HTTP/1.1\r\n";
        $out .= "Host:${host}\r\n";
        $out .= "Connection:close\r\n\r\n";

        fwrite($fp, $out);

        //忽略執行結果;否則等待返回結果
//        if(APP_DEBUG === true){
        if(false){
            $ret = '';
            while (!feof($fp)) {
                $ret .= fgets($fp, 128);
            }
        }

        usleep(20000); //fwrite之后馬上執行fclose,nginx會直接返回499

        fclose($fp);
    }
   
}

?>

參考:
1、PHP實現異步調用方法研究 | 風雪之隅
http://www.laruence.com/2008/04/14/318.html
2、PHP fsockopen 異步調用接口在nginx上偶爾實效的情況 - 張煒施 - 博客園
http://www.cnblogs.com/zhangweishi/p/5306813.html
3、php 利用fsockopen GET/POST 提交表單及上傳文件 - 傲雪星楓 - 博客頻道 - CSDN.NET
http://blog.csdn.net/fdipzone/article/details/11712607
4、關於PHP的異步調用 - 浮雲比翼 - 博客園
http://www.cnblogs.com/fuyunbiyi/archive/2013/03/27/2985089.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM