使用 PHP 和 WebSocket 构建实时聊天应用完整指南 第二部分
用户认证机制设计:通过 Session 或 Token 实现用户身份识别与权限控制。
消息持久化方案:结合 MySQL 存储聊天记录,确保数据不丢失。
多房间支持架构:实现用户加入/离开房间、房间内消息广播等功能。
客户端优化建议:包括心跳检测、断线重连、消息去重等稳定性增强措施。
部署与性能调优:介绍如何在生产环境运行 WebSocket 服务及性能监控建议。
一、用户认证机制设计
1.1 基于 Session 的认证方式
在传统的 Web 应用中,PHP 使用 Session 来管理用户登录状态。为了使 WebSocket 连接也能识别用户身份,我们需要让客户端在连接时传递 Session ID。
实现步骤:
客户端在建立 WebSocket 连接时,将当前用户的 Session ID 作为查询参数附加到 URL 上;
服务端解析该 Session ID 并恢复对应的 Session 数据;
验证用户是否已登录,若通过则允许连接并绑定用户信息。
php
Copy Code
// 示例:客户端连接地址
ws://localhost:8080?session_id=abc123xyz
服务端可通过以下方式获取和验证 Session:
php
Copy Code
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class ChatServer implements MessageComponentInterface {
protected $clients;
protected $users;
public function __construct() {
$this->clients = new \SplObjectStorage;
$this->users = [];
}
public function onOpen(ConnectionInterface $conn) {
$sessionId = $conn->WebSocket->request->getQuery()['session_id'] ?? null;
if (!$sessionId) {
$conn->close();
return;
}
session_id($sessionId);
session_start();
if (!isset($_SESSION['user_id'])) {
$conn->close();
return;
}
$userId = $_SESSION['user_id'];
$this->clients->attach($conn);
$this->users[$conn->resourceId] = $userId;
echo "User {$userId} connected\n";
}
// 其他方法略...
}
1.2 基于 Token 的认证方式(推荐)
对于前后端分离或移动端场景,使用 JWT(JSON Web Token)更为常见。客户端在建立连接时携带 Token,服务端验证其有效性后授权连接。
php
Copy Code
// 客户端连接示例
ws://localhost:8080?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
服务端可通过 firebase/php-jwt 解析 Token:
bash
Copy Code
composer require firebase/php-jwt
php
Copy Code
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
public function onOpen(ConnectionInterface $conn) {
$token = $conn->WebSocket->request->getQuery()['token'] ?? null;
if (!$token) {
$conn->close();
return;
}
try {
$decoded = JWT::decode($token, new Key('your_secret_key', 'HS256'));
$userId = $decoded->user_id;
$this->clients->attach($conn);
$this->users[$conn->resourceId] = $userId;
echo "User {$userId} connected\n";
} catch (\Exception $e) {
$conn->close();
}
}
二、消息持久化方案
为了保留聊天记录以便后续查看和分析,需要将消息存储到数据库中。推荐使用 MySQL,配合 PDO 操作。
2.1 创建消息表结构
sql
Copy Code
CREATE TABLE messages (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
room_id VARCHAR(50),
content TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
2.2 消息入库逻辑
当收到新消息时,将其插入数据库并广播给房间内的所有用户。
php
Copy Code
public function onMessage(ConnectionInterface $from, $msg) {
$data = json_decode($msg, true);
$userId = $this->users[$from->resourceId];
$roomId = $data['room'] ?? 'default';
$content = $data['message'];
// 插入数据库
$pdo = new PDO("mysql:host=localhost;dbname=chat_app", "root", "");
$stmt = $pdo->prepare("INSERT INTO messages (user_id, room_id, content) VALUES (?, ?, ?)");
$stmt->execute([$userId, $roomId, $content]);
// 广播消息
foreach ($this->clients as $client) {
if ($client !== $from) {
$client->send(json_encode([
'type' => 'message',
'user_id' => $userId,
'room' => $roomId,
'content' => $content,
'timestamp' => date('Y-m-d H:i:s')
]));
}
}
}
三、多房间支持架构
3.1 房间管理机制
每个用户可以加入多个房间,服务端需维护每个房间的连接列表。
php
Copy Code
protected $rooms = []; // ['room1' => [$conn1, $conn2], ...]
3.2 加入房间操作
php
Copy Code
public function onMessage(ConnectionInterface $from, $msg) {
$data = json_decode($msg, true);
if ($data['type'] === 'join_room') {
$roomId = $data['room'];
if (!isset($this->rooms[$roomId])) {
$this->rooms[$roomId] = [];
}
$this->rooms[$roomId][] = $from;
echo "User joined room: {$roomId}\n";
}
}
3.3 房间内消息广播
php
Copy Code
foreach ($this->rooms[$roomId] as $client) {
$client->send(json_encode([
'type' => 'message',
'user_id' => $userId,
'content' => $content
]));
}
四、客户端优化建议
4.1 心跳检测与断线重连
为防止连接异常中断,可设置心跳包机制:
javascript
Copy Code
let ws = new WebSocket("ws://localhost:8080?token=xxx");
setInterval(() => {
ws.send(JSON.stringify({ type: 'ping' }));
}, 30000);
4.2 消息去重与排序
客户端应缓存已接收的消息 ID,避免重复渲染;同时按时间戳排序展示消息。
4.3 异常处理与提示
对网络异常、认证失败等情况提供友好提示,如“连接已断开,请检查网络”。
五、部署与性能调优
5.1 使用 Supervisor 管理进程
安装并配置 supervisor 来守护 WebSocket 服务:
ini
Copy Code
[program:websocket]
command=php /var/www/html/server.php
autostart=true
autorestart=true
stderr_logfile=/var/log/websocket.err.log
stdout_logfile=/var/log/websocket.out.log
5.2 Nginx 反向代理配置
nginx
Copy Code
location /ws {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
5.3 性能监控建议
使用 htop、netstat 监控连接数,结合日志分析工具排查瓶颈。
六、结语
本文作为《PHP + WebSocket 实时聊天应用开发指南》系列的第二部分,详细介绍了用户认证、消息持久化、多房间支持等核心功能的实现方法,并提供了客户端优化建议及部署方案。通过本系列的学习,开发者可以构建出一个功能完备、稳定可靠的实时聊天系统。
下一篇文章将聚焦于前端界面设计与交互优化,敬请期待!
