Асинхронные вызовы PHP?

Есть ли способ PHP для создания асинхронных HTTP-вызовов? Меня не волнует ответ, я просто хочу сделать что-то вроде file_get_contents() , но не дождаться завершения запроса до выполнения остальной части моего кода. Это было бы очень полезно для установки «событий» сорта в моем приложении или запуска длинных процессов.

Есть идеи?

  • PHP асинхронный cURL с обратным вызовом
  • Может ли PHP асинхронно использовать сокеты?
  • асинхронная обработка с PHP - один рабочий на работу
  • Асинхронные HTTP-запросы в PHP
  • Как импортировать огромный файл CSV с 200,00 строк в MySQL (асинхронный и быстрый)?
  • Как сделать асинхронный запрос GET в PHP?
  • Загрузка нескольких файлов в один HTTP-запрос
  • Каков размер лимита почтового запроса?
  • Как показать непрерывные обновления в реальном времени, такие как facebook ticker, домашняя страница meetup.com?
  • Получить заголовки http из текущего запроса в PHP
  • Google Chrome и потоковые HTTP-соединения?
  • Тип «application / json» предотвращает отправку почтовых переменных
  • 16 Solutions collect form web for “Асинхронные вызовы PHP?”

    Ответ, который я ранее принял, не сработал. Он все еще ждал ответов. Это работает, хотя, взято из того, как сделать асинхронный запрос 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); } 

    это требует php5, я украл его из docs.php.net и отредактировал конец.

    Я использую его для мониторинга при возникновении ошибки на сайте клиентов, он отправляет данные мне, не поднимая вывод

     function do_post_request($url, $data, $optional_headers = null,$getresponse = false) { $params = array( 'http' => array( 'method' => 'POST', 'content' => $data ) ); if ($optional_headers !== null) { $params['http']['header'] = $optional_headers; } $ctx = stream_context_create($params); $fp = @fopen($url, 'rb', false, $ctx); if (!$fp) { return false; } if ($getresponse) { $response = stream_get_contents($fp); return $response; } return true; } return false; function do_post_request($url, $data, $optional_headers = null,$getresponse = false) { $params = array( 'http' => array( 'method' => 'POST', 'content' => $data ) ); if ($optional_headers !== null) { $params['http']['header'] = $optional_headers; } $ctx = stream_context_create($params); $fp = @fopen($url, 'rb', false, $ctx); if (!$fp) { return false; } if ($getresponse) { $response = stream_get_contents($fp); return $response; } return true; } return true; function do_post_request($url, $data, $optional_headers = null,$getresponse = false) { $params = array( 'http' => array( 'method' => 'POST', 'content' => $data ) ); if ($optional_headers !== null) { $params['http']['header'] = $optional_headers; } $ctx = stream_context_create($params); $fp = @fopen($url, 'rb', false, $ctx); if (!$fp) { return false; } if ($getresponse) { $response = stream_get_contents($fp); return $response; } return true; } 

    Если вы контролируете цель, которую хотите вызывать асинхронно (например, собственный «longtask.php»), вы можете закрыть соединение с этого конца, и оба сценария будут выполняться параллельно. Он работает следующим образом:

    1. quick.php открывает longtask.php через cURL (здесь нет магии)
    2. longtask.php закрывает соединение и продолжает (магия!)
    3. cURL возвращается к quick.php, когда соединение закрыто.
    4. Обе задачи продолжаются параллельно

    Я пробовал это, и все работает отлично. Но 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, и несколько улучшился.

    Wez Furlong продемонстрировал, как это сделать:

    http://netevil.org/blog/2005/may/guru-multiplexing

    он предоставил как PHP4-, так и PHP5-совместимые реализации.

    Вы можете сделать обман, используя exec () для вызова того, что может выполнять HTTP-запросы, например wget , но вы должны направлять весь вывод из программы куда-нибудь, например файл или / dev / null, иначе процесс PHP будет ждать этого вывод.

    Если вы хотите полностью отделить процесс от потока apache, попробуйте что-то вроде (я не уверен в этом, но надеюсь, что вы поняли эту идею):

     exec('bash -c "wget -O (url goes here) > /dev/null 2>&1 &"'); 

    Это не приятный бизнес, и вы, вероятно, захотите что-то вроде задания cron, вызывающего скрипт heartbeat, который опросает реальную очередь событий базы данных для выполнения реальных асинхронных событий.

     /** * 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 &"); } 
    1. Поддельный аборт запроса с использованием CURL устанавливающий низкий CURLOPT_TIMEOUT_MS

    2. set 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'); return true; 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

    Если вы хотите, чтобы cURL был тайм-аут менее чем за одну секунду, вы можете использовать CURLOPT_TIMEOUT_MS, хотя в «Unix-подобных системах» есть ошибка / «функция», что приводит к немедленному отключению libcurl, если значение <1000 мс с ошибкой " cURL Ошибка (28): время ожидания достигнуто ». Объяснение этого поведения:

    […]

    Решение состоит в отключении сигналов с использованием CURLOPT_NOSIGNAL

    Ресурсы

    позвольте мне показать вам мой путь 🙂

    Требуется узел 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.. } ?> else { <? $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.log('<%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.log("i am too far gone");process.exit(); } function log() { var fd = fs.openSync(logdosya, 'a+'); fs.writeSync(fd, dosya + '-'+count+'\n'); fs.closeSync(fd); } - 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.log('<%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.log("i am too far gone");process.exit(); } function log() { var fd = fs.openSync(logdosya, 'a+'); fs.writeSync(fd, dosya + '-'+count+'\n'); fs.closeSync(fd); } -во 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.log('<%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.log("i am too far gone");process.exit(); } function log() { var fd = fs.openSync(logdosya, 'a+'); fs.writeSync(fd, dosya + '-'+count+'\n'); fs.closeSync(fd); } } else { 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.log('<%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.log("i am too far gone");process.exit(); } function log() { var fd = fs.openSync(logdosya, 'a+'); fs.writeSync(fd, dosya + '-'+count+'\n'); fs.closeSync(fd); } 

    Вы можете использовать неблокирующие сокеты и один из расширений pecl для PHP:

    Вы можете использовать библиотеку, которая дает вам уровень абстракции между вашим кодом и расширением pecl: https://github.com/reactphp/event-loop

    Вы также можете использовать асинхронный http-клиент на основе предыдущей библиотеки: https://github.com/reactphp/http-client

    См. Другие библиотеки ReactPHP: http://reactphp.org

    Будьте осторожны с асинхронной моделью. Я рекомендую посмотреть это видео на YouTube: http://www.youtube.com/watch?v=MWNcItWuKpI

     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"); 

    Вы можете использовать эту библиотеку: 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 } 

    Ниже вы можете увидеть консольный вывод вышеприведенного примера. Он отобразит простые живые часы, указывающие, сколько времени выполняется:


    анимация

    Расширение 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); 

    Расширение событий

    Расширение события очень подходит. Это библиотека библиотеки Libevent , предназначенная для ввода-вывода с событиями, в основном для сетей.

    Я написал пример HTTP-клиента, который позволяет планировать несколько HTTP-запросов и запускать их асинхронно.

    Это пример клиентского класса HTTP, основанный на расширении Event .

    Класс позволяет планировать несколько HTTP-запросов, а затем запускать их асинхронно.

    HTTP-client.php

     <?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, eg '/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(); return false; <?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, eg '/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(); } else { <?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, eg '/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(); } else { <?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, eg '/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(); 

    test.php

    Это пример скрипта на стороне сервера.

     <?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 низкого уровня в сочетании с контекстом ssl Event. Примеры:

    • SSL-эхо-сервер
    • SSL-клиент

    Хотя HTTP-API Libevent прост, он не так гибкий, как буферные события. Например, HTTP API в настоящее время не поддерживает настраиваемые методы HTTP. Но можно реализовать практически любой протокол с использованием низкоуровневого API.

    Расширение Ev

    Я также написал образец другого HTTP-клиента, использующего расширение Ev с сокетами в неблокирующем режиме . Код несколько более подробный, чем образец, основанный на событии, потому что Ev – это цикл событий общего назначения. Он не предоставляет сетевые функции, но его наблюдатель EvIo способен прослушивать файловый дескриптор, инкапсулированный в ресурс сокета, в частности.

    Это пример HTTP-клиента на основе расширения Ev .

    Расширение Ev реализует простой, но мощный цикл событий общего назначения. Он не обеспечивает сетевых наблюдателей, но его наблюдатель I / O может использоваться для асинхронной обработки сокетов .

    Следующий код показывает, как HTTP-запросы могут быть запланированы для параллельной обработки.

    HTTP-client.php

     <?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, eg google.co.uk * @param string $resource HTTP resource, eg /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 расширение сокетов может записывать предупреждения для значений errno EINPROGRESS , EAGAIN и EWOULDBLOCK . Можно отключить журналы с помощью

     error_reporting(E_ERROR); 

    Относительно «Остального» Кодекса

    Я просто хочу сделать что-то вроде file_get_contents() , но не дождался завершения запроса до выполнения остальной части моего кода.

    Код, который должен выполняться параллельно с сетевыми запросами, может выполняться, например, при обратном вызове таймера события или, например, для наблюдателя бездействия Ev. Вы можете легко понять это, просмотрев образцы, упомянутые выше. В противном случае я добавлю еще один пример 🙂

    Вот рабочий пример, просто запустите его и откройте 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)); 

    Вот моя собственная функция PHP, когда я делаю POST на определенный URL любой страницы …. Пример: *** использование моей функции …

      <?php parse_str("email=myemail@ehehehahaha.com&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 parse_str("email=myemail@ehehehahaha.com&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> 

    Ну, тайм-аут можно установить в миллисекундах, см. «CURLOPT_CONNECTTIMEOUT_MS» в http://www.php.net/manual/en/function.curl-setopt

    PHP is the Best Programming Language in the world.