凌峰创科服务平台

WebSocket PHP服务器代码如何实现?

PHP 如何实现 WebSocket?

标准的 PHP(如 php-fpmmod_php)是同步阻塞的,每次只能处理一个请求,当它等待 WebSocket 客户端消息时,整个进程就会被阻塞,无法处理其他 HTTP 请求。不能直接用传统的 PHP 脚本来构建一个高性能的 WebSocket 服务器

WebSocket PHP服务器代码如何实现?-图1
(图片来源网络,侵删)

要实现一个健壮的 WebSocket 服务器,我们需要 PHP 的 多进程多线程 能力,以下是目前最主流和推荐的几种方法:

  1. Ratchet (PHP 库):这是最流行、最简单的选择,它基于 ReactPHP 事件循环库,让你能用面向对象的方式轻松构建 WebSocket 服务器,而无需直接处理底层的 socket、多进程等复杂细节。强烈推荐初学者和大多数项目使用。
  2. Swoole (PHP 扩展):一个功能极其强大的 PHP 协程框架,它提供了高性能的异步网络通信能力,包括 WebSocket 服务器、HTTP 服务器、TCP/UDP 服务器等,性能远超 Ratchet,但需要安装 PECL 扩展,学习曲线稍陡。
  3. Workerman (PHP 库):另一个高性能的 PHP socket 服务器框架,与 Swoole 类似,支持长连接、异步、多进程,它不依赖 ReactPHP,有自己的实现。

使用 Ratchet (最推荐,最简单)

这个方案非常适合快速开发和中小型应用。

步骤 1: 安装 Ratchet

你需要通过 Composer 安装 Ratchet。

# 在你的项目根目录下执行
composer require cboden/ratchet

步骤 2: 编写 WebSocket 服务器代码

创建一个文件,server.php,这个文件将定义一个处理 WebSocket 连接、消息和断开连接的类。

WebSocket PHP服务器代码如何实现?-图2
(图片来源网络,侵删)
<?php
// server.php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
// 1. 创建一个处理消息的类
class Chat implements MessageComponentInterface {
    // 存储所有连接的客户端
    protected $clients;
    public function __construct() {
        // 使用一个 SplObjectStorage 来存储客户端,它是一个高性能的对象集合
        $this->clients = new \SplObjectStorage;
    }
    // 当有新的客户端连接时触发
    public function onOpen(ConnectionInterface $conn) {
        // 将新连接添加到客户端列表
        $this->clients->attach($conn);
        echo "New connection! ({$conn->resourceId})\n";
    }
    // 当从客户端接收到消息时触发
    public function onMessage(ConnectionInterface $from, $msg) {
        $numRecv = count($this->clients) - 1;
        echo sprintf('Connection %d sending message "%s" to %d other connection%s' . "\n"
            , $from->resourceId, $msg, $numRecv, $numRecv == 1 ? '' : 's');
        // 将收到的消息广播给所有其他客户端
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // 发送消息
                $client->send($msg);
            }
        }
    }
    // 当客户端断开连接时触发
    public function onClose(ConnectionInterface $conn) {
        // 从客户端列表中移除断开的连接
        $this->clients->detach($conn);
        echo "Connection {$conn->resourceId} has disconnected\n";
    }
    // 当发生错误时触发
    public function onError(ConnectionInterface $conn, \Exception $e) {
        echo "An error has occurred: {$e->getMessage()}\n";
        $conn->close();
    }
}
// 2. 运行服务器
use Ratchet\Server\IoServer;
use Ratchet\Http\HttpServer;
use Ratchet\WebSocket\WsServer;
// 创建一个 WebSocket 服务器,监听所有网络接口的 8080 端口
$server = IoServer::factory(
    new HttpServer(
        new WsServer(
            new Chat()
        )
    ),
    8080
);
echo "Chat server started on ws://localhost:8080\n";
// 启动服务器并开始循环监听
$server->run();

步骤 3: 启动服务器

在终端中,运行 server.php 文件。注意: 这个脚本需要持续运行,不能关闭终端。

php server.php

你会看到输出: Chat server started on ws://localhost:8080

你的 WebSocket 服务器已经在 8080 端口上运行了。

步骤 4: 创建一个简单的 HTML/JavaScript 客户端来测试

创建一个 index.html 文件:

WebSocket PHP服务器代码如何实现?-图3
(图片来源网络,侵删)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">PHP WebSocket Chat</title>
    <style>
        body { font-family: Arial, sans-serif; }
        #chat-log { border: 1px solid #ccc; height: 300px; overflow-y: scroll; padding: 10px; margin-bottom: 10px; }
        #message-input { width: 80%; padding: 5px; }
        #send-button { width: 18%; padding: 5px; }
    </style>
</head>
<body>
    <h1>PHP WebSocket Chat</h1>
    <div id="chat-log"></div>
    <input type="text" id="message-input" placeholder="Type a message...">
    <button id="send-button">Send</button>
    <script>
        // 连接到 WebSocket 服务器
        // 注意:如果你的服务器不在本机,请将 localhost 替换为服务器 IP
        const socket = new WebSocket('ws://localhost:8080');
        const chatLog = document.getElementById('chat-log');
        const messageInput = document.getElementById('message-input');
        const sendButton = document.getElementById('send-button');
        // 连接建立时
        socket.onopen = function(event) {
            console.log('Connection established!');
            addToLog('Connected to the chat server.');
        };
        // 收到消息时
        socket.onmessage = function(event) {
            console.log('Message received:', event.data);
            addToLog(event.data);
        };
        // 连接关闭时
        socket.onclose = function(event) {
            if (event.wasClean) {
                console.log(`Connection closed cleanly, code=${event.code} reason=${event.reason}`);
            } else {
                console.error('Connection died');
            }
            addToLog('Disconnected from the chat server.');
        };
        // 发生错误时
        socket.onerror = function(error) {
            console.error(`WebSocket Error: ${error.message}`);
            addToLog('An error occurred.');
        };
        // 发送消息
        function sendMessage() {
            const message = messageInput.value;
            if (message) {
                socket.send(message);
                messageInput.value = '';
            }
        }
        // 添加消息到日志
        function addToLog(message) {
            const p = document.createElement('p');
            p.textContent = message;
            chatLog.appendChild(p);
            chatLog.scrollTop = chatLog.scrollHeight;
        }
        // 绑定发送按钮事件
        sendButton.addEventListener('click', sendMessage);
        // 绑定回车键发送
        messageInput.addEventListener('keypress', function(event) {
            if (event.key === 'Enter') {
                sendMessage();
            }
        });
    </script>
</body>
</html>

用浏览器打开 index.html 文件,打开两个浏览器窗口(或两个标签页),你可以在一个窗口输入消息,然后在另一个窗口看到它,这就是一个简单的聊天室!


使用 Swoole (高性能方案)

如果你的应用对性能有极高要求,可以考虑 Swoole。

步骤 1: 安装 Swoole 扩展

你需要先编译并安装 Swoole PECL 扩展。

# (CentOS/RHEL)
pecl install swoole
# (Ubuntu/Debian)
sudo apt-get install php-dev php-pear
sudo pecl install swoole
# 然后在 php.ini 中添加 extension=swoole

步骤 2: 编写 Swoole WebSocket 服务器代码

创建 swoole_server.php 文件:

<?php
// swoole_server.php
$server = new Swoole\WebSocket\Server("0.0.0.0", 9501);
// 当有新的 WebSocket 连接进入时
$server->on('open', function (Swoole\WebSocket\Server $server, $request) {
    echo "New connection: {$request->fd}\n";
    // 可以在这里将用户ID与fd关联,
    // $server->bind($request->fd, $userId);
});
// 当收到客户端消息时
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
    echo "Received message: {$frame
分享:
扫描分享到社交APP
上一篇
下一篇