當前位置: 首頁> 最新文章列表> 如何在非阻塞監聽中臨時切換socket_set_block 狀態

如何在非阻塞監聽中臨時切換socket_set_block 狀態

gitbox 2025-05-26

在PHP 中使用socket進行網絡編程時,非阻塞模式能夠提升並發處理能力,是實現事件驅動或多路復用服務器的關鍵。然而,某些時候我們又需要臨時地將套接字設置為阻塞狀態,以完成某些“必須等待”的操作,例如完整讀取一個大數據包,或者等待客戶端發送完整的握手數據。

幸運的是,PHP 提供了socket_set_block()socket_set_nonblock()兩個函數,可以非常方便地在阻塞與非阻塞模式之間進行切換。

本文將結合一個具體的TCP 服務端示例,演示如何在非阻塞監聽中臨時切換套接字為阻塞狀態,並在完成操作後恢復非阻塞狀態。

示例場景

我們構建一個非阻塞TCP 服務器,監聽客戶端連接,並在接收到客戶端連接之後,臨時將該連接切換為阻塞模式,從客戶端讀取一段數據,完成後再切回非阻塞模式。

以下是完整代碼:

<code> <?php

$host = '0.0.0.0';
$port = 12345;

// 創建socket
$server = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($server, SOL_SOCKET, SO_REUSEADDR, 1);

// 綁定和監聽
socket_bind($server, $host, $port);
socket_listen($server);

// 設置為非阻塞
socket_set_nonblock($server);

echo "Server listening on {$host}:{$port} ...\n";

$clients = [];

while (true) {
// 接受連接(非阻塞)
$client = @socket_accept($server);
if ($client !== false) {
echo "New client connected.\n";
// 添加到客戶端列表
$clients[] = $client;

     // 設置客戶端為非阻塞
    socket_set_nonblock($client);
}

foreach ($clients as $index => $sock) {
    $buffer = '';

    // 臨時設置為阻塞以確保完整讀取(例如讀取固定長度或直到某個標誌)
    socket_set_block($sock);

    $bytes = @socket_recv($sock, $buffer, 1024, MSG_DONTWAIT);

    // 恢復為非阻塞
    socket_set_nonblock($sock);

    if ($bytes === false || $bytes === 0) {
        // 客戶端斷開
        echo "Client disconnected.\n";
        socket_close($sock);
        unset($clients[$index]);
        continue;
    }

    // 輸出讀取數據
    echo "Received: " . trim($buffer) . "\n";

    // 示例響應
    $response = "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello from server!\n";
    socket_write($sock, $response);
}

// 輕微延遲避免 CPU 佔滿
usleep(100000);

}

socket_close($server);
?>
</code>

使用場景說明

在上述代碼中,我們的監聽socket($server)是非阻塞的,因此不會在socket_accept()上阻塞。每當接受到新連接後,我們將新客戶端也設置為非阻塞。然而,在我們處理接收數據的部分,我們主動將客戶端socket 切換為阻塞模式,這是因為我們希望保證socket_recv()能完整接收到我們期望的數據,避免數據殘缺。

這是非常常見的混合使用方式,在很多場景(如協議握手階段)尤其重要。例如你設計了一個簡單協議,規定客戶端第一次發送必須包含固定頭信息,服務端需要完整接收後才能繼續處理,此時使用阻塞讀取就變得非常有意義。

注意事項

  • 切換模式要謹慎:在非阻塞流程中臨時使用阻塞讀取是一種“強制等待”的手段,雖然方便,但如果客戶端沒有發送任何數據,服務端會被卡住,建議搭配超時機製或預設數據長度。

  • 適用於短期阻塞:這類模式切換適用於某些操作必須阻塞完成的情況,不建議在整個通信過程中都採用阻塞方式。

結語

使用socket_set_block()socket_set_nonblock()可以讓你的PHP 套接字編程更具彈性。你可以根據處理邏輯靈活選擇合適的阻塞模式,從而兼顧效率與完整性。

更多socket 編程的實踐與示例可以參考文檔或訪問我們的資源平台:

https://gitbox.net/php-socket-guide