首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

PHP 非同步併發請求實戰:stream_socket_client + stream_select 實現高效多目標檢查

     編輯:PHP 相關知識

PHP 本身無執行緒,但可透過非阻塞 I/O 多路複用實現「類多執行緒」的併發效果。本文以檢查多台主機 HTTP 服務為例,對比阻塞/非阻塞寫法,詳細講解 stream_socket_client + stream_select 實作。

阻塞版本(串行、慢)

$hosts = ['host1.example.com', 'host2.example.com', 'host3.example.com'];
$timeout = 15;
$status = [];

foreach ($hosts as $host) {
    $errno = $errstr = '';
    $fp = fsockopen($host, 80, $errno, $errstr, $timeout);
    if (!$fp) { $status[$host] = "Failed: $errstr"; continue; }
    fwrite($fp, "HEAD / HTTP/1.0\r\nHost: $host\r\n\r\n");
    $data = '';
    while (!feof($fp)) { $data .= fread($fp, 8192); }
    fclose($fp);
    $status[$host] = $data ?: 'Connected (no response)';
}
// 總耗時 ≈ N * timeout

非阻塞併發版本(並行、快)

<?php
$hosts = ['host1.example.com', 'host2.example.com', 'host3.example.com'];
$timeout = 15;
$status = [];
$sockets = [];

// 1. 發起所有非阻塞連線
foreach ($hosts as $id => $host) {
    $fp = stream_socket_client(
        "tcp://{$host}:80",
        $errno, $errstr, $timeout,
        STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT
    );
    if ($fp) {
        $sockets[$id] = $fp;
        $status[$id] = 'connecting';
    } else {
        $status[$id] = "failed: $errstr";
    }
}

// 2. 事件迴圈
while ($sockets) {
    $read = $write = $sockets;
    $n = stream_select($read, $write, $except = null, $timeout);

    if ($n === false) { break; } // error
    if ($n === 0) { // timeout
        foreach ($sockets as $id => $fp) {
            $status[$id] = 'timeout';
            fclose($fp);
        }
        break;
    }

    // 可寫:連線成功,發送請求
    foreach ($write as $fp) {
        $id = array_search($fp, $sockets, true);
        fwrite($fp, "HEAD / HTTP/1.0\r\nHost: {$hosts[$id]}\r\n\r\n");
        $status[$id] = 'waiting_response';
    }

    // 可讀:收到回應或連線失敗
    foreach ($read as $fp) {
        $id = array_search($fp, $sockets, true);
        $chunk = fread($fp, 8192);
        if ($chunk === '' || $chunk === false) {
            // EOF 或錯誤
            if ($status[$id] === 'connecting') $status[$id] = 'connect_failed';
            fclose($fp);
            unset($sockets[$id]);
        } else {
            $status[$id] .= $chunk;
        }
    }
}

// 3. 輸出結果
foreach ($hosts as $id => $host) {
    echo "Host: $host\nStatus: " . ($status[$id] ?? 'unknown') . "\n\n";
}
?>

關鍵機制解析

  • STREAM_CLIENT_ASYNC_CONNECT:stream_socket_client 立即回傳資源,不等待 TCP 三向交握完成。
  • stream_select:封裝系統 select(2)/poll(2)/epoll(7),單執行緒監聽多個 fd 的讀/寫/例外事件。
  • 狀態機:每個 socket 經歷 connecting → waiting_response → reading → done,根據事件推進。

生產級替代方案

  • Guzzle + Promise:$client->sendAsync() 返回 Promise,支援並發、重試、中介軟體。
  • ReactPHP / Amp:事件驅動非阻塞框架,提供完整的 HTTP Client、DNS、Timer。
  • Swoole / OpenSwoole:協程級非阻塞,Co::sleep、CoHttp\Client 寫法同步、效能極高。
  • curl_multi_*:原生 cURL 多工,適合不想引入框架的專案。

總結

掌握 stream_select 核心模型有助於理解所有非阻塞框架的底層原理。實務上優先選擇成熟庫(Guzzle、Swoole),僅在極簡環境或教學場景手寫事件迴圈。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved