Есть ли способ в PHP выполнять асинхронные HTTP-вызовы? Меня не волнует ответ, я просто хочу сделать что-то вроде file_get_contents(), но не ждать завершения запроса перед выполнением остальной части моего кода. Это было бы очень полезно для запуска своего рода «событий» в моем приложении или запуска длительных процессов.
Есть идеи?
Название этого сообщения вводит в заблуждение. Я искал асинхронные вызовы действительно, похожие на запросы в Node.js или запрос AJAX. Принятый ответ не является асинхронным (он блокирует и не обеспечивает обратный вызов), а просто более быстрый синхронный запрос. Попробуйте изменить вопрос или принятый ответ.
Игра с обработкой соединения через заголовки и буфер небезопасна. Я только что опубликовал новый ответ, не зависящий от ОС, браузера или версии PHP.
Асинхронность не означает, что вам наплевать на ответ. Это просто означает, что вызов не блокирует выполнение основного потока. Асинхронный режим по-прежнему требует ответа, но ответ может быть обработан в другом потоке выполнения или позже в цикле событий. В этом вопросе запрашивается запрос «запустил и забыл», который может быть синхронным или асинхронным в зависимости от семантики доставки сообщения, независимо от того, заботитесь ли вы о порядке сообщения или подтверждении доставки.
Я думаю, вы должны сделать этот HTTP-запрос fire в неблокирующем режиме (w / c - это то, что вы действительно хотите). Потому что, когда вы вызываете ресурс, вы в основном хотите знать, достигли ли вы сервера или нет (или по любой другой причине, вам просто нужен ответ). Лучшим ответом на самом деле является fsockopen и установка чтения или записи потока в неблокирующий режим. Это как вызвать и забыть.






Вы можете обмануть, используя exec () для вызова чего-то, что может выполнять HTTP-запросы, например wget, но вы должны направить весь вывод из программы куда-нибудь, например, в файл или / dev / null, иначе процесс PHP будет ждать этого выход.
Если вы хотите полностью отделить процесс от потока apache, попробуйте что-нибудь вроде (я не уверен в этом, но надеюсь, что вы уловили идею):
exec('bash -c "wget -O (url goes here) > /dev/null 2>&1 &"');
Это неприятный бизнес, и вам, вероятно, понадобится что-то вроде задания cron, вызывающего скрипт пульса, который опрашивает реальную очередь событий базы данных для выполнения реальных асинхронных событий.
Точно так же я сделал следующее: exec ("curl $ url> / dev / null &");
Вопрос: есть ли преимущество вызова 'bash -c "wget"', а не просто 'wget'?
В моем тестировании использование exec("curl $url > /dev/null 2>&1 &"); - одно из самых быстрых решений. Это намного быстрее (1,9 с на 100 итераций), чем функция post_without_wait() (14,8 с) в «принятом» ответе выше. И это однострочный ...
Используйте полный путь (например, / usr / bin / curl), чтобы сделать его еще быстрее
ждать, пока сценарий не будет завершен?
Разве exec() не отключен на большинстве общих серверов?
Если вы управляете целью, которую хотите вызвать асинхронно (например, ваш собственный longtask.php), вы можете закрыть соединение с этого конца, и оба сценария будут выполняться параллельно. Это работает так:
Я пробовал это, и он отлично работает. Но quick.php ничего не будет знать о том, как работает longtask.php, если вы не создадите какие-либо средства связи между процессами.
Попробуйте использовать этот код в longtask.php, прежде чем делать что-либо еще. Он закроет соединение, но все равно продолжит работу (и подавит любой вывод):
while(ob_get_level()) ob_end_clean();
header('Connection: close');
ignore_user_abort();
ob_start();
echo('Connection Closed');
$size = ob_get_length();
header("Content-Length: $size");
ob_end_flush();
flush();
Код скопирован из Примечания, внесенные пользователем в руководство по PHP и несколько улучшен.
Это сработает. Но если вы используете платформу MVC, это может быть сложно реализовать из-за того, как эта структура перехватывает и перезаписывает вызовы. Например, он не работает в контроллере в CakePHP.
Сомневаетесь в этом коде, процесс, который вам нужно выполнить в longtask, должен идти после этих строк? Спасибо.
Не работает идеально. Попробуйте добавить while(true); после вашего кода. Страница зависнет, это означает, что она все еще работает на переднем плане.
Как мне "открыть через cURL"? Как мне «создать какие-то средства связи между процессами»?
/**
* Asynchronously execute/include a PHP file. Does not record the output of the file anywhere.
*
* @param string $filename file to execute, relative to calling script
* @param string $options (optional) arguments to pass to file via the command line
*/
function asyncInclude($filename, $options = '') {
exec("/path/to/php -f {$filename} {$options} >> /dev/null &");
}
Это не асинхронно, потому что exec блокируется до тех пор, пока вы не завершите или не ответите на процесс, который хотите запустить.
Вы заметили & в конце?
Так будет ли это блокировать сценарий или нет, я запутался?
@pleshy не будет. амперсанд (&) означает запуск скрипта в фоновом режиме
Разве exec() не отключен на большинстве общих серверов?
Ответ, который я ранее принял, не сработал. Он все еще ждал отзывов. Это действительно работает, взято из Как мне сделать асинхронный запрос GET в PHP?
function post_without_wait($url, $params)
{
foreach ($params as $key => &$val) {
if (is_array($val)) $val = implode(',', $val);
$post_params[] = $key.'='.urlencode($val);
}
$post_string = implode('&', $post_params);
$parts=parse_url($url);
$fp = fsockopen($parts['host'],
isset($parts['port'])?$parts['port']:80,
$errno, $errstr, 30);
$out = "POST ".$parts['path']." HTTP/1.1\r\n";
$out.= "Host: ".$parts['host']."\r\n";
$out.= "Content-Type: application/x-www-form-urlencoded\r\n";
$out.= "Content-Length: ".strlen($post_string)."\r\n";
$out.= "Connection: Close\r\n\r\n";
if (isset($post_string)) $out.= $post_string;
fwrite($fp, $out);
fclose($fp);
}
Если вы посмотрите ссылку, которую вы разместили здесь, мой ответ также включает способ выполнения запросов GET.
Это НЕ асинхронно! В частности, если сервер на другой стороне не работает, этот фрагмент кода будет зависать на 30 секунд (5-й параметр в fsockopen). Также fwrite займет свое приятное время для выполнения (которое вы можете ограничить с помощью stream_set_timeout ($ fp, $ my_timeout). Лучшее, что вы можете сделать, это установить низкий тайм-аут для fsockopen на 0,1 (100 мс) и $ my_timeout на 100 мс Однако вы рискуете, что тайм-аут запроса.
Уверяю вас, что это асинхронно и не занимает 30 секунд. Это таймаут макс. Возможно, у вас разные настройки, вызывающие такой эффект, но у меня это отлично сработало.
@UltimateBrent В коде нет ничего, что предполагало бы асинхронность. Он не ждет ответа, но это не асинхронно. Если удаленный сервер открывает соединение, а затем зависает, этот код будет ждать 30 секунд, пока вы не достигнете этого тайм-аута.
Я не знаю, что тебе сказать. Я использую его повсюду, и он работает как задумано.
причина, по которой кажется, что он работает «асинхронно», потому что вы не читаете из сокета перед его закрытием, поэтому он не зависает, даже если сервер не отправил ответ вовремя. Однако это абсолютно не асинхронно. Если буфер записи заполнен (что очень маловероятно), ваш скрипт обязательно там зависнет. Вам следует подумать об изменении заголовка на что-то вроде «запрос веб-страницы, не дожидаясь ответа».
вот статья про асинхронность действительный: vince.shiftrunstop.com/dev/…
Может быть, http_build_query будет полезен для построения строки запроса ...
Это не асинхронный и не использование curl, как вы осмеливаетесь называть его curl_post_async и получать даже положительные голоса ...
Я не называл это так, я скопировал из связанного мной вопроса. Однако я обновил имя функции.
Я пытаюсь использовать его, но на самом деле запрос не отправляется каждый раз ... Когда я отлаживаю и просматриваю код, я всегда вижу запрос в журнале целевого сервера ... но при обычном использовании нет .. . Почему это могло быть?
Как вы читаете на запрошенной странице php $ post_string (параметры)? Я пробую if (isset ($ _ GET ["myvar"])) $ token = $ _GET ["myvar"]; но пусто :(
exec("curl $url > /dev/null 2>&1 &"); - одно из самых быстрых решений здесь (спасибо @Matt Huggins). Это намного быстрее (1,9 с на 100 итераций), чем функция post_without_wait() (14,8 с). И он не имеет тех же ограничений тайм-аута / перезаписи URL / и т. д., Поскольку это полноценный cURL. И это однострочный ...
@DavidKrmpotic, что случилось со мной, я добавил sleep(1); до fclose(). Это должно сработать.
@UltimateBrent Я не думаю, что вы понимаете значение async, как уже говорилось в нескольких ответах, это не имеет ничего общего с async. Может это поможет: stackoverflow.com/questions/748175/…
Работает хорошо, в то время как Guzzle, например, который рекомендуется в других потоках, не позволяет "выстрелить и забыть"
Как, черт возьми, это принятый ответ? Это совсем не асинхронно. Он использует 100% блокировку ввода-вывода. В этом коде нет ничего асинхронного.
Я не хочу POST ...
позволь мне показать тебе свой путь :)
требуется установка nodejs на сервере
(мой сервер отправляет 1000 запросов https на получение всего за 2 секунды)
url.php:
<?
$urls = array_fill(0, 100, 'http://google.com/blank.html');
function execinbackground($cmd) {
if (substr(php_uname(), 0, 7) == "Windows"){
pclose(popen("start /B ". $cmd, "r"));
}
else {
exec($cmd . " > /dev/null &");
}
}
fwite(fopen("urls.txt","w"),implode("\n",$urls);
execinbackground("nodejs urlscript.js urls.txt");
// { do your work while get requests being executed.. }
?>
urlscript.js>
var https = require('https');
var url = require('url');
var http = require('http');
var fs = require('fs');
var dosya = process.argv[2];
var logdosya = 'log.txt';
var count=0;
http.globalAgent.maxSockets = 300;
https.globalAgent.maxSockets = 300;
setTimeout(timeout,100000); // maximum execution time (in ms)
function trim(string) {
return string.replace(/^\s*|\s*$/g, '')
}
fs.readFile(process.argv[2], 'utf8', function (err, data) {
if (err) {
throw err;
}
parcala(data);
});
function parcala(data) {
var data = data.split("\n");
count=''+data.length+'-'+data[1];
data.forEach(function (d) {
req(trim(d));
});
/*
fs.unlink(dosya, function d() {
console.info('<%s> file deleted', dosya);
});
*/
}
function req(link) {
var linkinfo = url.parse(link);
if (linkinfo.protocol == 'https:') {
var options = {
host: linkinfo.host,
port: 443,
path: linkinfo.path,
method: 'GET'
};
https.get(options, function(res) {res.on('data', function(d) {});}).on('error', function(e) {console.error(e);});
} else {
var options = {
host: linkinfo.host,
port: 80,
path: linkinfo.path,
method: 'GET'
};
http.get(options, function(res) {res.on('data', function(d) {});}).on('error', function(e) {console.error(e);});
}
}
process.on('exit', onExit);
function onExit() {
log();
}
function timeout()
{
console.info("i am too far gone");process.exit();
}
function log()
{
var fd = fs.openSync(logdosya, 'a+');
fs.writeSync(fd, dosya + '-'+count+'\n');
fs.closeSync(fd);
}
Обратите внимание, что многие хостинг-провайдеры не разрешают использование определенных функций PHP (например, popen / exec). См. Директиву PHP disable_functions.
Разве exec() не отключен на большинстве общих серверов? Кроме того, мне нужно чистое PHP-решение.
Ну, таймаут можно выставить в миллисекундах, см. "CURLOPT_CONNECTTIMEOUT_MS" в http://www.php.net/manual/en/function.curl-setopt
Ставил только в шапку думал тайм-аут. Это совсем не асинхронный режим.
Вы уверены, что это асинхронный режим? Не похоже ...
Расширение swoole. https://github.com/matyhtf/swoole Фреймворк для асинхронных и параллельных сетей для PHP.
$client = new swoole_client(SWOOLE_SOCK_TCP, SWOOLE_SOCK_ASYNC);
$client->on("connect", function($cli) {
$cli->send("hello world\n");
});
$client->on("receive", function($cli, $data){
echo "Receive: $data\n";
});
$client->on("error", function($cli){
echo "connect fail\n";
});
$client->on("close", function($cli){
echo "close\n";
});
$client->connect('127.0.0.1', 9501, 0.5);
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет?
Вот моя собственная функция PHP, когда я выполняю POST для определенного URL-адреса любой страницы .... Пример: *** использование моей функции ...
<?php
parse_str("[email protected]&subject=this is just a test");
$_POST['email']=$email;
$_POST['subject']=$subject;
echo HTTP_POST("http://example.com/mail.php",$_POST);***
exit;
?>
<?php
/*********HTTP POST using FSOCKOPEN **************/
// by ArbZ
function HTTP_Post($URL,$data, $referrer = "") {
// parsing the given URL
$URL_Info=parse_url($URL);
// Building referrer
if ($referrer= = "") // if not given use this script as referrer
$referrer=$_SERVER["SCRIPT_URI"];
// making string from $data
foreach($data as $key=>$value)
$values[] = "$key = ".urlencode($value);
$data_string=implode("&",$values);
// Find out which port is needed - if not given use standard (=80)
if (!isset($URL_Info["port"]))
$URL_Info["port"]=80;
// building POST-request: HTTP_HEADERs
$request. = "POST ".$URL_Info["path"]." HTTP/1.1\n";
$request. = "Host: ".$URL_Info["host"]."\n";
$request. = "Referer: $referer\n";
$request. = "Content-type: application/x-www-form-urlencoded\n";
$request. = "Content-length: ".strlen($data_string)."\n";
$request. = "Connection: close\n";
$request. = "\n";
$request.=$data_string."\n";
$fp = fsockopen($URL_Info["host"],$URL_Info["port"]);
fputs($fp, $request);
while(!feof($fp)) {
$result .= fgets($fp, 128);
}
fclose($fp); //$eco = nl2br();
function getTextBetweenTags($string, $tagname) {
$pattern = "/<$tagname ?.*>(.*)</$tagname>/";
preg_match($pattern, $string, $matches);
return $matches[1];
}
//STORE THE FETCHED CONTENTS to a VARIABLE, because its way better and fast...
$str = $result;
$txt = getTextBetweenTags($str, "span"); $eco = $txt; $result = explode("&",$result);
return $result[1];
<span style=background-color:LightYellow;color:blue>".trim($_GET['em'])."</span>
</pre> ";
}
</pre>
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет?
Вот рабочий пример, просто запустите его, а потом откройте storage.txt, чтобы проверить волшебный результат.
<?php
function curlGet($target){
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $target);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result = curl_exec ($ch);
curl_close ($ch);
return $result;
}
// Its the next 3 lines that do the magic
ignore_user_abort(true);
header("Connection: close"); header("Content-Length: 0");
echo str_repeat("s", 100000); flush();
$i = $_GET['i'];
if (!is_numeric($i)) $i = 1;
if ($i > 4) exit;
if ($i == 1) file_put_contents('storage.txt', '');
file_put_contents('storage.txt', file_get_contents('storage.txt') . time() . "\n");
sleep(5);
curlGet($_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '?i=' . ($i + 1));
curlGet($_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME'] . '?i=' . ($i + 1));
Вы уверены, что это асинхронный режим? Не похоже ...
Вы можете использовать неблокирующие сокеты и одно из расширений pecl для PHP:
Вы можете использовать библиотеку, которая дает вам уровень абстракции между вашим кодом и расширением pecl: https://github.com/reactphp/event-loop
Также можно использовать асинхронный http-клиент, основанный на предыдущей библиотеке: https://github.com/reactphp/http-client
См. Другие библиотеки ReactPHP: http://reactphp.org
Будьте осторожны с асинхронной моделью. Рекомендую посмотреть это видео на youtube: https://thewikihow.com/video_MWNcItWuKpI
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет?
Подделать запрос на прерывание беременности с помощью CURL, установив низкий CURLOPT_TIMEOUT_MS
установите ignore_user_abort(true) для продолжения обработки после закрытия соединения.
С помощью этого метода нет необходимости реализовывать обработку соединения через заголовки и буфер, слишком зависящие от ОС, браузера и версии PHP.
Мастер-процесс
function async_curl($background_process=''){
//-------------get curl contents----------------
$ch = curl_init($background_process);
curl_setopt_array($ch, array(
CURLOPT_HEADER => 0,
CURLOPT_RETURNTRANSFER =>true,
CURLOPT_NOSIGNAL => 1, //to timeout immediately if the value is < 1000 ms
CURLOPT_TIMEOUT_MS => 50, //The maximum number of mseconds to allow cURL functions to execute
CURLOPT_VERBOSE => 1,
CURLOPT_HEADER => 1
));
$out = curl_exec($ch);
//-------------parse curl contents----------------
//$header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
//$header = substr($out, 0, $header_size);
//$body = substr($out, $header_size);
curl_close($ch);
return true;
}
async_curl('http://example.com/background_process_1.php');
Фоновый процесс
ignore_user_abort(true);
//do something...
NB
If you want cURL to timeout in less than one second, you can use CURLOPT_TIMEOUT_MS, although there is a bug/"feature" on "Unix-like systems" that causes libcurl to timeout immediately if the value is < 1000 ms with the error "cURL Error (28): Timeout was reached". The explanation for this behavior is:
[...]
The solution is to disable signals using CURLOPT_NOSIGNAL
Ресурсы
Как вы обрабатываете тайм-аут соединения (разрешение, DNS)? Когда я устанавливаю timeout_ms на 1, я всегда получаю «разрешение тайм-аута через 4 мс» или что-то в этом роде
Я не знаю, но для меня 4 мс уже звучат довольно быстро ... Я не думаю, что вы можете решить проблему быстрее, изменив какие-либо настройки curl. Возможно, попробуйте оптимизировать целевой запрос ...
Хорошо, но timeout_ms = 1 устанавливает тайм-аут для всего запроса. Поэтому, если ваше решение занимает более 1 мс, то curl отключит тайм-аут и остановит запрос. Я не понимаю, как это вообще может работать (при условии, что разрешение занимает> 1 мс).
Я вообще этого не понимаю ...
class async_file_get_contents extends Thread{
public $ret;
public $url;
public $finished;
public function __construct($url) {
$this->finished=false;
$this->url=$url;
}
public function run() {
$this->ret=file_get_contents($this->url);
$this->finished=true;
}
}
$afgc=new async_file_get_contents("http://example.org/file.ext");
У меня не работает. Да, он отлично извлекает файлы, но все равно работает так же медленно, как и обычный file_get_contents().
Вы можете использовать эту библиотеку: https://github.com/stil/curl-easy
Тогда это довольно просто:
<?php
$request = new cURL\Request('http://yahoo.com/');
$request->getOptions()->set(CURLOPT_RETURNTRANSFER, true);
// Specify function to be called when your request is complete
$request->addListener('complete', function (cURL\Event $event) {
$response = $event->response;
$httpCode = $response->getInfo(CURLINFO_HTTP_CODE);
$html = $response->getContent();
echo "\nDone.\n";
});
// Loop below will run as long as request is processed
$timeStart = microtime(true);
while ($request->socketPerform()) {
printf("Running time: %dms \r", (microtime(true) - $timeStart)*1000);
// Here you can do anything else, while your request is in progress
}
Ниже вы можете увидеть вывод на консоль приведенного выше примера. Он будет отображать простые живые часы, показывающие, сколько времени выполняется запрос:

Это должен быть принятый ответ на вопрос, потому что, даже если он не является истинным асинхронным, он лучше, чем принятый один и все «асинхронные» ответы с жадностью (здесь вы можете выполнять операции во время выполнения запроса)
Принятый ответ ©
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет?
Расширение Мероприятие очень уместно. Это порт библиотеки Libevent, который предназначен для ввода-вывода, управляемого событиями, в основном для работы в сети.
Я написал образец HTTP-клиента, который позволяет запланировать ряд HTTP-запросы и запускать их асинхронно.
Это образец класса HTTP-клиента на основе расширения Мероприятие.
Класс позволяет планировать ряд HTTP-запросов, а затем запускать их асинхронно.
<?php
class MyHttpClient {
/// @var EventBase
protected $base;
/// @var array Instances of EventHttpConnection
protected $connections = [];
public function __construct() {
$this->base = new EventBase();
}
/**
* Dispatches all pending requests (events)
*
* @return void
*/
public function run() {
$this->base->dispatch();
}
public function __destruct() {
// Destroy connection objects explicitly, don't wait for GC.
// Otherwise, EventBase may be free'd earlier.
$this->connections = null;
}
/**
* @brief Adds a pending HTTP request
*
* @param string $address Hostname, or IP
* @param int $port Port number
* @param array $headers Extra HTTP headers
* @param int $cmd A EventHttpRequest::CMD_* constant
* @param string $resource HTTP request resource, e.g. '/page?a=b&c=d'
*
* @return EventHttpRequest|false
*/
public function addRequest($address, $port, array $headers,
$cmd = EventHttpRequest::CMD_GET, $resource = '/')
{
$conn = new EventHttpConnection($this->base, null, $address, $port);
$conn->setTimeout(5);
$req = new EventHttpRequest([$this, '_requestHandler'], $this->base);
foreach ($headers as $k => $v) {
$req->addHeader($k, $v, EventHttpRequest::OUTPUT_HEADER);
}
$req->addHeader('Host', $address, EventHttpRequest::OUTPUT_HEADER);
$req->addHeader('Connection', 'close', EventHttpRequest::OUTPUT_HEADER);
if ($conn->makeRequest($req, $cmd, $resource)) {
$this->connections []= $conn;
return $req;
}
return false;
}
/**
* @brief Handles an HTTP request
*
* @param EventHttpRequest $req
* @param mixed $unused
*
* @return void
*/
public function _requestHandler($req, $unused) {
if (is_null($req)) {
echo "Timed out\n";
} else {
$response_code = $req->getResponseCode();
if ($response_code == 0) {
echo "Connection refused\n";
} elseif ($response_code != 200) {
echo "Unexpected response: $response_code\n";
} else {
echo "Success: $response_code\n";
$buf = $req->getInputBuffer();
echo "Body:\n";
while ($s = $buf->readLine(EventBuffer::EOL_ANY)) {
echo $s, PHP_EOL;
}
}
}
}
}
$address = "my-host.local";
$port = 80;
$headers = [ 'User-Agent' => 'My-User-Agent/1.0', ];
$client = new MyHttpClient();
// Add pending requests
for ($i = 0; $i < 10; $i++) {
$client->addRequest($address, $port, $headers,
EventHttpRequest::CMD_GET, '/test.php?a=' . $i);
}
// Dispatch pending requests
$client->run();
Это образец сценария на стороне сервера.
<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;
echo 'User-Agent: ', $_SERVER['HTTP_USER_AGENT'] ?? '(none)', PHP_EOL;
php http-client.php
Пример вывода
Success: 200
Body:
GET: array (
'a' => '1',
)
User-Agent: My-User-Agent/1.0
Success: 200
Body:
GET: array (
'a' => '0',
)
User-Agent: My-User-Agent/1.0
Success: 200
Body:
GET: array (
'a' => '3',
)
...
(Обрезанный.)
Обратите внимание, код предназначен для длительной обработки в CLI SAPI.
Для пользовательских протоколов рассмотрите возможность использования низкоуровневого API, то есть буферные события, буферы. Для связи SSL / TLS я бы рекомендовал низкоуровневый API в сочетании с Event ssl context. Примеры:
Хотя HTTP API Libevent прост, он не так гибок, как буферные события. Например, HTTP API в настоящее время не поддерживает пользовательские методы HTTP. Но можно реализовать практически любой протокол с помощью низкоуровневого API.
Я также написал образец другого HTTP-клиента, использующего расширение Ev с Розетки в неблокирующий режим. Код немного более подробный, чем пример, основанный на событии, потому что Ev - это цикл обработки событий общего назначения. Он не предоставляет специфичных для сети функций, но его наблюдатель EvIo способен, в частности, прослушивать файловый дескриптор, инкапсулированный в ресурс сокета.
Это образец HTTP-клиента на основе расширения Ev.
Расширение Ev реализует простой, но мощный цикл обработки событий общего назначения. Он не предоставляет наблюдателей для конкретной сети, но его Наблюдатель ввода / вывода можно использовать для асинхронной обработки Розетки.
В следующем коде показано, как можно запланировать параллельную обработку HTTP-запросов.
<?php
class MyHttpRequest {
/// @var MyHttpClient
private $http_client;
/// @var string
private $address;
/// @var string HTTP resource such as /page?get=param
private $resource;
/// @var string HTTP method such as GET, POST etc.
private $method;
/// @var int
private $service_port;
/// @var resource Socket
private $socket;
/// @var double Connection timeout in seconds.
private $timeout = 10.;
/// @var int Chunk size in bytes for socket_recv()
private $chunk_size = 20;
/// @var EvTimer
private $timeout_watcher;
/// @var EvIo
private $write_watcher;
/// @var EvIo
private $read_watcher;
/// @var EvTimer
private $conn_watcher;
/// @var string buffer for incoming data
private $buffer;
/// @var array errors reported by sockets extension in non-blocking mode.
private static $e_nonblocking = [
11, // EAGAIN or EWOULDBLOCK
115, // EINPROGRESS
];
/**
* @param MyHttpClient $client
* @param string $host Hostname, e.g. google.co.uk
* @param string $resource HTTP resource, e.g. /page?a=b&c=d
* @param string $method HTTP method: GET, HEAD, POST, PUT etc.
* @throws RuntimeException
*/
public function __construct(MyHttpClient $client, $host, $resource, $method) {
$this->http_client = $client;
$this->host = $host;
$this->resource = $resource;
$this->method = $method;
// Get the port for the WWW service
$this->service_port = getservbyname('www', 'tcp');
// Get the IP address for the target host
$this->address = gethostbyname($this->host);
// Create a TCP/IP socket
$this->socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$this->socket) {
throw new RuntimeException("socket_create() failed: reason: " .
socket_strerror(socket_last_error()));
}
// Set O_NONBLOCK flag
socket_set_nonblock($this->socket);
$this->conn_watcher = $this->http_client->getLoop()
->timer(0, 0., [$this, 'connect']);
}
public function __destruct() {
$this->close();
}
private function freeWatcher(&$w) {
if ($w) {
$w->stop();
$w = null;
}
}
/**
* Deallocates all resources of the request
*/
private function close() {
if ($this->socket) {
socket_close($this->socket);
$this->socket = null;
}
$this->freeWatcher($this->timeout_watcher);
$this->freeWatcher($this->read_watcher);
$this->freeWatcher($this->write_watcher);
$this->freeWatcher($this->conn_watcher);
}
/**
* Initializes a connection on socket
* @return bool
*/
public function connect() {
$loop = $this->http_client->getLoop();
$this->timeout_watcher = $loop->timer($this->timeout, 0., [$this, '_onTimeout']);
$this->write_watcher = $loop->io($this->socket, Ev::WRITE, [$this, '_onWritable']);
return socket_connect($this->socket, $this->address, $this->service_port);
}
/**
* Callback for timeout (EvTimer) watcher
*/
public function _onTimeout(EvTimer $w) {
$w->stop();
$this->close();
}
/**
* Callback which is called when the socket becomes wriable
*/
public function _onWritable(EvIo $w) {
$this->timeout_watcher->stop();
$w->stop();
$in = implode("\r\n", [
"{$this->method} {$this->resource} HTTP/1.1",
"Host: {$this->host}",
'Connection: Close',
]) . "\r\n\r\n";
if (!socket_write($this->socket, $in, strlen($in))) {
trigger_error("Failed writing $in to socket", E_USER_ERROR);
return;
}
$loop = $this->http_client->getLoop();
$this->read_watcher = $loop->io($this->socket,
Ev::READ, [$this, '_onReadable']);
// Continue running the loop
$loop->run();
}
/**
* Callback which is called when the socket becomes readable
*/
public function _onReadable(EvIo $w) {
// recv() 20 bytes in non-blocking mode
$ret = socket_recv($this->socket, $out, 20, MSG_DONTWAIT);
if ($ret) {
// Still have data to read. Append the read chunk to the buffer.
$this->buffer .= $out;
} elseif ($ret === 0) {
// All is read
printf("\n<<<<\n%s\n>>>>", rtrim($this->buffer));
fflush(STDOUT);
$w->stop();
$this->close();
return;
}
// Caught EINPROGRESS, EAGAIN, or EWOULDBLOCK
if (in_array(socket_last_error(), static::$e_nonblocking)) {
return;
}
$w->stop();
$this->close();
}
}
/////////////////////////////////////
class MyHttpClient {
/// @var array Instances of MyHttpRequest
private $requests = [];
/// @var EvLoop
private $loop;
public function __construct() {
// Each HTTP client runs its own event loop
$this->loop = new EvLoop();
}
public function __destruct() {
$this->loop->stop();
}
/**
* @return EvLoop
*/
public function getLoop() {
return $this->loop;
}
/**
* Adds a pending request
*/
public function addRequest(MyHttpRequest $r) {
$this->requests []= $r;
}
/**
* Dispatches all pending requests
*/
public function run() {
$this->loop->run();
}
}
/////////////////////////////////////
// Usage
$client = new MyHttpClient();
foreach (range(1, 10) as $i) {
$client->addRequest(new MyHttpRequest($client, 'my-host.local', '/test.php?a=' . $i, 'GET'));
}
$client->run();
Предположим, сценарий http://my-host.local/test.php распечатывает дамп $_GET:
<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;
Тогда вывод команды php http-client.php будет похож на следующий:
<<<<
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Fri, 02 Dec 2016 12:39:54 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/7.0.13-pl0-gentoo
1d
GET: array (
'a' => '3',
)
0
>>>>
<<<<
HTTP/1.1 200 OK
Server: nginx/1.10.1
Date: Fri, 02 Dec 2016 12:39:54 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/7.0.13-pl0-gentoo
1d
GET: array (
'a' => '2',
)
0
>>>>
...
(обрезано)
Обратите внимание, что в PHP 5 расширение Розетки может регистрировать предупреждения для значений EINPROGRESS, EAGAIN и EWOULDBLOCKerrno. Можно отключить журналы с помощью
error_reporting(E_ERROR);
I just want to do something like
file_get_contents(), but not wait for the request to finish before executing the rest of my code.
Код, который должен работать параллельно с сетевыми запросами, может выполняться, например, в обратном вызове Таймер событий или Ev праздный наблюдатель. В этом легко разобраться, посмотрев образцы, упомянутые выше. В противном случае добавлю еще один пример :)
По состоянию на 2018 год Жрать стал фактически стандартной библиотекой для HTTP-запросов, используемой в нескольких современных фреймворках. Он написан на чистом PHP и не требует установки каких-либо дополнительных расширений.
Он может очень хорошо выполнять асинхронные HTTP-вызовы и даже объединить их, например, когда вам нужно сделать 100 HTTP-вызовов, но вы не хотите запускать более 5 за раз.
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
$client = new Client(['base_uri' => 'http://httpbin.org/']);
// Initiate each request but do not block
$promises = [
'image' => $client->getAsync('/image'),
'png' => $client->getAsync('/image/png'),
'jpeg' => $client->getAsync('/image/jpeg'),
'webp' => $client->getAsync('/image/webp')
];
// Wait on all of the requests to complete. Throws a ConnectException
// if any of the requests fail
$results = Promise\unwrap($promises);
// Wait for the requests to complete, even if some of them fail
$results = Promise\settle($promises)->wait();
// You can access each result using the key provided to the unwrap
// function.
echo $results['image']['value']->getHeader('Content-Length')[0]
echo $results['png']['value']->getHeader('Content-Length')[0]
См. http://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests
Однако этот ответ не асинхронен. очевидно жрать не делает этого
Guzzle требует, чтобы вы установили curl. В противном случае он не является параллельным и не дает вам никаких предупреждений о том, что он непараллельный.
Спасибо за ссылку @daslicious - да, похоже, это не полностью асинхронно (например, когда вы хотите отправить запрос, но не заботитесь о результате), но несколько сообщений в этом потоке пользователь предложил обходной путь установка очень низкого значения тайм-аута запроса, которое по-прежнему разрешает время соединения, но не ждет результата.
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне установить Guzzle, если до этого дойдет?
ReactPHP асинхронный http client
https://github.com/shuchkin/react-http-client
Установить через Composer
$ composer require shuchkin/react-http-client
Асинхронный HTTP GET
// get.php
$loop = \React\EventLoop\Factory::create();
$http = new \Shuchkin\ReactHTTP\Client( $loop );
$http->get( 'https://tools.ietf.org/rfc/rfc2068.txt' )->then(
function( $content ) {
echo $content;
},
function ( \Exception $ex ) {
echo 'HTTP error '.$ex->getCode().' '.$ex->getMessage();
}
);
$loop->run();
Запускаем php в CLI-режиме
$ php get.php
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет?
Я считаю этот пакет весьма полезным и очень простым: https://github.com/amphp/parallel-functions
<?php
use function Amp\ParallelFunctions\parallelMap;
use function Amp\Promise\wait;
$responses = wait(parallelMap([
'https://google.com/',
'https://github.com/',
'https://stackoverflow.com/',
], function ($url) {
return file_get_contents($url);
}));
Он загрузит все 3 URL-адреса параллельно. Вы также можете использовать методы экземпляра класса в закрытии.
Например, я использую расширение Laravel на основе этого пакета https://github.com/spatie/laravel-collection-macros#parallelmap
Вот мой код:
/**
* Get domains with all needed data
*/
protected function getDomainsWithdata(): Collection
{
return $this->opensrs->getDomains()->parallelMap(function ($domain) {
$contact = $this->opensrs->getDomainContact($domain);
$contact['domain'] = $domain;
return $contact;
}, 10);
}
Он загружает все необходимые данные в 10 параллельных потоках и вместо 50 секунд без асинхронности завершает работу всего за 8 секунд.
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет?
@ RedGuy11 composer require amphp/parallel-functions
где мне запустить это?
В терминале (консоли)
Symfony HttpClient - асинхронный https://symfony.com/doc/current/components/http_client.html.
Например, вы можете
use Symfony\Component\HttpClient\HttpClient;
$client = HttpClient::create();
$response1 = $client->request('GET', 'https://website1');
$response2 = $client->request('GET', 'https://website1');
$response3 = $client->request('GET', 'https://website1');
//these 3 calls with return immediately
//but the requests will fire to the website1 webserver
$response1->getContent(); //this will block until content is fetched
$response2->getContent(); //same
$response3->getContent(); //same
Я не хочу ничего устанавливать на свой сервер; Я хочу чистую версию PHP. Но как мне это установить, если до этого дойдет?
Это чистый php, но для работы вам понадобится расширение curl php.
хм ок. Я просто использовал curl_multi, хотя
одна функция - curl_multi, поищите ее в документации php. Должен решить твои проблемы