Bre*_*ent 202 php asynchronous http
PHP中有没有办法进行异步HTTP调用?我不关心响应,我只想做类似的事情file_get_contents(),但是在执行其余代码之前不要等待请求完成.这对于在我的应用程序中引发排序的"事件"或触发长进程非常有用.
有任何想法吗?
Bre*_*ent 43
我之前接受的答案没有用.它仍在等待回应.这确实有效,取决于如何在PHP中创建异步GET请求?
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);
}
Run Code Online (Sandbox Code Playgroud)
Chr*_*vén 26
如果您控制要异步调用的目标(例如,您自己的"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();
Run Code Online (Sandbox Code Playgroud)
代码是从PHP手册的用户提供的注释中复制而来的,并有所改进.
Bru*_*dge 25
这需要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;
}
Run Code Online (Sandbox Code Playgroud)
Int*_*end 15
你可以通过使用exec()调用可以执行HTTP请求的东西来做欺骗wget,但是你必须将程序的所有输出都指向某个地方,比如文件或/ dev/null,否则PHP进程将等待输出.
如果你想完全将进程与apache线程分开,那就试试(我不确定这个,但我希望你能得到这个想法):
exec('bash -c "wget -O (url goes here) > /dev/null 2>&1 &"');
Run Code Online (Sandbox Code Playgroud)
这不是一个好的业务,你可能想要一个像cron作业调用心跳脚本的东西,该脚本轮询一个实际的数据库事件队列来做真正的异步事件.
Sim*_*ast 10
截至2018年,Guzzle已成为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]
Run Code Online (Sandbox Code Playgroud)
请参阅http://docs.guzzlephp.org/en/stable/quickstart.html#concurrent-requests
/**
* 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 &");
}
Run Code Online (Sandbox Code Playgroud)
使用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');
Run Code Online (Sandbox Code Playgroud)
后台进程
ignore_user_abort(true);
//do something...
Run Code Online (Sandbox Code Playgroud)
NB
如果你希望cURL在不到一秒的时间内超时,你可以使用CURLOPT_TIMEOUT_MS,尽管"类Unix系统"上存在一个错误/"特性",如果该值<1000毫秒,则会导致libcurl立即超时" cURL错误(28):达到超时".这种行为的解释是:
[...]
解决方案是使用CURLOPT_NOSIGNAL禁用信号
资源
您可以使用此库: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
}
Run Code Online (Sandbox Code Playgroud)
下面你可以看到上面例子的控制台输出.它将显示简单的实时时钟,指示请求运行的时间:

小智 5
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);
Run Code Online (Sandbox Code Playgroud)
您可以使用非阻塞套接字和 PHP 的 pecl 扩展之一:
您可以使用库,它为您的代码和 pecl 扩展之间提供了一个抽象层: https: //github.com/reactphp/event-loop
您还可以使用基于之前的库的异步http-client: https: //github.com/reactphp/http-client
查看 ReactPHP 的其他库: http: //reactphp.org
小心异步模型。我建议在 youtube 上观看此视频:http://www.youtube.com/watch ?v=MWNcItWuKpI
活动延期非常合适。它是Libevent库的一个端口,专为事件驱动 I/O 而设计,主要用于网络。
我编写了一个示例 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();
Run Code Online (Sandbox Code Playgroud)
这是服务器端的示例脚本。
<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;
echo 'User-Agent: ', $_SERVER['HTTP_USER_AGENT'] ?? '(none)', PHP_EOL;
Run Code Online (Sandbox Code Playgroud)
php http-client.php
Run Code Online (Sandbox Code Playgroud)
样本输出
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',
)
...
Run Code Online (Sandbox Code Playgroud)
(已修剪。)
请注意,该代码设计用于在CLI SAPI中进行长期处理。
对于自定义协议,请考虑使用低级 API,即buffer events、buffers。对于 SSL/TLS 通信,我建议将低级 API 与 Event 的ssl context结合使用。例子:
虽然Libevent的HTTP API很简单,但它不如缓冲事件那么灵活。例如,HTTP API 目前不支持自定义 HTTP 方法。但使用低级 API 实现几乎任何协议都是可能的。
我还编写了另一个使用Ev扩展和非阻塞模式套接字的 HTTP 客户端示例。该代码比基于 Event 的示例稍微详细一些,因为 Ev 是通用事件循环。它不提供特定于网络的功能,但其监视程序能够侦听封装到套接字资源中的文件描述符。EvIo
这是一个基于Ev扩展的 HTTP 客户端示例。
Ev 扩展实现了一个简单但功能强大的通用事件循环。它不提供特定于网络的观察程序,但其I/O 观察程序可用于套接字的异步处理。
以下代码显示了如何安排 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();
Run Code Online (Sandbox Code Playgroud)
假设http://my-host.local/test.php脚本正在打印以下转储$_GET:
<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;
Run Code Online (Sandbox Code Playgroud)
那么命令的输出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
>>>>
...
Run Code Online (Sandbox Code Playgroud)
(已修剪)
请注意,在 PHP 5 中,套接字扩展可能会记录EINPROGRESS、EAGAIN和EWOULDBLOCK errno值的警告。可以使用以下命令关闭日志
error_reporting(E_ERROR);
Run Code Online (Sandbox Code Playgroud)
我只想做类似的事情
file_get_contents(),但不等待请求完成后再执行其余的代码。
例如,应该与网络请求并行运行的代码可以在事件计时器或 Ev 的空闲观察程序的回调中执行。通过观察上面提到的示例,您可以轻松地弄清楚。否则,我将添加另一个示例:)
| 归档时间: |
|
| 查看次数: |
158352 次 |
| 最近记录: |