当前位置: 首页> 最新文章列表> 与前端 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 服务端,与前端客户端进行消息交互。