asynchronousPHP调用?
有没有办法在PHP中进行asynchronoushttp调用? 我不在乎的回应,我只是想做一些像file_get_contents()
,但不等待请求完成之前执行我的代码的其余部分。 这对于在我的应用程序中设置sorting的“事件”或者触发长时间的stream程是非常有用的。
有任何想法吗?
我以前接受的答案是行不通的。 它还在等待回应。 这虽然工作,取自我如何在PHP中进行asynchronousGET请求?
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; }
如果你控制你想asynchronous调用的目标(例如你自己的“longtask.php”),你可以closures连接,两个脚本将并行运行。 它是这样工作的:
- quick.php通过cURL打开longtask.php(这里没有魔法)
- longtask.phpclosures连接并继续(魔术!)
- 当连接closures时,cURL返回到quick.php
- 两个任务都是并行的
我已经试过这个,它工作得很好。 但是quick.php不会知道关于longtask.php在做什么,除非你在这些进程之间创build了一些通信手段。
在做任何事情之前,在longtask.php中试试这个代码。 它将closures连接,但仍然继续运行(并抑制任何输出):
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手册的用户贡献的笔记复制,并有所改善。
你可以用exec()来调用一些可以执行HTTP请求的东西,比如wget
,但是你必须把程序的所有输出放在某个地方,比如文件或者/ dev / null,否则PHP进程将会等待输出。
如果你想完全从apache线程分离进程,请尝试类似(我不确定这一点,但我希望你能明白):
exec('bash -c "wget -O (url goes here) > /dev/null 2>&1 &"');
这不是一个好的业务,你可能需要像cron作业调用心跳脚本轮询一个实际的数据库事件队列做真正的asynchronous事件。
/** * 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 &"); }
-
使用
CURL
请求中止设置低CURLOPT_TIMEOUT_MS
-
设置
ignore_user_abort(true)
在连接closures后继续处理。
使用这种方法,不需要通过头文件和缓冲区来实现连接处理,这取决于操作系统,浏览器和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
如果你希望cURL在不到一秒的时间内超时,你可以使用CURLOPT_TIMEOUT_MS,虽然在“类Unix系统”上有一个bug /“function”,导致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.. } ?>
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); }
您可以使用非阻塞套接字和PHP的pecl扩展之一:
你可以使用库,它提供了你的代码和pecl扩展之间的抽象层: https : //github.com/reactphp/event-loop
您也可以使用asynchronoushttp客户端,基于以前的库: https : //github.com/reactphp/http-client
查看其他ReactPHP库: http: //reactphp.org
小心asynchronous模型。 我build议您在YouTube上看这个video: 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 }
下面你可以看到上面例子的控制台输出。 它将显示简单的实时时钟,指示请求正在运行多less时间:
斯沃尔扩展。 https://github.com/matyhtf/swoole PHP的asynchronous和并发networking框架。
$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);
事件扩展
事件扩展是非常合适的。 它是为事件驱动I / O而devise的Libevent库的一个端口,主要用于networking连接。
我写了一个示例HTTP客户端,它允许安排一些HTTP请求并asynchronous运行它们。
这是基于Event扩展的示例HTTP客户端类。
该类允许安排大量的HTTP请求,然后asynchronous运行。
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();
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中进行长期处理而devise的。
对于自定义协议,请考虑使用低级API,即缓冲区事件 , 缓冲区 。 对于SSL / TLS通信,我会推荐将低级API与Event的ssl上下文一起使用。 例子:
- SSL回应服务器
- SSL客户端
虽然Libevent的HTTP API很简单,但它不像缓冲区事件那么灵活。 例如,HTTP API目前不支持自定义的HTTP方法。 但是使用低级API可以实现几乎所有的协议。
Ev扩展
我还用非阻塞模式下的 套接字使用Ev扩展编写了另一个HTTP客户端的样本。 代码稍微比基于事件的示例冗长,因为Ev是通用事件循环。 它不提供特定于networking的function,但是它的EvIo
监视器能够监听封装到套接字资源中的文件描述符。
这是一个基于Ev扩展的示例HTTP客户端。
Ev扩展实现了一个简单而强大的通用事件循环。 它不提供networking特定的观察者,但是它的I / O监视器可以用于套接字的asynchronous处理。
以下代码显示了如何为并行处理安排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();
testing
假设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中, 套接字扩展可能会loggingEINPROGRESS
, EAGAIN
和EWOULDBLOCK
errno
值的警告。 可以使用closures日志
error_reporting(E_ERROR);
关于“守则”的其余部分
我只想做一些像
file_get_contents()
这样的事情,但不要等待请求完成,然后再执行我的代码。
应该与networking请求并行运行的代码可以在事件计时器或Ev的空闲监视器的callback中执行。 你可以通过观察上面提到的样品来轻松搞清楚。 否则,我会添加另一个例子:)
这里是一个工作的例子,运行它然后打开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….样品:***使用我的function…
<?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>
那么超时时间可以设置为几毫秒,参见http://www.php.net/manual/en/function.curl-setopt中的; “CURLOPT_CONNECTTIMEOUT_MS”