使用 PHP 和 WebSocket 构建实时聊天应用 完整指南

使用 PHP 和 WebSocket 构建实时聊天应用:完整指南

什么是实时聊天,为什么要构建它?

实时通信已经成为现代 Web 应用的核心功能,让用户之间可以即时交互。想想 Slack、Facebook Messenger 或 WhatsApp 这些应用——它们都依赖于实时发送消息的能力,无需刷新页面或手动重新加载内容。

对于想要构建类似系统的开发者来说,PHP——最广泛使用的服务端语言之一——是一个很好的起点。但我们如何在 PHP 中实现实时消息传递呢?

让我们深入探讨如何使用 PHP 和 WebSocket 构建实时聊天应用。虽然 PHP 通常用于处理标准的 HTTP 请求(常规的请求-响应循环),但它本身并不支持持久的双向通信。这就是 WebSocket 的用武之地——一个专门为服务器和客户端之间的实时通信而设计的协议。

我们将探讨 WebSocket 背后的概念,指导你完成设置过程,并展示如何将它们与 PHP 集成,以构建一个强大且可扩展的聊天应用。无论你是初学者还是经验丰富的开发者,本指南都将帮助你掌握在 PHP 中实现实时通信的理论和实践步骤。

原文链接 使用 PHP 和 WebSocket 构建实时聊天应用:完整指南

什么是 WebSocket,它如何工作?

在深入代码之前,让我们先了解一下 WebSocket 以及它与传统 HTTP 通信的区别。

WebSocket vs. HTTP:关键区别

HTTP 是无状态协议。每次客户端发送请求(例如,当你加载网页或发送消息时),它都会与服务器建立新连接,处理请求,并在数据发送完成后关闭连接。

WebSocket 则支持全双工通信。这意味着一旦建立 WebSocket 连接,服务器和客户端就可以相互发送消息,而无需反复打开和关闭连接。WebSocket 非常适合需要实时数据交换的应用,如聊天应用、在线游戏和股票行情更新。

WebSocket 的工作原理

握手(Handshake):客户端(浏览器)通过 HTTP 向服务器发送 WebSocket 握手请求。如果服务器支持 WebSocket,它会响应并升级到 WebSocket 协议。

持久连接:握手完成后,建立持久连接,允许客户端和服务器随时发送数据。

数据交换:连接打开后,数据可以以小数据包的形式发送,最大限度地减少延迟。

关闭连接:客户端或服务器都可以在不再需要时发起关闭 WebSocket 连接。

设置环境:工具和要求

在本指南中,我们将使用以下技术:

  • PHP:用于构建服务端逻辑
  • Ratchet:一个用于 WebSocket 的 PHP 库
  • Composer:用于管理 PHP 依赖
  • JavaScript(客户端):与 WebSocket 服务器交互并实时更新 UI

前置要求

  • PHP 和 JavaScript 的基础知识
  • 机器上安装了 PHP 7.4+
  • 安装了 Composer 用于管理依赖

你可以按照 Composer 官方网站上的说明安装 Composer。

安装 Ratchet

Ratchet 是一个 PHP WebSocket 库,可以轻松使用 WebSocket。要安装它,打开终端并运行以下命令:

/* by 01130.hk - online tools website : 01130.hk/zh/formatperl.html */
composer require cboden/ratchet

这将安装 Ratchet 及其依赖项,包括必要的 WebSocket 服务器功能。

构建 WebSocket 服务器:PHP 和 Ratchet

步骤 1:创建 WebSocket 服务器

我们来创建一个基本的 WebSocket 服务器来处理客户端连接和消息。

1. 创建 WebSocket 服务器脚本

在项目目录中创建一个名为 chat-server.php 的文件。

/* by 01130.hk - online tools website : 01130.hk/zh/formatperl.html */
<?php
require dirname(__DIR__) . '/vendor/autoload.php'; // 自动加载 Composer 依赖
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;

class ChatServer implements MessageComponentInterface {
    protected $clients;

    public function __construct() {
        $this->clients = new \SplObjectStorage;
    }

    public function onOpen(ConnectionInterface $conn) {
        // 当新客户端连接时,将其添加到客户端列表
        $this->clients->attach($conn);
        echo "New connection: " . $conn->resourceId . "\n";
    }

    public function onClose(ConnectionInterface $conn) {
        // 当客户端断开连接时,从客户端列表中移除
        $this->clients->detach($conn);
        echo "Connection closed: " . $conn->resourceId . "\n";
    }

    public function onMessage(ConnectionInterface $from, $msg) {
        // 将传入的消息广播给所有连接的客户端
        foreach ($this->clients as $client) {
            if ($from !== $client) {
                // 将消息发送给除发送者之外的所有客户端
                $client->send($msg);
            }
        }
    }

    public function onError(ConnectionInterface $conn, \Exception $e) {
        // 在这里处理错误
        echo "Error: " . $e->getMessage() . "\n";
        $conn->close();
    }
}

这是 WebSocket 服务器的核心。当新客户端连接时,它会被添加到客户端列表中,当任何客户端发送消息时,该消息会广播给所有连接的客户端。

2. 启动 WebSocket 服务器

现在我们有了服务器脚本,需要创建一个命令来启动 WebSocket 服务器。

创建一个名为 server.php 的新文件:

<?php
require dirname(__DIR__) . '/vendor/autoload.php';

use Ratchet\Server\IoServer;
use Ratchet\WebSocket\WsServer;
use Ratchet\HTTP\Router;
use Ratchet\WebSocket\WsProtocol;

$server = IoServer::factory(
    new WsServer(
        new ChatServer()
    ),
    8080 // WebSocket 服务器端口
);

echo "WebSocket server running on ws://localhost:8080\n";
$server->run();

要启动服务器,只需运行:

php server.php

你的 WebSocket 服务器现在将在 ws://localhost:8080 上运行。

构建客户端:连接到 WebSocket 服务器

现在我们已经设置好了服务器,接下来创建客户端逻辑来与它交互。我们将使用 JavaScript 打开 WebSocket 连接并发送/接收消息。

步骤 2:创建前端

创建一个名为 index.html 的 HTML 文件作为聊天 UI。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Real-Time Chat</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f1f1f1;
            padding: 20px;
        }
        #chat {
            border: 1px solid #ccc;
            padding: 20px;
            height: 400px;
            overflow-y: scroll;
            background-color: #fff;
        }
        input[type="text"] {
            width: 80%;
            padding: 10px;
        }
        button {
            padding: 10px;
        }
    </style>
</head>
<body>
    <div id="chat"></div>
    <input type="text" id="message" placeholder="Type a message..." />
    <button onclick="sendMessage()">Send</button>

    <script>
        const socket = new WebSocket('ws://localhost:8080');
        
        socket.onopen = function() {
            console.log('Connected to WebSocket server');
        };

        socket.onmessage = function(event) {
            const message = event.data;
            const chat = document.getElementById('chat');
            chat.innerHTML += `<p>${message}</p>`;
            chat.scrollTop = chat.scrollHeight;
        };

        socket.onerror = function(error) {
            console.log('WebSocket Error: ' + error);
        };

        function sendMessage() {
            const messageInput = document.getElementById('message');
            const message = messageInput.value;
            if (message.trim() !== '') {
                socket.send(message);
                messageInput.value = '';
            }
        }
    </script>
</body>
</html>

这个基本的前端连接到 WebSocket 服务器并允许用户发送消息。消息显示在 #chat div 中。

身份验证:实现用户认证以支持私信和用户识别

虽然基本的聊天应用可以很好地向所有连接的用户广播消息,但它还没有处理用户身份验证。在实际应用中,用户身份验证对于验证用户身份和确保私密对话的安全至关重要。我们来看看如何在 PHP WebSocket 聊天应用中实现身份验证。

步骤 1:用户身份验证概述

在这个实现中,我们将使用 JWT(JSON Web Token)进行身份验证。JWT 是一种紧凑且自包含的方式,可以在各方之间安全地传输信息。它将帮助我们验证用户并为他们创建会话以发送和接收私信。

步骤 2:在 PHP 中设置 JWT 身份验证

1. 安装 JWT PHP 库

要处理 JWT 的创建和验证,我们将使用 firebase/php-jwt 库。通过 Composer 安装:

composer require firebase/php-jwt
2. 创建身份验证服务器脚本

登录脚本(login.php):

此脚本将验证用户(使用硬编码凭据的简单示例)并发送 JWT。

<?php
require_once 'vendor/autoload.php';

use \Firebase\JWT\JWT;

$secretKey = 'your_secret_key';
$issuedAt = time();
$expirationTime = $issuedAt + 3600;  // jwt 从签发时间起 1 小时内有效
$issuer = 'localhost';

// 演示用的硬编码用户凭据(你可以用真实数据库替换)
$validUsername = 'user';
$validPassword = 'password';

if ($_POST['username'] == $validUsername && $_POST['password'] == $validPassword) {
    // 生成 JWT token
    $payload = array(
        'iat' => $issuedAt,
        'exp' => $expirationTime,
        'iss' => $issuer,
        'user' => $validUsername
    );

    $jwt = JWT::encode($payload, $secretKey);
    echo json_encode(array('token' => $jwt));
} else {
    echo json_encode(array('error' => 'Invalid credentials'));
}
3. WebSocket 服务器身份验证(chat-server.php)

修改 WebSocket 服务器,在 WebSocket 连接过程中使用从客户端发送的 JWT token 验证用户。

// 在 chat-server.php 中修改 onOpen 方法
private function validateToken($token) {
    try {
        $decoded = JWT::decode($token, $this->secretKey, array('HS256'));
        return true;
    } catch (Exception $e) {
        return false;
    }
}
4. 客户端身份验证

连接到 WebSocket 服务器时,客户端将 JWT token 作为查询参数包含进来。

const token = 'your-jwt-token-here'; // 登录后获取
const socket = new WebSocket('ws://localhost:8080?token=' + token);

持久化:在数据库中存储聊天消息

为了使聊天应用更加实用,存储聊天消息至关重要,这样用户可以稍后查看或检索旧消息。为此,我们将使用 MySQL 在数据库中持久化聊天消息。

步骤 1:创建 MySQL 数据库和表

创建一个数据库和一个用于存储聊天消息的表:

CREATE DATABASE chat_app;
USE chat_app;

CREATE TABLE messages (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(255) NOT NULL,
    message TEXT NOT NULL,
    timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

步骤 2:修改 WebSocket 服务器以保存消息

修改 WebSocket 服务器脚本,将每条传入的消息存储在数据库中。我们将使用 PDO 与 MySQL 数据库交互。

// 在 chat-server.php 顶部添加以下内容
$pdo = new PDO('mysql:host=localhost;dbname=chat_app', 'root', '');  // 替换为你的数据库凭据

// 修改 onMessage 方法以将消息存储在数据库中
public function onMessage(ConnectionInterface $from, $msg) {
    $username = 'user'; // 你可以从 token 或其他来源检索用户名
    $stmt = $pdo->prepare("INSERT INTO messages (username, message) VALUES (:username, :message)");
    $stmt->bindParam(':username', $username);
    $stmt->bindParam(':message', $msg);
    $stmt->execute();

    // 将消息广播给所有客户端
    foreach ($this->clients as $client) {
        if ($from !== $client) {
            $client->send($msg);
        }
    }
}

步骤 3:检索消息

为了在用户连接时显示之前的消息,我们可以在打开新连接时查询数据库,并将过去的消息发送给客户端。

public function onOpen(ConnectionInterface $conn) {
    // 检索最近 10 条消息
    $stmt = $pdo->query("SELECT username, message FROM messages ORDER BY timestamp DESC LIMIT 10");
    $messages = $stmt->fetchAll(PDO::FETCH_ASSOC);

    // 将消息发送给新客户端
    foreach ($messages as $message) {
        $conn->send($message['username'] . ": " . $message['message']);
    }

    $this->clients->attach($conn);
    echo "New connection: " . $conn->resourceId . "\n";
}

可扩展性:使用 Redis Pub/Sub 扩展应用

随着实时聊天应用的增长和更多用户的加入,单个 WebSocket 服务器可能不够用。为了扩展应用,我们可以使用 Redis Pub/Sub(发布/订阅)来管理多个 WebSocket 服务器之间的消息分发。

步骤 1:安装 Redis 和 PHP 的 Redis 客户端

你需要在服务器上安装 Redis,以及 Redis PHP 客户端:

composer require predis/predis

步骤 2:在 WebSocket 服务器中实现 Redis Pub/Sub

在分布式系统中,你可以在不同机器上运行多个 WebSocket 服务器。Redis 将充当消息代理,允许服务器向所有连接的客户端广播消息。

修改 WebSocket 服务器以发布和订阅 Redis 频道:

// 添加 Redis 配置
$redis = new Predis\Client();

// 订阅 Redis 频道
$redis->connect();
$redis->subscribe(['chat_channel'], function ($message) {
    // 将来自 Redis 的消息广播给所有连接的客户端
    foreach ($this->clients as $client) {
        $client->send($message);
    }
});

// 收到新消息时将消息发布到 Redis 频道
public function onMessage(ConnectionInterface $from, $msg) {
    // 将消息发布到 Redis
    $redis->publish('chat_channel', $msg);
}

总结:整合所有内容

恭喜!你已经学会了如何使用 PHP 和 WebSocket 构建一个功能完整的实时聊天应用。你还学会了如何实现用户身份验证、在数据库中存储聊天消息,以及使用 Redis Pub/Sub 扩展应用。

构建实时应用既具有挑战性又令人满足,通过本文介绍的概念,你已经具备了进一步推进项目的能力。无论你是添加私信、媒体分享还是视频通话等功能,可能性都是无限的。

借助 PHP 和 WebSocket,你拥有了创建可扩展、实时应用的基础,这些应用可以提供无缝的用户体验。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值