前情提要
在实际开发中,往往有一段程序需要同时执行多个操作的场景,如果在此类场景中请求方式不当,到后面的逻辑堆积数据增多之后就会影响到程序响应效率,一般这种场景下就可以使用非阻塞模式,这里也可以理解为并发模式。
什么是阻塞和非阻塞
阻塞:是指应用程序执行IO操作需要彻底完成后才返回到用户空间
非阻塞:是指应用程序执行IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。
如何实现非阻塞模式
PHP实现非阻塞模式的方式有很多,比如在PHP-FPM
模式下使用函数fastcgi_finish_request()
或者fsockopen()
等方式来实现,这里将采用原生CURL
的方式实现请求的非阻塞模式。
实现方式
主要实现核心其实是利用CURL中的 curl_multi_* 函数发送异步请求
- 创建类文件
MultiHttpRequest.php
,代码实现如下
<?php
class MultiHttpRequest
{
public $requests = [];
/**
* 设置请求url
*
* @param $requests
* @return $this
*/
public function setRequests($requests) {
$this->requests = $requests;
return $this;
}
/**
* 发送请求
*
* @return array|false
*/
public function request()
{
if(!is_array($this->requests) or count($this->requests) == 0){
return false;
}
$curl = $response = [];
$handle = curl_multi_init();
foreach($this->requests as $k => $v){
$url = isset($v['url']) ? $v['url'] : '';
$postData = isset($v['postData']) ? $v['postData'] : [];
$header = isset($v['header']) ? $v['header'] : [];
$timeOut = isset($v['timeOut']) ? $v['timeOut'] : 1;
$proxy = isset($v['proxy']) ? $v['proxy'] : '';
$curl[$k] = $this->buildCurlObject($url, $postData, $header, $timeOut, $proxy);
curl_multi_add_handle($handle, $curl[$k]);
}
$this->execHandle($handle);
foreach ($this->requests as $key => $val){
$response[$key] = curl_multi_getcontent($curl[$key]);
curl_multi_remove_handle($handle, $curl[$key]);
curl_close($curl[$key]);
}
curl_multi_close($handle);
return $response;
}
/**
* 构造请求
*
* @param $url
* @param $postData
* @param $header
* @param $timeOut
* @param $proxy
* @return false|resource
*/
private function buildCurlObject($url, $postData, $header, $timeOut, $proxy) {
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt($curl, CURLOPT_HEADER, false);
curl_setopt($curl, CURLOPT_TIMEOUT, (int)$timeOut);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
// 配置代理
if (!empty($proxy))
curl_setopt($curl, CURLOPT_PROXY, $proxy);
// 合并请求头部信息
if(!empty($header))
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
// 是否是post请求
if(!empty($postData) && is_array($postData)){
curl_setopt($curl, CURLOPT_POST, true);
curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($postData));
}
// 是否是https
if(stripos($url,'https') === 0){
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
}
return $curl;
}
/**
* 执行批处理句柄
*
* @param $handle
* @return void
*/
private function execHandle($handle)
{
$active = true;
$mrc = CURLM_OK;
while ($active && $mrc == CURLM_OK) {
do {
$mrc = curl_multi_exec($handle, $active);
} while ($mrc == CURLM_CALL_MULTI_PERFORM);
if (curl_multi_select($handle) == -1) {
usleep(100);
}
}
}
}
- 调用程序,非阻塞请求
<?php
$response = new MultiHttpRequest();
$url = [
['url' => 'https://www.baidu.com',
'postData' =>['aaa'=>1],
'header' => [
"Content-Type:application/Json",
"X-Requested-With:XMLhttpRequest"
],
'timeOut' => 2,
'proxy' => '127.0.0.1'
],
['url' => 'https://www.baidu.com'],
['url' => 'https://www.baidu.com'],
];
$response = $this->setRequests($url)->request();
var_dump($response);