发布于 2026-01-05 1 阅读
0

由 Mux 呈现的 PHP DEV 全球展示挑战赛:展示你的项目!

使用 PHP 的 WebSocket

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

WebSocket场景

PHP 通常用作服务器端语言,用于响应 HTTP 请求。这类请求不具有持久性,且是无状态的。其流程很简单:客户端(浏览器)向服务器发出请求(GET、POST 或 PUT 等)以获取特定资源。服务器收到资源后,会关闭连接。如果客户端需要其他资源,则会再次发起请求。这种类型的流程不具有持久性,并且由客户端控制。

在 WebSocket 场景中,包含服务器端和多个客户端。客户端可以连接到服务器端;如果客户端想要与其他客户端通信,可以通过向服务器发送消息来实现。服务器会将消息转发或发送给客户端。
客户端和服务器之间的连接是持久的,允许进行双向消息通信。

基于 PHP schema 架构的 WebSocket

服务器端逻辑概述

在开始编写代码之前,让我先解释一下我们将要实现的示例应用程序。
我们有一个使用 PHP 和 OpenSwoole 实现的服务器端服务。我们将使用基于 TCP 协议和安全 WebSocket 的“Swoole WebSocket 服务器”(网络通信经过加密,类似于 HTTP 和 HTTPS)。
在服务器端,我们将实现一个基于 TCP 协议的 WebSocket 服务,该服务监听并响应一些特定事件:

  • 启动:当服务器端 WebSocket 部分启动时;
  • 打开:当收到来自新客户端的新连接请求时
  • 消息:当从某个活动连接收到消息时。以“Open”初始化的连接保持活动状态并持久存在;
  • 关闭:当客户端关闭连接时。客户端关闭连接意味着其特定连接失效;
  • 断开连接:客户端断开连接时。

服务器会通过 Swoole 表来跟踪已连接的客户端。Swoole 表是一种类似于集合(二维数组)的数据结构,它允许多个并发线程同时进行添加、删除和检索操作。在典型的 PHP 应用程序中,由于通常只有一个线程访问集合或数据结构,因此不需要这种机制。

一旦服务器收到来自已连接客户端的新消息(通过“消息”事件),服务器将遍历所有已连接的客户端(存储在 Swoole 表数据结构中),并将消息传递给所有客户端(通过推送方法)。

前端逻辑概述

前端逻辑很简单。我们将创建一个简单的 HTML 文件(抱歉,没有样式),其中包含一个文本输入框,供用户填写消息,以及一个按钮,用于通过 WebSocket 发送消息。这部分功能由 HTML 文件中的内联 JavaScript 脚本实现。

我们将与该类建立连接WebSocket,并实现一些回调函数:

  • onmessage:用于管理客户端何时收到消息;
  • onopen:表单管理何时建立 WebSocket 连接(与服务器的连接);
  • onclose:用于管理 WebSocket 连接何时关闭;

当用户点击按钮时,将调用sendMessage()send()函数。该函数会调用WebSocket 对象中的方法将消息推送到服务器。

构建 WebSocket 示例

安装 OpenSwoole

要在 PHP 中实现 WebSocket,您必须安装一个额外的模块,例如 Swoole。有很多模块可以让您在 PHP 中实现 WebSocket 服务。在本教程中,我将使用 SwooleLabs 的 Open Swoole 实现,因为 Open Swoole 包含对 WebSocket 的支持。

OpenSwoole是通过PECL软件包发布的,所以你可以使用PECL安装程序来安装它。

pecl install -f -D 'enable-openssl="no" enable-sockets="no" enable-http2="no" enable-mysqlnd="no" enable-hook-curl="no" with-postgres="no"' openswoole
Enter fullscreen mode Exit fullscreen mode

使用 -D 选项,您可以指定要启用的选项。如果您想启用安全 WebSocket,我建议启用“enable-openssl”。

-f 选项用于强制重新安装之前已安装过的软件包。

安装 Composer 包

使用最新版本(22)的 Open Swoole,您可以使用 Open Swoole 提供的标准软件包(PSR)。因此,您需要安装该openswoole/core软件包:

composer require openswoole/core:22.1.5
Enter fullscreen mode Exit fullscreen mode

或者,如果您想安装最新的稳定版本,可以运行:

composer require openswoole/core
Enter fullscreen mode Exit fullscreen mode

安装 IDE 助手(可选)

如果您的编辑器(例如 VS Code 或 Zed)无法识别 OpenSwoole 类或方法的语法,则应考虑安装 OpenSwoole IDE-Helper 包。

编辑器无法识别 OpenSwoole 语法

OpenSwoole IDE Helper 软件包提供全面的 IDE 帮助文件,实现准确的自动补全,提升您的整体开发体验。

OpenSwoole IDE-Helper 包:https://packagist.org/packages/openswoole/ide-helper

要安装 OpenSwoole IDE Helper 包,您可以使用 Composer。请在您的项目目录中运行以下命令:

composer require openswoole/ide-helper --dev
Enter fullscreen mode Exit fullscreen mode

安装 IDE 助手后,您的编辑器将能够识别 OpenSwoole 元素的语法。

带有 OpenSwoole IDE 助手的编辑器

使用 SSL 证书(可选)

如果您想在安装过程中建立安全的enable-sockets="yes"WebSocket 连接,并且使用了该选项,则需要:

  • 创建私有证书和公共证书
  • 正确设置 WebSocket 服务

首先,让我来创建新证书:

mkcert localhost 127.0.0.1 ::1
Enter fullscreen mode Exit fullscreen mode

创建两个文件:

  • localhost+2-key.pem SSL密钥文件
  • localhost+2.pem SSL证书文件

这两个文件会在您实例化 WebSocket 服务器类时加载。

服务器端代码

创建一个名为websocket.php 的新文件。

首先,我们需要引入正确的类。从 OpenSwoole 版本 22 开始,您可以使用 OpenSwoole 命名空间:

<?php

use OpenSwoole\WebSocket\{Frame, Server};
use OpenSwoole\Constant;
use OpenSwoole\Http\Request;
use OpenSwoole\Table;
Enter fullscreen mode Exit fullscreen mode

Server在端口上创建新实例9501,监听0.0.0.0(接受所有传入请求),使用 TCP 协议(Constant::SOCK_TCP​​)。如果要启用安全 WebSocket,则应使用Constant::SSL
第四个参数Constant::SOCK_TCP || Constant::SSL

$server = new Server("0.0.0.0", 9501, Server::SIMPLE_MODE, Constant::SOCK_TCP);
Enter fullscreen mode Exit fullscreen mode

为了存储连接到 WebSocket 的客户端列表,创建一个包含fd(文件描述符)和name字段的表(二维内存表)

$fds = new Table(1024);
$fds->column('fd', Table::TYPE_INT, 4);
$fds->column('name', Table::TYPE_STRING, 16);
$fds->create();
Enter fullscreen mode Exit fullscreen mode

如果要创建安全的WebSocket 连接,则必须配置服务器以使用 SSL 证书:

$server->set([
    'ssl_cert_file' => __DIR__ . '/localhost+2.pem',
    'ssl_key_file' => __DIR__ . '/localhost+2-key.pem'
]);
Enter fullscreen mode Exit fullscreen mode

在启动服务器之前,必须定义 WebSocket 服务器分发的事件的处理函数。

聆听开始事件。

WebSocket 服务启动后,将触发“Start”事件。

$server->on("Start", function (Server $server) {
    echo "Swoole WebSocket Server is started at " . $server->host . ":" . $server->port . "\n";
});
Enter fullscreen mode Exit fullscreen mode

聆听公开活动。

当客户端连接后,就会触发“打开”事件。

$server->on('Open', function (Server $server, Request $request) use ($fds) {
    $fd = $request->fd;
    $clientName = sprintf("Client-%'.06d\n", $request->fd);
    $fds->set($request->fd, [
        'fd' => $fd,
        'name' => sprintf($clientName)
    ]);
    echo "Connection <{$fd}> open by {$clientName}. Total connections: " . $fds->count() . "\n";
    foreach ($fds as $key => $value) {
        if ($key == $fd) {
            $server->push($request->fd, "Welcome {$clientName}, there are " . $fds->count() . " connections");
        } else {
            $server->push($key, "A new client ({$clientName}) is joining to the party");
        }
    }
});
Enter fullscreen mode Exit fullscreen mode

监听消息事件。

当客户端向 WebSocket 服务发送消息时,就会触发“消息”事件。

$server->on('Message', function (Server $server, Frame $frame) use ($fds) {
    $sender = $fds->get(strval($frame->fd), "name");
    echo "Received from " . $sender . ", message: {$frame->data}" . PHP_EOL;
    foreach ($fds as $key => $value) {
        if ($key == $frame->fd) {
            $server->push($frame->fd, "Message sent");
        } else {
            $server->push($key,  "FROM: {$sender} - MESSAGE: " . $frame->data);
        }
    }
});
Enter fullscreen mode Exit fullscreen mode

聆听关闭事件。

当客户端关闭连接时,将触发“关闭”事件。

$server->on('Close', function (Server $server, int $fd) use ($fds) {
    $fds->del($fd);
    echo "Connection close: {$fd}, total connections: " . $fds->count() . "\n";
});
Enter fullscreen mode Exit fullscreen mode

正在监听断开连接事件。

当客户端失去连接时,将触发“断开连接”事件。

$server->on('Disconnect', function (Server $server, int $fd) use ($fds) {
    $fds->del($fd);
    echo "Disconnect: {$fd}, total connections: " . $fds->count() . "\n";
});
Enter fullscreen mode Exit fullscreen mode

启动服务

使用该方法启动 WebSocket 服务器start()。服务器启动后,将触发“ Start ”事件。

$server->start();
Enter fullscreen mode Exit fullscreen mode

启动 WebSocket 服务

您可以通过命令行启动这项新服务:

php websocket.php
Enter fullscreen mode Exit fullscreen mode

启动 WebSocket PHP 服务

前端代码

创建一个新的index.html文件。

代码很简单。如果在服务器端进行设置并想使用安全 Web 套接字连接,则必须使用wss://协议而不是ws://协议。

在实例化 WebSocket 对象时,请务必使用正确的 IP 地址(或主机名)和正确的端口(与您在 PHP 脚本中定义的端口相同)。

<!doctype html>
<html>
<head>
    <title> WebSocket with PHP and Open Swoole </title>
    <script>
        let echo_service;
        append = function (text) {
            document.getElementById("websocket_events").insertAdjacentHTML('beforeend',
                "<li>" + text + ";</li>"
            );
        }
        window.onload = function () {
            echo_service = new WebSocket('ws://127.0.0.1:9501');
            echo_service.onmessage = function (event) {
                append(event.data)
            }
            echo_service.onopen = function () {
                append("Connected to WebSocket!");
            }
            echo_service.onclose = function () {
                append("Connection closed");
            }
            echo_service.onerror = function () {
                append("Error happens");
            }
        }

        function sendMessage(event) {
            console.log(event)
            let message = document.getElementById("message").value;
            echo_service.send(message);
        }

    </script>
</head>

<body>
    <div>
        Message: <input value="Hello!" type="text" id="message" /><br><br>
        <input type="button" value="Submit" onclick="sendMessage(event)" /><br>
        <ul id="websocket_events">
        </ul>
    </div>
</body>

</html>
Enter fullscreen mode Exit fullscreen mode

如果你用浏览器打开或访问HTML文件(在多个标签页中打开以模拟多个客户端),你就可以开始和自己聊天了 ;)

使用 PHP 和 WebSocket 实现实时聊天

源代码

我已将本文中使用的代码发布到 GitHub:https://github.com/roberto-butti/websocket-php

故障排除

有时,在安装或编译过程中,您可能会遇到错误,提示找不到“pcre2.h”文件。

这通常是由于系统中缺少 PCRE(Perl 兼容正则表达式)库或配置不正确导致的。以下是在 macOS 上解决此问题的方法。

首先,您可以检查是否已安装该pcre2库。
如果您使用的是 Homebrew,可以进行如下检查:

brew info pcre2
Enter fullscreen mode Exit fullscreen mode

如果安装了 Pcre2 软件包,则出现此问题的最常见原因之一是 pcre2.h(pcre2 的 C 源头文件)不在预期的目录中。

编译过程需要 OpenSwoole 模块的文件。
该文件可能位于 Pcre2 目录中,但 OpenSwoole 编译过程会在 PHP 文件之间查找该文件。因此,一种方法是从 PHP 目录创建指向 Pcre2 目录的符号链接:

ln -s /opt/homebrew/Cellar/pcre2/10.42/include/pcre2.h /opt/homebrew/Cellar/php/8.3.3/include/php/ext/pcre/pcre2.h
Enter fullscreen mode Exit fullscreen mode

在命令中,我使用了链接工具ln,该-s标志会创建一个符号链接,并且我使用了指定的10.42pcre2 版本和8.3.3PHP 版本。您需要替换当前 pcre2 和 PHP 的版本。

执行该命令后,您将在 PHP 目录中找到一个符号链接/opt/homebrew/Cellar/php/8.3.3/include/php/ext/pcre/pcre2.h,以便您可以执行pecl安装 OpenSwoole 模块的命令。

文章来源:https://dev.to/robertobutti/websocket-with-php-4k2c