當前位置: 首頁> 最新文章列表> 為什麼socket_accept() 一直阻塞?怎麼用socket_select() 來避免阻塞並解決這個問題?

為什麼socket_accept() 一直阻塞?怎麼用socket_select() 來避免阻塞並解決這個問題?

M66 2025-06-12

在使用PHP 進行Socket 編程時,很多開發者初次接觸socket_accept()時都會遇到一個問題:。這在某些需要高並發或者多個連接同時處理的場景中會成為瓶頸。

那麼,為什麼會出現這種情況?我們又該如何使用socket_select()來避免阻塞並優雅地解決這個問題呢?

一、為什麼socket_accept()會阻塞?

socket_accept()是用來接受一個來自監聽套接字的連接請求的函數。其原型如下:

 resource socket_accept(resource $socket);

這個函數的本質是:當沒有客戶端連接時,它會阻塞程序執行,直到有連接到來。這是典型的阻塞式I/O 行為。

這種行為對於單線程服務器來說,是不可取的。如果服務器因為等待一個連接而阻塞,其他任務將完全無法執行。

二、如何使用socket_select()來避免阻塞?

為了解決這個問題,PHP 提供了socket_select()函數,可以在多個套接字上進行“監聽”,判斷哪些套接字已經準備好進行讀/寫操作,從而避免阻塞等待。

socket_select()的基本用法

int socket_select(
    array &$read,
    array &$write,
    array &$except,
    int $tv_sec,
    int $tv_usec = 0
);

你只需要將要監聽的socket 放入$read數組,然後調用socket_select() 。當該socket 可讀時(即有客戶端連接請求時), socket_select()就會返回,這樣你再調用socket_accept()就不會阻塞了。

示例代碼

下面是一個使用socket_select()的服務器端完整示例:

 <?php
set_time_limit(0);

// 創建 socket
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($server, '0.0.0.0', 8080);
socket_listen($server);

// 設置非阻塞模式(可選,但推薦)
socket_set_nonblock($server);

$clients = [];

while (true) {
    $read_sockets = [$server] + $clients;
    $write = $except = [];

    // 監聽所有 sockets,超時為 1 秒
    $changed = socket_select($read_sockets, $write, $except, 1);

    if ($changed === false) {
        echo "socket_select() 發生錯誤\n";
        break;
    }

    // 有新的連接請求
    if (in_array($server, $read_sockets)) {
        $client = socket_accept($server);
        if ($client !== false) {
            socket_getpeername($client, $ip, $port);
            echo "新連接來自 {$ip}:{$port}\n";
            $clients[] = $client;
        }
        // 從 read 列表中移除監聽 socket
        $key = array_search($server, $read_sockets);
        unset($read_sockets[$key]);
    }

    // 處理已有連接的數據
    foreach ($read_sockets as $sock) {
        $data = @socket_read($sock, 1024, PHP_NORMAL_READ);
        if ($data === false || trim($data) === '') {
            // 客戶端斷開連接
            $key = array_search($sock, $clients);
            unset($clients[$key]);
            socket_close($sock);
            continue;
        }

        $data = trim($data);
        echo "接收到數據:{$data}\n";

        $response = "你發送了:{$data}\n";
        socket_write($sock, $response);
    }
}
socket_close($server);

三、總結

  • socket_accept()是阻塞的,適合簡單的、同步的單連接場景;

  • 要構建一個支持並發的服務器,應當使用socket_select()來監聽socket 的狀態;

  • socket_select()可以讓你在多個socket 上“等待”事件,並避免因為一個阻塞操作導致整個程序掛起;

  • 建議配合socket_set_nonblock()使用,提高靈活性。

通過以上方式,你可以實現一個響應迅速、可同時處理多個客戶端連接的PHP Socket 服務器,為更複雜的網絡編程打下基礎。

在實際開發中,域名或者地址替換為自己的服務地址,例如: