使用 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。要安装它,打开终端并运行以下命令:

bash 复制代码
composer require cboden/ratchet

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

构建 WebSocket 服务器:PHP 和 Ratchet

步骤 1:创建 WebSocket 服务器

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

1. 创建 WebSocket 服务器脚本

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

php 复制代码
<?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 复制代码
<?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();

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

bash 复制代码
php server.php

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

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

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

步骤 2:创建前端

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

html 复制代码
<!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 安装:

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

登录脚本(login.php):

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

php 复制代码
<?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 验证用户。

php 复制代码
// 在 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 作为查询参数包含进来。

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

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

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

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

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

sql 复制代码
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 数据库交互。

php 复制代码
// 在 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:检索消息

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

php 复制代码
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 客户端:

bash 复制代码
composer require predis/predis

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

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

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

php 复制代码
// 添加 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,你拥有了创建可扩展、实时应用的基础,这些应用可以提供无缝的用户体验。

相关推荐
JaguarJack2 小时前
使用 PHP 和 WebSocket 构建实时聊天应用 完整指南
后端·php
LCG米2 小时前
基于LoRa的远距离低功耗农业传感器网络设计与实现(SX1278+STM32L071)
网络·stm32·php
CodeSheep2 小时前
中国四大软件外包公司
前端·后端·程序员
千寻技术帮2 小时前
10370_基于Springboot的校园志愿者管理系统
java·spring boot·后端·毕业设计
风象南2 小时前
Spring Boot 中统一同步与异步执行模型
后端
聆风吟º2 小时前
【Spring Boot 报错已解决】彻底解决 “Main method not found in class com.xxx.Application” 报错
java·spring boot·后端
乐茵lin3 小时前
golang中 Context的四大用法
开发语言·后端·学习·golang·编程·大学生·context
步步为营DotNet3 小时前
深度探索ASP.NET Core中间件的错误处理机制:保障应用程序稳健运行
后端·中间件·asp.net
ai_xiaogui3 小时前
Debian系统PVE虚拟机安装详解:ISO镜像上传+硬件配置+图形化安装指南
运维·debian·php·panelai兼容测试·图形化安装指南·iso镜像上传配置·debian pve虚拟机安装