使用 socket_create + socket_set_nonblock + socket_connect + socket_select 實現帶超時的非阻塞連接,避免 fsockopen 無法精確控制連接超時的問題。
<?php
class Health {
public static $status;
public function check($ip, $port) {
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$sock) return false;
socket_set_nonblock($sock);
@socket_connect($sock, $ip, $port); // 非阻塞下立即返回
socket_set_block($sock); // 恢復阻塞模式供 select 使用
// select 監聽寫就緒(連接建立)或異常,超時 5 秒
$r = [$sock]; $w = [$sock]; $e = [$sock];
self::$status = socket_select($r, $w, $e, 5);
socket_close($sock);
return self::$status;
}
public function status() {
switch (self::$status) {
case 1: echo "Open\n"; break; // 可寫 → 連接成功
case 0: echo "Timeout\n"; break; // 超時
default: echo "Closed/Error\n"; break; // 異常/拒絕
}
}
}
// 使用
$health = new Health();
$health->check('192.168.2.10', 80);
$health->status();
| 返回值 | 含義 | socket_select 就緒數組 |
|---|---|---|
| 1 | 端口開放(連接成功) | $w 包含 socket |
| 0 | 超時(5 秒內無響應) | 三個數組均為空 |
| -1 / false | 錯誤(創建失敗、權限等) | — |
function checkPorts(array $targets, $timeout=3) {
$sockets = [];
foreach ($targets as $i => [$ip, $port]) {
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_nonblock($sock);
@socket_connect($sock, $ip, $port);
$sockets[$i] = ['sock'=>$sock, 'ip'=>$ip, 'port'=>$port];
}
$start = microtime(true);
$results = [];
while ($sockets && microtime(true) - $start < $timeout) {
$r = []; $w = []; $e = [];
foreach ($sockets as $i => $s) {
$w[] = $s['sock']; $e[] = $s['sock'];
}
$n = @socket_select($r, $w, $e, 0, 500000); // 0.5 秒輪詢
if ($n > 0) {
foreach ($w as $sock) {
foreach ($sockets as $i => $s) {
if ($s['sock'] === $sock) {
$results[$i] = ['ip'=>$s['ip'], 'port'=>$s['port'], 'status'=>'open'];
socket_close($sock);
unset($sockets[$i]);
break;
}
}
}
foreach ($e as $sock) {
foreach ($sockets as $i => $s) {
if ($s['sock'] === $sock && isset($sockets[$i])) {
$results[$i] = ['ip'=>$s['ip'], 'port'=>$s['port'], 'status'=>'error'];
socket_close($sock);
unset($sockets[$i]);
break;
}
}
}
}
}
// 剩餘為 timeout
foreach ($sockets as $i => $s) {
$results[$i] = ['ip'=>$s['ip'], 'port'=>$s['port'], 'status'=>'timeout'];
socket_close($s['sock']);
}
return $results;
}
sockets 擴展(PHP 編譯時 --enable-sockets)socket_select 精度約 1ms,Linux 可達微秒