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

PHP 實現非阻塞並發請求:fsockopen 異步方案與 stream_socket_client 實戰

     編輯:PHP 綜合知識

問題背景

PHP 原生不支持多線程,但在高併發場景下(如同時檢測多台服務器狀態),逐個發起阻塞式 fsockopen() 連接會導致總耗時等於所有單次連接耗時之和,嚴重影響性能。本文介紹如何利用非阻塞 I/O 實現「偽多線程」並發請求。

傳統阻塞寫法的缺陷

$hosts = ["host1.sample.com", "host2.sample.com", "host3.sample.com"];
$timeout = 15;
$status = [];
foreach ($hosts as $host) {
    $s = fsockopen($host, 80, $errno, $errstr, $timeout);
    // ... 發送請求、讀取響應 ...
}

此代碼需等待每個連接建立或超時才能進行下一個,對於大量主機極其低效。

PHP 5+ 方案:stream_socket_client + stream_select

PHP 5 引入 stream_socket_client() 支持 STREAM_CLIENT_ASYNC_CONNECT 標誌,配合 stream_select() 實現事件驅動的非阻塞並發:

$hosts = ["host1.sample.com", "host2.sample.com", "host3.sample.com"];
$timeout = 15;
$status = [];
$sockets = [];

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

// 2. 事件循環:等待可讀/可寫事件
while (count($sockets)) {
    $read = $write = $sockets;
    $n = stream_select($read, $write, $e = null, $timeout);
    if ($n > 0) {
        // 處理可讀 socket(連接成功或收到數據)
        foreach ($read as $r) {
            $id = array_search($r, $sockets);
            $data = fread($r, 8192);
            if (strlen($data) === 0) {
                if ($status[$id] === "in progress") $status[$id] = "failed to connect";
                fclose($r);
                unset($sockets[$id]);
            } else {
                $status[$id] .= $data;
            }
        }
        // 處理可寫 socket(連接已建立,發送請求)
        foreach ($write as $w) {
            $id = array_search($w, $sockets);
            fwrite($w, "HEAD / HTTP/1.0\r\nHost: " . $hosts[$id] . "\r\n\r\n");
            $status[$id] = "waiting for response";
        }
    } else {
        // 超時處理
        foreach ($sockets as $id => $s) {
            $status[$id] = "timed out " . $status[$id];
        }
        break;
    }
}

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

核心原理: stream_select() 監聽多個流的讀/寫就緒事件,類似系統調用 select(2),實現單進程多路複用。

PHP 4 兼容方案:sockets 擴展

若環境不支持 streams,可使用 socket_create()、socket_set_nonblock()、socket_connect() 手動構建非阻塞 socket,並用 socket_select() 替換 stream_select(),邏輯完全一致。

總結與建議

  • 非阻塞 I/O 避免了進程/線程切換開銷,對於 I/O 密集型並發任務性能優於 fork/thread。
  • 推薦使用 PHP 5.3+ 的 stream_socket_client(),代碼更簡潔且支持所有流封裝協議(HTTP、SSL、UNIX domain socket 等)。
  • 實際項目中可考慮成熟庫如 ReactPHP、Amp 或 Swoole 進一步簡化異步編程。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved