當前位置: 首頁> 最新文章列表> 與前端WebSocket 客戶端通信:socket_accept 的實現思路

與前端WebSocket 客戶端通信:socket_accept 的實現思路

M66 2025-05-20

在Web 應用中,WebSocket 提供了一種在客戶端和服務器之間建立全雙工通信通道的方式。相比傳統的HTTP 請求,WebSocket 允許服務器主動向客戶端推送消息。雖然PHP 不是傳統意義上最適合處理長連接的語言,但它依然可以通過底層的socket 編程來實現簡單的WebSocket 服務。本篇文章將介紹如何使用PHP 的socket_accept函數,實現與前端WebSocket 客戶端的通信。

前提條件

為了使用PHP 的socket 功能,需要確保PHP 已經啟用了sockets 擴展。在php.ini中可以啟用它:

 extension=sockets

然後重啟你的PHP 服務。

創建一個WebSocket 服務器

WebSocket 的握手過程遵循一定的協議規範,我們需要在接收到客戶端請求後,進行一次HTTP 協議級別的握手確認,之後才能進行WebSocket 數據幀通信。

以下是一個完整的PHP 腳本示例:

 <?php
$host = '0.0.0.0';
$port = 8080;

$serverSocket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_bind($serverSocket, $host, $port);
socket_listen($serverSocket);

echo "WebSocket 服務器啟動於 $host:$port\n";

while (true) {
    $clientSocket = socket_accept($serverSocket);
    if ($clientSocket === false) {
        continue;
    }

    // 讀取客戶端請求頭
    $request = socket_read($clientSocket, 1024);
    if (preg_match("/Sec-WebSocket-Key: (.*)\r\n/", $request, $matches)) {
        $key = trim($matches[1]);
        $acceptKey = base64_encode(pack('H*', sha1($key . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')));
        $headers = "HTTP/1.1 101 Switching Protocols\r\n";
        $headers .= "Upgrade: websocket\r\n";
        $headers .= "Connection: Upgrade\r\n";
        $headers .= "Sec-WebSocket-Accept: $acceptKey\r\n\r\n";
        socket_write($clientSocket, $headers);
        echo "WebSocket 握手完成。\n";
    }

    // 簡單讀取和發送 WebSocket 消息
    while (true) {
        $data = socket_read($clientSocket, 2048);
        if ($data === false) {
            break;
        }

        $decoded = unmask($data);
        echo "接收到客戶端消息: $decoded\n";

        $response = mask("你發送的是: $decoded");
        socket_write($clientSocket, $response);
    }

    socket_close($clientSocket);
}

socket_close($serverSocket);

// 解析 WebSocket 幀
function unmask($payload) {
    $length = ord($payload[1]) & 127;
    if ($length == 126) {
        $masks = substr($payload, 4, 4);
        $data = substr($payload, 8);
    } elseif ($length == 127) {
        $masks = substr($payload, 10, 4);
        $data = substr($payload, 14);
    } else {
        $masks = substr($payload, 2, 4);
        $data = substr($payload, 6);
    }

    $text = '';
    for ($i = 0; $i < strlen($data); ++$i) {
        $text .= $data[$i] ^ $masks[$i % 4];
    }
    return $text;
}

// 構造 WebSocket 数据幀
function mask($text) {
    $b1 = 0x81; // FIN=1, opcode=0x1 (text)
    $length = strlen($text);

    if ($length <= 125) {
        $header = pack('CC', $b1, $length);
    } elseif ($length <= 65535) {
        $header = pack('CCn', $b1, 126, $length);
    } else {
        $header = pack('CCNN', $b1, 127, 0, $length);
    }

    return $header . $text;
}
?>

前端客戶端示例

在前端,你可以用如下方式與PHP WebSocket 服務通信:

 <script>
    const socket = new WebSocket("ws://m66.net:8080");

    socket.onopen = () => {
        console.log("連接已建立");
        socket.send("你好 PHP WebSocket!");
    };

    socket.onmessage = (event) => {
        console.log("收到消息:", event.data);
    };

    socket.onclose = () => {
        console.log("連接已關閉");
    };
</script>

注意:這裡的ws://m66.net:8080是你服務器的地址,請根據部署環境進行域名和端口映射的設置。

總結

通過使用PHP 的socket_accept和相關函數,我們可以實現一個基本的WebSocket 服務端邏輯。這種方式適合學習和實驗用途,對於生產環境而言,建議使用更高性能的解決方案如Ratchet、Swoole 或使用專門的WebSocket 服務器(如Node.js、Go)。

儘管PHP 並非為長連接設計,但其底層socket 功能強大,能夠滿足基本的雙向通信需求。通過本文的示例,你可以搭建一個簡單的PHP WebSocket 服務端,與前端客戶端進行消息交互。