本文目录导读:

- 拉取模式(Polling)- 最简单
- 长轮询(Long Polling)- 折中方案
- WebSocket - 推荐方案
- Server-Sent Events (SSE) - 适合单向推送
- 完整企业级方案(推荐)
- 推送平台/二要素认证
- 实际项目推荐选择
- 性能优化建议
在PHP中实现消息通知系统,有多种方案,从简单到复杂,我为你整理了几种主流实现方式:
拉取模式(Polling)- 最简单
原理
前端定时向后端发送请求,检查是否有新消息。
实现示例
// client.js
setInterval(async () => {
const response = await fetch('/api/notifications/check');
const data = await response.json();
if (data.hasNew) {
// 处理新消息
showNotification(data.messages);
}
}, 3000); // 每3秒检查一次
// server.php
<?php
// /api/notifications/check
$lastId = $_GET['last_id'] ?? 0;
$notifications = getNotificationsSince($lastId);
echo json_encode(['hasNew' => !empty($notifications), 'messages' => $notifications]);
✅ 优点:实现简单,兼容性好
❌ 缺点:实时性差,请求频繁浪费资源
长轮询(Long Polling)- 折中方案
原理
客户端发出请求后,服务器保持连接直到有新消息或超时才返回。
<?php
// long-polling.php
set_time_limit(30); // 设置30秒超时
$lastId = $_GET['last_id'] ?? 0;
$startTime = time();
while (time() - $startTime < 25) { // 25秒内循环检查
$newMessages = getMessagesSince($lastId);
if (!empty($newMessages)) {
echo json_encode(['messages' => $newMessages, 'last_id' => $newMessages[count($newMessages)-1]['id']]);
exit;
}
usleep(500000); // 休息0.5秒再查
}
echo json_encode(['messages' => [], 'last_id' => $lastId]);
✅ 优点:实时性较好,比轮询节省资源
❌ 缺点:服务器资源占用较高,不适合海量连接
WebSocket - 推荐方案
原理
全双工通信,客户端和服务器都可以随时发送消息。
使用 Ratchet(PHP WebSocket库)
// composer.json
{
"require": {
"cboden/ratchet": "^0.4.4"
}
}
// WebSocket服务器
<?php
use Ratchet\MessageComponentInterface;
use Ratchet\ConnectionInterface;
class NotificationServer implements MessageComponentInterface {
protected $clients;
public function __construct() {
$this->clients = new \SplObjectStorage;
}
public function onOpen(ConnectionInterface $conn) {
$this->clients->attach($conn);
echo "新连接: {$conn->resourceId}\n";
}
public function onMessage(ConnectionInterface $from, $msg) {
// 接收用户ID进行绑定
$data = json_decode($msg, true);
$from->userId = $data['user_id'] ?? null;
}
public function onClose(ConnectionInterface $conn) {
$this->clients->detach($conn);
}
public function onError(ConnectionInterface $conn, \Exception $e) {
$conn->close();
}
// 发送通知给特定用户
public function sendNotification($userId, $message) {
foreach ($this->clients as $client) {
if ($client->userId === $userId) {
$client->send(json_encode(['type' => 'notification', 'message' => $message]));
}
}
}
}
// 启动服务器
$server = \Ratchet\Server\IoServer::factory(
new \Ratchet\Http\HttpServer(
new \Ratchet\WebSocket\WsServer(
new NotificationServer()
)
),
8080
);
$server->run();
客户端代码
// client.js
const ws = new WebSocket('ws://localhost:8080');
ws.onopen = () => {
// 发送用户ID进行绑定
ws.send(JSON.stringify({ user_id: user_id }));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'notification') {
alert(data.message.message);
updateNotificationCount();
}
};
✅ 优点:实时性最好,双向通信
❌ 缺点:需要保持长连接,服务器配置相对复杂
Server-Sent Events (SSE) - 适合单向推送
原理
服务器向浏览器单向推送数据,基于HTTP。
<?php
// sse.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// 必须设置
@ini_set('implicit_flush', 1);
@ob_end_clean();
$lastEventId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? 0;
while (true) {
$messages = getMessagesSince($lastEventId);
if (!empty($messages)) {
foreach ($messages as $msg) {
echo "id: {$msg['id']}\n";
echo "data: " . json_encode($msg) . "\n\n";
}
ob_flush();
flush();
}
// 保持连接,发送心跳
echo ": heartbeat\n\n";
// flush();
sleep(1);
}
客户端
// client.js
var source = new EventSource('/sse.php');
source.onmessage = function(event) {
var data = JSON.parse(event.data);
console.log('收到通知:', data);
// 处理通知...
};
✅ 优点:实时性好,自动重连,使用简单
❌ 缺点:只能服务端向客户端单向推送
完整企业级方案(推荐)
结合数据库、Redis队列和WebSocket:
[业务触发] → [写入DB] → [发布到Redis] → [WebSocket服务器消费] → [推送客户端]
<?php
// 1. 业务端:发送通知
function sendNotification($userId, $type, $content) {
// 保存到数据库
$notification = [
'user_id' => $userId,
'type' => $type,
'content' => $content,
'created_at' => date('Y-m-d H:i:s')
];
$db->insert('notifications', $notification);
// 发布到Redis队列
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->lPush('notification_queue', json_encode($notification));
// 通知WebSocket服务器有新消息
$redis->publish('notification_channel', json_encode($notification));
}
// 2. WebSocket服务器
// 在Ratchet中订阅Redis并推送到客户端
public function onOpen(ConnectionInterface $conn) {
// 连接Redis并订阅通知
$this->redis = new \Redis();
$this->redis->connect('127.0.0.1', 6379);
$this->redis->subscribe(['notification_channel'], [$this, 'handleRedisMessage']);
}
public function handleRedisMessage($redis, $channel, $message) {
$data = json_decode($message, true);
$this->sendNotification($data['user_id'], $data);
}
推送平台/二要素认证
服务商集成
- 极光推送(JPush) - 支持Web/App推送
- 个推(Getui) - 偏移动端
- 阿里云推送 - 支持Uni/Push
// 示例:使用极光推送
// composer require jpush/jpush
use JPush\Client as JPushClient;
function sendPush($userId, $content) {
$client = new \JPush\Client('your_app_key', 'your_master_secret');
$push = $client->push()
->setPlatform('all')
->addRegistrationId($userId)
->setNotificationAlert($content);
try {
return $push->send();
} catch (\Exception $e) {
error_log('推送失败: ' . $e->getMessage());
}
}
实际项目推荐选择
| 场景 | 推荐方案 |
|---|---|
| 简单后台管理 | 轮询,5秒一次 |
| 中小型应用 | 长轮询 或 SSE |
| 实时聊天/多人协作 | WebSocket (Ratchet) |
| 移动端推送 | 极光/个推/阿里推送 |
| 大型应用 | Redis + WebSocket + 消息队列 |
性能优化建议
// 1. 使用Redis缓存已读状态 // 2. 消息只存有变更的,减少数据库查询 // 3. 错误处理要考虑网络异常 // 4. 使用Pulsar/Kafka等MQ异步处理大批量消息 // 5. 监控WebSocket连接数,设置最大连接数限制