php异步请求方法fsockopen和curl方式,不需要等待接口返回数据,只管发送即可,用在日志、调试等地方

这是一个利用实现异步非阻塞请求的方法,这样可以在收集记录、远程调试等方便执行,下面是fsockopen方式实现,curl方式在后面。

<?php
/**
 * 用fsockopen异步请求发送数据
 * @param $url
 * @param array $data
 * @param string $method
 * @return array|int[]
 * @author blog.alipay168.cn
 */
function sock_request($url, $data = [], $method = 'POST')
{
    $method = strtoupper($method);
    $parse = parse_url($url);
    $host = $parse['host'];
    $port = !empty($parse['port']) ? intval($parse['port']) : 80;
    $path = !empty($parse['path']) ? trim($parse['path']) : '';
    $scheme = !empty($parse['scheme']) ? trim($parse['scheme']) : '';
    $fp = fsockopen($host, $port, $error_code, $error_msg, 1);
    if (!$fp) {
        return array('code' => $error_code, 'msg' => $error_msg);
    }
    $query_str = '';
    if ($data) {
        $query_str = is_array($data)? http_build_query($data):$data;
    }
    ////阻塞模式:0-非阻塞,1-阻塞
    stream_set_blocking($fp, 0);
    //超时时间
    stream_set_timeout($fp, 1);

    if ($method=='GET'){
        $con =  "GET $path?$query_str HTTP/1.1\r\n";
        $con .= "Host: $host\r\n";
        $con .= "Connection: close\r\n\r\n";//长连接关闭
    }else{
        $con =  "POST $path HTTP/1.1\r\n";
        $con .= "Host: $host\r\n";
        //类型自定义,下面的方式可以用$_POST获取
      //  $con .="Content-Type: application/x-www-form-urlencoded\r\n";
        $con .="Content-Length:".strlen($query_str)."\r\n\r\n";
        //post内容
        $con .="$query_str\r\n";
        $con .= "Connection: close\r\n\r\n";//长连接关闭

    }
    fwrite($fp, $con);
    //修复nginx请求不成功问题
    usleep(1000);
    fclose($fp);
    //输出字符串
    echo $con;
    return array('code' => 0);
}
####测试

//get方式请求
sock_request("http://t.abc.top/abc.php", [
    'type' => 'test.t.mu',
    'time' => time(),
    'w' =>'a',
    'token' => 'adf'
],'GET');
//post一个json格式,后台用file_get_contents("php://input");获取后josn_decode就可以了,这是常用的方式,推荐使用
sock_request("http://t.abc.top/abc.php", json_encode([
    'type' => 'test.t.mu',
    'time' => time(),
    'w' =>'b',
    'token' => 'adf'
]),'post');
//post一个常规数组,后台用file_get_contents("php://input");获取然后自行解析,不推荐这种方式
sock_request("http://t.abc.top/abc.php", [
    'type' => 'test.t.mu',
    'time' => time(),
    'w' =>'c',
    'token' => 'adf'
],'post');


##abc.php记录日志
{"req":{"type":"test.t.mu","time":"1635147330","w":"a","token":"adf"},"post":[],"input":"","input1":null}
{"req":[],"post":[],"input":"{\"type\":\"test.t.mu\",\"time\":1635147330,\"w\":\"b\",\"token\":\"adf\"}","input1":{"type":"test.t.mu","time":1635147330,"w":"b","token":"adf"}}
{"req":[],"post":[],"input":"type=test.t.mu&time=1635147330&w=c&token=adf","input1":null}


请求时输出格式:


微信截图_20211025154219.png


要注意的地方:

  1. get请求直接在path后面加上?xx=abc即可,最后的拼接要两个换行:"\r\n\r\n";

  2. post请求设置Content-length时后面需要换行符合并且是两个,否则不成功,"\r\n\r\n"

  3. post设置CONTENT-TYPE时有不同的效果,和前端form提交一致,默认用get_file_contents("php://input")获取post数据

  4. 测试记得修改自己的模拟域名t.abc.top



下面是测试接收数据的abc.php

<?php
//故意延迟看异步效果
//sleep(10);
$d = $_REQUEST;
$d1 = file_get_contents('php://input');
$server = $_SERVER;
foreach ($server as $key => $val) {
    if (0 === strpos($key, 'HTTP_')) {
        $key = str_replace('_', '-', strtolower(substr($key, 5)));
        $header[$key] = $val;
    }
}
if (isset($server['CONTENT_TYPE'])) {
    $header['content-type'] = $server['CONTENT_TYPE'];
}
if (isset($server['CONTENT_LENGTH'])) {
    $header['content-length'] = $server['CONTENT_LENGTH'];
}
if (isset($server['HTTP_PALTFORM'])) {
    $header['system-os'] = strtolower($server['HTTP_PALTFORM']);
}
file_put_contents('abc.txt', json_encode([
        'req' => $d,
        'post' => $_POST,
        'input' => $d1,
        'input1' => json_decode($d1)
        // 'header'=>$header
    ]) . PHP_EOL, FILE_APPEND);
exit(json_encode(['code' => 0, 'msg' => 'okkkk']));



下面是curl方式:

function asyn_req($api, $data = [], $method = 'GET')
{
    $ch = curl_init();
    if ($data) $data = http_build_query($data);
    if (strtolower($method) == 'get') {
        $api = $api . '?' . $data;
    }
    // 设置请求的URL和其他选项
    curl_setopt($ch, CURLOPT_URL, $api);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HEADER, false);
    // 设置为非阻塞模式
    curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
    //单位毫秒,太小可能因为来得及发起而请求失败
    curl_setopt($ch, CURLOPT_TIMEOUT_MS, 30);
    if ($method == 'get') {
        curl_setopt($ch, CURLOPT_HTTPGET, true);
    } else {
        curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data));
    }
   
    // 设置回调函数处理响应结果
    curl_setopt($ch, CURLOPT_WRITEFUNCTION, function ($curl, $data) {
        // 处理响应数据
        //file_put_contents('asyn.log', $data . PHP_EOL, FILE_APPEND);
        return strlen($data);
    });

    // 发起异步请求
    curl_exec($ch);
    // 关闭cURL资源
    curl_close($ch);

}
//测试
$api = 'http://localhost/abc/asyn_ser.php';
// 设置请求的数据
$data = [
    'key1' => 'value1',
    'key2' => 'value2',
    'time' => date('Ymd H:i:s')
];

$stime = microtime(true);

asyn_req($api, $data);

$etime = microtime(true);

print_r(['time' => $etime - $stime]);//很短的

两种方式都能实现,curl相对简单点,不用自己编写请求过程。



评论/留言