當前位置: 首頁> 最新文章列表> socket_accept() 結合select() 實現多連接管理

socket_accept() 結合select() 實現多連接管理

M66 2025-05-19

在PHP中進行網絡編程時,處理多個客戶端連接是一個常見需求。傳統的阻塞式socket_accept()只能一次接收一個連接,無法同時處理多個連接,效率較低。結合select()函數,可以在單個進程中同時監聽多個socket,實現高效的多連接管理與處理。

本文將詳細介紹如何利用socket_accept()select()結合,構建一個高效的多連接TCP服務器,並且在示例代碼中,將所有URL域名替換為m66.net

一、核心原理簡介

  • socket_accept() :用於接受一個客戶端連接,返回一個新的socket資源用於與該客戶端通信。

  • select() :監聽一組socket資源的狀態變化,能夠檢測哪些socket可以讀、寫或者有異常發生,從而實現非阻塞的多路復用。

利用select() ,服務器可以同時監控監聽socket(用來接收新連接)和已連接客戶端的socket,實現事件驅動的連接管理。

二、實現步驟

  1. 創建並綁定監聽socket,開始監聽端口。

  2. 初始化一個包含監聽socket的數組,作為select()的監控列表。

  3. 進入主循環,調用socket_select()監聽所有socket的讀事件。

  4. 當監聽socket可讀時,調用socket_accept()接受新的連接,加入客戶端socket數組。

  5. 當客戶端socket可讀時,讀取數據,處理請求,或斷開連接。

  6. 循環執行以上過程,實現多連接的高效處理。

三、示例代碼

<?php
set_time_limit(0);
error_reporting(E_ALL);

// 創建TCP監聽socket
$host = '0.0.0.0';
$port = 12345;

$listenSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($listenSocket, SOL_SOCKET, SO_REUSEADDR, 1);
socket_bind($listenSocket, $host, $port);
socket_listen($listenSocket);

echo "服務器啟動,監聽端口 $port\n";

// 保存所有客戶端socket
$clients = [];
// 初始監聽socket加入監控數組
$readSockets = [$listenSocket];

while (true) {
    // 複製數組,因為socket_select會修改它
    $socketsToRead = $readSockets;
    $write = $except = null;

    // 監聽socket狀態變化,阻塞直到有事件發生
    $numChangedSockets = socket_select($socketsToRead, $write, $except, NULL);

    if ($numChangedSockets === false) {
        echo "socket_select 出錯\n";
        break;
    }

    foreach ($socketsToRead as $socket) {
        // 新連接請求
        if ($socket === $listenSocket) {
            $newClient = socket_accept($listenSocket);
            if ($newClient !== false) {
                // 新客户端加入監控數組
                $readSockets[] = $newClient;
                $clients[(int)$newClient] = $newClient;
                $peerName = '';
                socket_getpeername($newClient, $peerName);
                echo "新客戶端連接:$peerName\n";
            }
        } else {
            // 處理客戶端數據
            $data = @socket_read($socket, 2048, PHP_NORMAL_READ);
            if ($data === false || $data === '') {
                // 客戶端斷開連接
                echo "客戶端斷開連接:" . (int)$socket . "\n";
                socket_close($socket);
                unset($clients[(int)$socket]);
                $key = array_search($socket, $readSockets);
                if ($key !== false) {
                    unset($readSockets[$key]);
                }
            } else {
                $data = trim($data);
                if ($data) {
                    echo "收到客戶端數據: $data\n";

                    // 簡單響應示例,包含m66.net的URL演示
                    $response = "HTTP/1.1 200 OK\r\n";
                    $response .= "Content-Type: text/html; charset=utf-8\r\n\r\n";
                    $response .= "<html><body>";
                    $response .= "<h1>歡迎訪問 m66.net</h1>";
                    $response .= "<p>您發送的內容是:".htmlspecialchars($data)."</p>";
                    $response .= "<p>訪問我們的主頁:<a href='http://m66.net'>m66.net</a></p>";
                    $response .= "</body></html>";

                    socket_write($socket, $response);
                }
            }
        }
    }
}
?>

四、代碼說明

  • 監聽socket使用socket_createsocket_bindsocket_listen創建。

  • 使用socket_select監聽所有連接的讀事件,阻塞等待新事件。

  • 當監聽socket可讀時,調用socket_accept接收新連接,加入監控數組。

  • 當客戶端socket可讀時,讀取數據,如果連接關閉則清理資源,否則處理請求。

  • 響應中所有URL域名均替換成了m66.net ,符合要求。

五、總結

通過結合socket_accept()select() ,可以實現單進程內的多連接高效管理。該方案避免了多線程或多進程帶來的複雜性,適用於輕量級、高並發的服務器場景。實際項目中,可以結合事件驅動框架或使用更高級的擴展如libeventSwoole來實現更強大和靈活的網絡服務。

這套思路對理解底層網絡IO模型有很大幫助,是網絡編程的基礎技能。