Hugh's Blog

PHP 实现异步调用

PHP 实现异步调用在客户端的话比较简单,比如在页面加载完成后 jQuery 发送 AJAX 请求,或者嵌入到 img 标签,然后 src 指向请求,之前写的这篇文章 Nginx + Google Analytics 配置 就是使用 img 标签来触发谷歌分析统计,这两种都会比较依赖于浏览器与用户。

在服务端异步调用的话可以使用 curlfsockopen 函数,下面是一个简单的测试例子,其中 res.php 是请求文件。

// test.php
function sendCURL($method = 'get', $data = [])
{
    $url = 'http://127.0.0.1:8080/res.php';
    $method = strtoupper($method);
    $query = http_build_query($data);

    if ($method == 'GET') {
        $url .= "?$query";
    }

    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $query);
    curl_setopt($ch, CURLOPT_TIMEOUT, 1);
    curl_setopt($ch, CURLOPT_POST, $method === 'POST');

    $response = curl_exec($ch);

    // if ($response === false) {
    //     print_r(curl_getinfo($ch));
    //     echo '<br>';
    //     echo curl_errno($ch) . '<br>';
    //     echo curl_error($ch) . '<br>';
    // } else {
    //     header('Content-Type: text/plain');
    //     echo "CURL Response ({$method}): \r\n";
    //     print_r($response);
    // }

    curl_close($ch);
}

function sendSock($method = 'get', $data = [])
{
    $host = '127.0.0.1';
    $path = '/res.php';
    $port = 8080;

    $fp = fsockopen($host, $port, $errno, $errstr, 30);

    if (!$fp) {
        echo "$errstr ($errno)<br />\n";
    } else {

        $method = strtoupper($method);
        $content = http_build_query($data);

        if ($method === 'GET') {
            $path .= "?{$content}";
        }

        fwrite($fp, "{$method} {$path} HTTP/1.1\r\n");
        fwrite($fp, "Host: {$host}\r\n");
        fwrite($fp, "Content-Type: application/x-www-form-urlencoded\r\n");
        fwrite($fp, "Content-Length: " . strlen($content) . "\r\n");
        fwrite($fp, "Connection: close\r\n");
        fwrite($fp, "\r\n");

        fwrite($fp, $content);

        // header('Content-Type: text/plain');
        // echo "Sock Response ({$method}): \r\n";
        // while (!feof($fp)) {
        //     echo fgets($fp, 1024);
        // }

        fclose($fp);
    }
}

sendCURL('get', ['hello' => 'world!']);

echo "\r\n\r\n";
sendCURL('post', ['hello' => 'world!']);

echo "\r\n\r\n";
sendSock('get', ['hello' => 'world!']);

echo "\r\n\r\n";
sendSock('post', ['hello' => 'world!']);
// res.php
$data = [];
$data['method'] = $_SERVER['REQUEST_METHOD'];
$data['data'] = $_REQUEST;

sleep(5);

print_r($data);

本来准备简单的使用 http://localhost/res.php 来做请求地址,但是两个函数都不能访问连接,感觉很奇怪,最后还是新开一个端口做请求。

php -S 127.0.0.1:8080 -t /path/to/test/

把执行结果忽略,就可以做到异步调用了,把注释去掉,也可以看到结果,但是执行时间就不会缩短了。

执行上面的文件,大概要等 2 秒才能完成加载,因为 curl 中的 CURLOPT_TIMEOUT 最低为 1 秒,这没办法,下面是 res.php 的执行结果。

[Wed Aug 9 22:55:41 2017] 127.0.0.1:14467 [200]: /res.php?hello=world%21
[Wed Aug 9 22:55:46 2017] 127.0.0.1:14469 [200]: /res.php
[Wed Aug 9 22:55:51 2017] 127.0.0.1:14470 [200]: /res.php?hello=world%21
[Wed Aug 9 22:55:56 2017] 127.0.0.1:14471 [200]: /res.php

可以看到,每隔 5 秒,页面请求一次,就是说浏览器加载完了,后台还会继续执行之前的请求,这对用户体验来说会比较好,可以应用在发邮件通知等等。

20171012

今天运行报如下错误:

php_network_getaddresses: getaddrinfo failed: Name or service not known

修改了 DNS 之后就可以了。


参考

PHP实现异步调用方法研究