# __ .______ __ __ .______ .___________. ______ ______ .___ ___. # | | | _ \ | | | | | _ \ | | / | / __ \ | \/ | # | | | |_) | | |__| | | |_) | `---| |----` | ,----'| | | | | \ / | # | | | ___/ | __ | | ___/ | | | | | | | | | |\/| | # | | | | | | | | | | | | __ | `----.| `--' | | | | | # |__| | _| |__| |__| | _| |__| (__) \______| \______/ |__| |__| # ""$o o$"" ""$o o$" o "$""""o "o $" o""" $" "$o "$o" $o " $ o$" "$o $$$o$$$$o$$$$ $" "oooo o "" ""$$$$$$$$""o"" oo oooo" "$$$$$$oo"oo$$$o" o$$$$oo" o$$$o "o$$$$$$$ "$ $$$$$$$$$oo o$$$$$$$$$o"$" $ $$$ $$$$$$ o$$$$$$ "$$o"o $ $$$$o $$$$$$ $$$$$$$ $$$$o"o $ $$$$$ $$$$$" "$$$$$ $$$$$$ $ $o""""" """" """ """"""$" $ o$$$$$"""$$$$$"$$$$$""$$$$$ooo"o $ o"$o $$$$$$$$oo$$$$$$$$o $$"" $ oo$ "$$$$$$$$$$$$$$$$$$$$" o" o $oo o$$$"$ $$o"o $$$$$$$"" "$$$$$$$ o$$ $$$$o IPHPT BUG o$$$$" $ $$$$ o "$$$$$oo o$$$$$$ "o$$$$ $ $$$$$ o$$"" $ $$$$$o" "$$$$$$$$$$$$$ o o$$$$$o$ "" $$ $$" $ $$$" o"o$$$$$$$$$$$$ " "$$$ $ $$o o$$ "o $$ " $$$$$$$$$$$"o "$$ $ $$$ $$$ oo$ $ o""$$""$$$o " $"o$o $$$o o$$$$ o$$$"o"$oo$$$$o" o $o $$$$$oo$ $$$$o $$$$ $$$$ $$$$" $ $$$$$"" $$ o$$$ """$$$$"o" "$$$o "$$$o $$$" o """ $ $$$oo $$$$o" $$ o$$$"o" """"$ o$$$ o$" $$$ $ "$"" o$"o"$$o$$$$ "$$"o" o$$ "$oo $ " $$o $ "oo$"o$$$"o$o"$$$$o" o" $$$ ""$o $$ $$$o "o$$o$"$$"$$o$$o$$"$$o" $$$ ""o $$$ ""$$$ $$$$$$ $$$$ $" $$$$ $$ $$$$ $$$$"$$$o$ $"" $$$ $$$$ "$$$ """ $$$$ $$"" "$$ oo$" $ooo $ "$$ PHP的非阻塞或并行请求实现方式   -  叶落山城秋

PHP的非阻塞或并行请求实现方式

我们都知道,php是串行请求的,我们在碰到以下几个场景的时候,php的效率会变的比较低下:

    1:一个请求,在输出结果前,有比较长时间的耗时操作(但这个操作不影响输出结果),这时候,客户端会等待比较长的时候。

    2:如果我要同时获得多个远程接口的结果,耗时是所有接口响应耗时之和那么。

有没有办法来提升上述场景的效率呢?

答案是肯定的

1:针对第一个场景,若你使用的是FastCGI模式 ,使用fastcgi_finish_request()能把结果输出到客户端,但PHP进程继续在跑

<?php
fastcgi_finish_request();
echo "xtgxiso"//这儿结束之后的执行

备注:此方式不能算是非阻塞,只是把结果尽快输出到客户端,但本身这个php进程还是被阻塞占用着

2:使用curl_multi_init的方法

<?php
$time = time();
// 创建一对cURL资源
$ch1 = curl_init();
$ch2 = curl_init();
$ch3 = curl_init();

// 设置URL和相应的选项
curl_setopt($ch1, CURLOPT_URL, "http://test.xtgxiso.cn/sleep1.php");
curl_setopt($ch1, CURLOPT_HEADER, 0);
curl_setopt($ch2, CURLOPT_URL, "http://test.xtgxiso.cn/sleep2.php");
curl_setopt($ch2, CURLOPT_HEADER, 0);
curl_setopt($ch3, CURLOPT_URL, "http://test.xtgxiso.cn/sleep3.php");
curl_setopt($ch3, CURLOPT_HEADER, 0);

// 创建批处理cURL句柄
$mh = curl_multi_init();

// 增加2个句柄
curl_multi_add_handle($mh,$ch1);
curl_multi_add_handle($mh,$ch2);
curl_multi_add_handle($mh,$ch3);

$running=null;
// 执行批处理句柄
do {
    usleep(10000);
    curl_multi_exec($mh,$running);
} while ($running > 0);

// 关闭全部句柄
curl_multi_remove_handle($mh, $ch1);
curl_multi_remove_handle($mh, $ch2);
curl_multi_remove_handle($mh, $ch3);
curl_multi_close($mh);

echo "\n total time : ".(time()-$time)."\n";

3:使用stream_set_blocking方法

// url数组,每个url发送一个请求
$urls = array(
    "sleep1.php",
    "sleep2.php",
    "sleep3.php");

// 保存socket的数组
$sockets = array();

$time = time();
// 批量创建链接并发送数据
foreach($urls as $url)
{
    $socket = stream_socket_client("tcp://test.xtgxiso.cn:80", $errno, $errstr, 3);
    // 设置成非阻塞
    stream_set_blocking($socket, 0);
    fwrite($socket, "GET /{$url} HTTP/1.0\r\nHost: test.xtgxiso.cn\r\nAccept: */*\r\n\r\n");
    // 记录数组
    $sockets[(int)$socket] = $socket;
}

// 批量等待数据返回
while(count($sockets)>0)
{
    $read = $sockets;
    $write = $e = array();
    // 等待数据可读
    if(stream_select($read, $write, $e, 10))
    {
        // 循环读数据
        foreach($read as $socket)
        {
            // 这里是服务端返回的数据,需要的话可以循环读
            echo fread($socket, 8192)."\n";
            // 数据读取完毕关闭链接,并删除链接
            fclose($socket);
            unset($sockets[(int)$socket]);
        }
    }
}

echo time()-$time;
echo "\n";

上面两种方式请求多个url的时候,时间由原来的所有请求响应时间之和变为只是最长的那个请求的响应时间(如请求1:10ms,请求2:15ms,请求3:20ms,串行处理的时间将是:45ms,而并行处理的时间只有:25ms)

简单总结如上几种方式,这样的好处当需要请求多个url的时候,可以节省时间,从而提高效率和并发,我们可以封装个get请求的方法,post方法和get类似!

function http_get_url($arr=''){
    if ( is_array($arr) && $arr ){
        $sockets = array();
        foreach($arr as $url)
        {
            $url_info = parse_url($url);
            if ( !@$url_info["port"] ){
                $url_info["port"] = 80;
            }
            $socket = stream_socket_client("tcp://".$url_info["host"].":".$url_info["port"], $errno, $errstr,3);
            if ( $socket ) {
                stream_set_blocking($socket, 0);
                $str = "GET ".$url_info["path"]."?".$url_info["query"]." HTTP/1.0\r\nHost: ".$url_info["host"]."\r\nAccept: */*\r\n\r\n";
                echo $str;
                fwrite($socket,$str);
                $sockets[(int)$socket] = $socket;
            }
        }

        while(count($sockets)>0)
        {
            $read = $sockets;
            $write = $e = array();
            if(stream_select($read, $write, $e, 3))
            {
                foreach($read as $socket)
                {
                    $result[(int)$socket] .= fread($socket, 8192);
                    unset($sockets[(int)$socket]);
                }
            }
        }
        foreach ($result as  $k=>$v) {
            $result[$k] = trim(strstr($v,"\r\n\r\n"),"\r\n");
        }

        return $result;
    }else if ( $arr ){
        $curl = curl_init (); 
        curl_setopt($curl, CURLOPT_URL,$arr);
        curl_setopt($curl, CURLOPT_HEADER, 0);
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($curl, CURLOPT_TIMEOUT_MS,3000);
        curl_setopt($curl, CURLOPT_FOLLOWLOCATION, 1);
        curl_setopt($curl, CURLOPT_MAXREDIRS, 3);
        $result = curl_exec($curl);
        curl_close($curl);
        return $result;
    }else{
        return false;
    }
}

这个方法可以get一个或多个url,尤其在多个的时候,可以提高效率!

备注:

1:curl_multi和stream_select都是调用系统的select进行多路I/O利用

2:并行请求的场景,用swoole也更合适,fpm里,通过swoole_client把url发送到swoole的server,swoole_server天然支持并行请求,把汇总的结果返回到fpm.

本文非原创,原文链接 http://www.xtgxiso.com/php%E7%9A%84%E9%9D%9E%E9%98%BB%E5%A1%9E%E5%AE%9E%E7%8E%B0%E6%96%B9%E5%BC%8F/


欢迎转载,但请附上原文地址哦,尊重原创,谢谢大家 本文地址: https://www.iphpt.com/detail/50/
本站(PHP --> Golang)已重构,代码开源

当你能力不能满足你的野心的时候,你就该沉下心来学习