Cách tạo các yêu cầu HTTP không đồng bộ trong PHP


209

Có cách nào trong PHP để thực hiện các cuộc gọi HTTP không đồng bộ không? Tôi không quan tâm đến phản hồi, tôi chỉ muốn làm một cái gì đó như thế file_get_contents(), nhưng không đợi yêu cầu kết thúc trước khi thực hiện phần còn lại của mã. Điều này sẽ rất hữu ích để tắt "sự kiện" của một loại trong ứng dụng của tôi hoặc kích hoạt các quy trình dài.

Có ý kiến ​​gì không?


9
một hàm - 'curl_multi', hãy tìm tài liệu php cho nó. Nên giải quyết vấn đề của bạn
James Butler

22
Tiêu đề của bài viết này là sai lệch. Tôi đã tìm kiếm các cuộc gọi thực sự không đồng bộ tương tự như các yêu cầu trong Node.js hoặc yêu cầu AJAX. Câu trả lời được chấp nhận không đồng bộ (nó chặn và không cung cấp cuộc gọi lại), chỉ là một yêu cầu đồng bộ nhanh hơn. Xem xét thay đổi câu hỏi hoặc câu trả lời được chấp nhận.
Johntron

Chơi với xử lý kết nối thông qua các tiêu đề và bộ đệm không phải là chống đạn. Tôi vừa đăng một câu trả lời mới độc lập từ hệ điều hành, trình duyệt hoặc bản án PHP
RafaSashi

1
Không đồng bộ không có nghĩa là bạn không quan tâm đến phản hồi. Nó chỉ có nghĩa là cuộc gọi không chặn thực thi luồng chính. Không đồng bộ vẫn yêu cầu phản hồi, nhưng phản hồi có thể được xử lý trong một luồng thực thi khác hoặc sau đó trong một vòng lặp sự kiện. Câu hỏi này đang yêu cầu một yêu cầu quên và có thể đồng bộ hoặc không đồng bộ tùy thuộc vào ngữ nghĩa gửi tin nhắn, cho dù bạn quan tâm đến thứ tự tin nhắn hay xác nhận gửi.
CMCDragonkai

Tôi nghĩ bạn nên thực hiện yêu cầu HTTP này ở chế độ không chặn (w / c là những gì bạn thực sự muốn) .. Bởi vì khi bạn gọi một tài nguyên, về cơ bản bạn muốn biết liệu bạn có đến được máy chủ hay không (hoặc bất kỳ lý do gì, bạn chỉ cần phản hồi). Câu trả lời tốt nhất thực sự là fsockopen và thiết lập đọc hoặc ghi luồng thành chế độ không chặn. Nó giống như gọi và quên.
KiX Ortillan

Câu trả lời:


42

Câu trả lời tôi đã chấp nhận trước đây không hoạt động. Nó vẫn chờ hồi âm. Mặc dù vậy, điều này không hoạt động, được lấy từ Làm cách nào để tôi thực hiện một yêu cầu GET không đồng bộ trong 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);
}

67
Đây không phải là không đồng bộ! Đặc biệt, nếu máy chủ ở phía bên kia không hoạt động, đoạn mã này sẽ bị treo trong 30 giây (tham số thứ 5 trong fsockopen). Ngoài ra, fwrite sẽ dành thời gian ngọt ngào để thực thi (bạn có thể giới hạn với stream_set_timeout ($ fp, $ my_timeout). Cách tốt nhất bạn có thể làm là đặt thời gian chờ thấp trên fsockopen thành 0,1 (100ms) và $ my_timeout thành 100ms Tuy nhiên, bạn có nguy cơ hết thời gian yêu cầu.
Chris Cinelli

3
Tôi đảm bảo với bạn rằng nó không đồng bộ và không mất 30 giây. Đó là thời gian chờ tối đa. Điều khả thi là các cài đặt của bạn khác nhau gây ra hiệu ứng đó, nhưng điều này rất tốt cho tôi.
Brent

11
@UltimateBrent Không có gì trong mã cho thấy nó không đồng bộ. Nó không chờ phản hồi, nhưng điều đó không đồng bộ. Nếu máy chủ từ xa mở kết nối và sau đó bị treo, mã này sẽ đợi trong 30 giây cho đến khi bạn nhấn thời gian chờ đó.
chmac

17
lý do khiến nó dường như hoạt động "không đồng bộ" vì bạn không đọc từ ổ cắm trước khi đóng nó để nó không bị treo ngay cả khi máy chủ không phát ra phản hồi kịp thời. Tuy nhiên điều này là hoàn toàn không đồng bộ. Nếu bộ đệm ghi đã đầy (rất ít khả năng) tập lệnh của bạn chắc chắn sẽ bị treo ở đó. Bạn nên xem xét việc thay đổi tiêu đề của mình thành một cái gì đó như "yêu cầu một trang web mà không cần chờ phản hồi".
howanghk

3
Đây không phải là không đồng bộ cũng không phải là sử dụng curl, làm thế nào bạn dám gọi nó curl_post_asyncvà thậm chí nâng cấp ...
Daniel W.

27

Nếu bạn kiểm soát mục tiêu mà bạn muốn gọi không đồng bộ (ví dụ: "longtask.php" của riêng bạn), bạn có thể đóng kết nối từ đầu đó và cả hai tập lệnh sẽ chạy song song. Nó hoạt động như thế này:

  1. quick.php mở longtask.php thông qua cURL (không có phép thuật ở đây)
  2. longtask.php đóng kết nối và tiếp tục (ma thuật!)
  3. cURL trở về quick.php khi kết nối được đóng lại
  4. Cả hai nhiệm vụ tiếp tục song song

Tôi đã thử điều này, và nó hoạt động tốt. Nhưng quick.php sẽ không biết gì về việc longtask.php đang hoạt động như thế nào, trừ khi bạn tạo ra một số phương tiện liên lạc giữa các quy trình.

Hãy thử mã này trong longtask.php, trước khi bạn làm bất cứ điều gì khác. Nó sẽ đóng kết nối, nhưng vẫn tiếp tục chạy (và triệt tiêu mọi đầu ra):

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

Mã được sao chép từ ghi chú đóng góp của người dùng hướng dẫn PHP và phần nào được cải thiện.


3
Điều này sẽ làm việc. Nhưng nếu bạn đang sử dụng một khung công tác MVC thì có thể khó thực hiện vì cách mà các khung này chặn và viết lại các cuộc gọi. Ví dụ: nó không hoạt động trong Bộ điều khiển trong CakePHP
Chris Cinelli

Một nghi ngờ về mã này, quá trình bạn cần làm trong longtask phải đi sau dòng này? Cảm ơn.
morgar

Nó không hoạt động hoàn hảo. Cố gắng thêm while(true);sau mã của bạn. Trang sẽ bị treo, điều này có nghĩa là nó vẫn đang chạy ở nền trước.
زياد

17

Bạn có thể thực hiện thủ thuật bằng cách sử dụng exec () để gọi một cái gì đó có thể thực hiện các yêu cầu HTTP, như wget, nhưng bạn phải hướng tất cả đầu ra từ chương trình đến một nơi nào đó, như một tệp hoặc / dev / null, nếu không, quá trình PHP sẽ đợi đầu ra đó .

Nếu bạn muốn tách hoàn toàn quy trình khỏi chuỗi apache, hãy thử một cái gì đó như (tôi không chắc về điều này, nhưng tôi hy vọng bạn có ý tưởng):

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

Đây không phải là một công việc tốt, và có lẽ bạn sẽ muốn một cái gì đó giống như một công việc định kỳ gọi một kịch bản nhịp tim để thăm dò hàng đợi sự kiện cơ sở dữ liệu thực tế để thực hiện các sự kiện không đồng bộ thực sự.


3
Tương tự, tôi cũng đã thực hiện các thao tác sau: exec ("curl $ url> / dev / null &");
Matt Huggins

2
Câu hỏi: có lợi ích gì khi gọi 'bash -c "wget"' thay vì chỉ 'wget'?
Matt Huggins

2
Trong thử nghiệm của tôi, sử dụng exec("curl $url > /dev/null 2>&1 &");là một trong những giải pháp nhanh nhất ở đây. Nó nhanh hơn rất nhiều (1,9 giây cho 100 lần lặp) so với post_without_wait()hàm (14,8 giây) trong câu trả lời "được chấp nhận" ở trên. VÀ đó là một lớp lót ...
rinogo

Sử dụng đường dẫn đầy đủ (ví dụ / usr / bin / curl) để làm cho nó nhanh hơn nữa
Putnik

Điều này có đợi cho đến khi kịch bản kết thúc?
cikatomo

11

Kể từ năm 2018, Guheads đã trở thành thư viện chuẩn defacto cho các yêu cầu HTTP, được sử dụng trong một số khung hiện đại. Nó được viết bằng PHP thuần túy và không yêu cầu cài đặt bất kỳ tiện ích mở rộng tùy chỉnh nào.

Nó có thể thực hiện các cuộc gọi HTTP không đồng bộ rất độc đáo và thậm chí gộp chúng như khi bạn cần thực hiện 100 cuộc gọi HTTP, nhưng không muốn chạy nhiều hơn 5 lần một lần.

Ví dụ yêu cầu đồng thời

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]

Xem http://docs.guheadsphp.org/en/urdy/quickstart.html#concản-requests


3
Tuy nhiên, câu trả lời này không đồng bộ. rõ ràng là không có ý định làm điều đó
bực bội vào

2
Guheads yêu cầu bạn cài đặt curl. Mặt khác, nó không song song và nó không cung cấp cho bạn bất kỳ cảnh báo nào rằng nó không song song.
Velizar Hristov

Cảm ơn liên kết @daslicious - vâng, có vẻ như nó không hoàn toàn không đồng bộ (như khi bạn muốn gửi yêu cầu nhưng không quan tâm đến kết quả) nhưng một vài bài đăng trong chủ đề đó người dùng đã đưa ra cách giải quyết thiết lập giá trị hết thời gian yêu cầu rất thấp mà vẫn cho phép thời gian kết nối, nhưng không chờ kết quả.
Simon East

9
/**
 * 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 &");
}

Điều này không đồng bộ vì exec đang chặn cho đến khi bạn thoát hoặc rẽ nhánh tiến trình bạn muốn chạy.
Daniel W.

6
Bạn đã nhận thấy &cuối cùng?
philfreo

Vì vậy, điều này sẽ chặn kịch bản sau đó hay không, tôi bối rối?
pleshy

1
@pleshy nó ​​sẽ không. ampersand (&) có nghĩa là chạy tập lệnh ở chế độ nền
daisura99

8

Bạn có thể sử dụng thư viện này: https://github.com/stil/curl-easy

Sau đó, nó khá đơn giản:

<?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
}

Dưới đây bạn có thể thấy đầu ra giao diện điều khiển của ví dụ trên. Nó sẽ hiển thị đồng hồ trực tiếp đơn giản cho biết thời gian yêu cầu đang chạy:


hoạt hình


Điều này sẽ là câu trả lời được chấp nhận cho câu hỏi vì, ngay cả khi nó không phải async đúng, nó là tốt hơn so với một chấp nhận và tất cả "async" câu trả lời với tật ham ăn (Ở đây bạn có thể thực hiện các hoạt động trong khi yêu cầu được thực hiện)
0ddlyoko

7
  1. Giả mạo phá thai yêu cầu bằng cách CURLđặt mức thấpCURLOPT_TIMEOUT_MS

  2. thiết lập ignore_user_abort(true)để tiếp tục xử lý sau khi kết nối đóng.

Với phương pháp này, không cần thực hiện xử lý kết nối qua các tiêu đề và bộ đệm quá phụ thuộc vào phiên bản HĐH, Trình duyệt và PHP

Quy trình tổng thể

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

Quá trình nền

ignore_user_abort(true);

//do something...

Lưu ý

Nếu bạn muốn cURL hết thời gian chờ chưa đến một giây, bạn có thể sử dụng CURLOPT_TIMEOUT_MS, mặc dù có một lỗi / "tính năng" trên "các hệ thống giống Unix" khiến libcurl hết thời gian ngay lập tức nếu giá trị <1000 ms bị lỗi " Lỗi cURL (28): Đã hết thời gian chờ ". Giải thích cho hành vi này là:

[...]

Giải pháp là vô hiệu hóa các tín hiệu bằng cách sử dụng CURLOPT_NOSIGNAL

Tài nguyên


Làm thế nào để bạn xử lý thời gian kết nối ra (giải quyết, dns)? Khi tôi đặt timeout_ms thành 1, tôi luôn kết thúc bằng "giải quyết hết thời gian sau 4 ms" hoặc đại loại như thế
Martin Wickman

Tôi không biết nhưng âm thanh 4 ms đã khá nhanh đối với tôi ... Tôi không nghĩ bạn có thể giải quyết nhanh hơn bằng cách thay đổi bất kỳ cài đặt cuộn tròn nào. Hãy thử tối ưu hóa yêu cầu được nhắm mục tiêu có lẽ ...
RafaSashi

Ok, nhưng timeout_ms = 1 đặt thời gian chờ cho toàn bộ yêu cầu. Vì vậy, nếu giải quyết của bạn mất hơn 1ms, thì curl sẽ hết thời gian và dừng yêu cầu. Tôi không thấy làm thế nào điều này có thể hoạt động cả (giả sử giải quyết mất> 1 ms).
Martin Wickman

4

hãy để tôi chỉ cho bạn cách của tôi :)

cần cài đặt nodejs trên máy chủ

(máy chủ của tôi gửi 1000 https yêu cầu nhận chỉ mất 2 giây)

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

1
Xin lưu ý rằng nhiều nhà cung cấp dịch vụ lưu trữ không cho phép sử dụng các chức năng PHP nhất định (như popen / exec ). Xem vô hiệu hóa chỉ thị PHP.
Eugen Mihailescu

4

Phần mở rộng swoole. https://github.com/matyhtf/swoole Khung mạng không đồng bộ & đồng thời cho 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);

4

Bạn có thể sử dụng các ổ cắm không chặn và một trong các tiện ích mở rộng pecl cho PHP:

Bạn có thể sử dụng thư viện cung cấp cho bạn một lớp trừu tượng giữa mã của bạn và tiện ích mở rộng pecl: https://github.com/reactphp/event-loop

Bạn cũng có thể sử dụng async http-client, dựa trên thư viện trước đó: https://github.com/reactphp/http-client

Xem các thư viện khác của ReactPHP: http://reactphp.org

Hãy cẩn thận với một mô hình không đồng bộ. Tôi khuyên bạn nên xem video này trên youtube: http://www.youtube.com/watch?v=MWNcItWuKpI


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

2

Gia hạn sự kiện

Sự kiện mở rộng là rất thích hợp. Đây là một cổng của thư viện Libevent được thiết kế cho I / O theo sự kiện, chủ yếu để kết nối mạng.

Tôi đã viết một máy khách HTTP mẫu cho phép lên lịch một số yêu cầu HTTP và chạy chúng không đồng bộ.

Đây là lớp máy khách HTTP mẫu dựa trên tiện ích mở rộng Sự kiện .

Lớp cho phép lên lịch một số yêu cầu HTTP, sau đó chạy chúng không đồng bộ.

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, 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();

test.php

Đây là một kịch bản mẫu ở phía máy chủ.

<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;
echo 'User-Agent: ', $_SERVER['HTTP_USER_AGENT'] ?? '(none)', PHP_EOL;

Sử dụng

php http-client.php

Đầu ra mẫu

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',
)
...

(Cắt tỉa.)

Lưu ý, mã được thiết kế để xử lý lâu dài trong CLI SAPI .


Đối với các giao thức tùy chỉnh, hãy xem xét sử dụng API cấp thấp, tức là các sự kiện bộ đệm , bộ đệm . Đối với truyền thông SSL / TLS, tôi sẽ đề xuất API cấp thấp kết hợp với bối cảnh ssl của Sự kiện . Ví dụ:


Mặc dù API HTTP của Libevent rất đơn giản, nhưng nó không linh hoạt như các sự kiện đệm. Ví dụ: API HTTP hiện không hỗ trợ các phương thức HTTP tùy chỉnh. Nhưng có thể thực hiện hầu như bất kỳ giao thức nào bằng API cấp thấp.

Mở rộng Ev

Tôi cũng đã viết một mẫu của một máy khách HTTP khác bằng cách sử dụng tiện ích mở rộng Evổ cắmchế độ không chặn . Mã dài hơn một chút so với mẫu dựa trên Sự kiện, vì Ev là một vòng lặp sự kiện có mục đích chung. Nó không cung cấp các chức năng cụ thể cho mạng, nhưng trình EvIotheo dõi của nó có khả năng nghe một bộ mô tả tệp được gói gọn trong tài nguyên ổ cắm, đặc biệt.

Đây là một máy khách HTTP mẫu dựa trên phần mở rộng Ev .

Phần mở rộng Ev thực hiện một vòng lặp sự kiện mục đích chung đơn giản nhưng mạnh mẽ. Nó không cung cấp các trình theo dõi dành riêng cho mạng, nhưng trình theo dõi I / O của nó có thể được sử dụng để xử lý các ổ cắm không đồng bộ .

Đoạn mã sau cho thấy cách yêu cầu HTTP có thể được lên lịch để xử lý song song.

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, 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();

Kiểm tra

Giả sử http://my-host.local/test.phptập lệnh đang in kết xuất của $_GET:

<?php
echo 'GET: ', var_export($_GET, true), PHP_EOL;

Sau đó, đầu ra của php http-client.phplệnh sẽ tương tự như sau:

<<<<
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
>>>>
...

(cắt tỉa)

Lưu ý, trong PHP 5 ổ cắm mở rộng có thể đăng nhập cảnh báo cho EINPROGRESS, EAGAINEWOULDBLOCK errnogiá trị. Có thể tắt nhật ký với

error_reporting(E_ERROR);

Liên quan đến "phần còn lại" của Bộ luật

Tôi chỉ muốn làm một cái gì đó như thế file_get_contents(), nhưng không đợi yêu cầu kết thúc trước khi thực hiện phần còn lại của mã của tôi.

Mã được cho là chạy song song với các yêu cầu mạng có thể được thực thi trong một cuộc gọi lại của bộ đếm thời gian Sự kiện , hoặc trình theo dõi nhàn rỗi của Ev , chẳng hạn. Bạn có thể dễ dàng tìm ra nó bằng cách xem các mẫu được đề cập ở trên. Nếu không, tôi sẽ thêm một ví dụ khác :)


1

Dưới đây là một ví dụ hoạt động, chỉ cần chạy nó và mở Storage.txt sau đó, để kiểm tra kết quả kỳ diệu

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

1

Đây là chức năng PHP của riêng tôi khi tôi POST cho một URL cụ thể của bất kỳ trang nào .... Ví dụ: *** sử dụng Chức năng của tôi ...

    <?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>

1

ReactPHP async http client
https://github.com/shuchkin/react-http-client

Cài đặt qua Trình soạn thảo

$ composer require shuchkin/react-http-client

Async 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();

Chạy php ở chế độ CLI

$ php get.php

0

Tôi thấy gói này khá hữu ích và rất đơn giản: https://github.com/amphp/abul-fifts

<?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);
}));

Nó sẽ tải cả 3 url song song. Bạn cũng có thể sử dụng các phương thức thể hiện của lớp trong bao đóng.

Ví dụ: tôi sử dụng tiện ích mở rộng Laravel dựa trên gói này https://github.com/spatie/laravel-collection-macros#abulmap

Đây là mã của tôi:

    /**
     * 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);
    }

Nó tải tất cả dữ liệu cần thiết trong 10 luồng song song và thay vì 50 giây mà không đồng bộ hóa, nó đã hoàn thành chỉ sau 8 giây.


0

Symfony HttpClient không đồng bộ https://symfony.com/doc/civerse/components/http_client.html .

Ví dụ bạn có thể

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.