由于 PHP 本身的特性(通常是同步、阻塞的请求-响应模型),它不适合像 Node.js 那样直接维持一个长连接来实时推送消息,PHP 在消息推送系统中通常扮演“触发者”或“任务分发者”的角色,而不是“实时服务端”。

下面我将从易到难,介绍几种主流的实现方式,并给出详细的代码示例。
核心思想
无论采用哪种方案,PHP 的核心任务通常是:
- 识别推送事件:用户A给用户B发了一条新消息。
- 准备推送数据:将消息内容、接收者ID等信息打包成数据。
- 通知推送服务:调用一个外部服务(如 WebSocket 服务器、第三方推送平台)来执行真正的推送。
- 立即返回响应:让用户的浏览器或客户端可以继续执行其他操作,而不是等待消息被推送成功。
AJAX 轮询
这是最简单、最直接的方式,但也是效率最低的方式。
原理: 浏览器每隔几秒就向服务器发送一个 AJAX 请求,询问“我有什么新消息吗?”,服务器检查后,如果有新消息就返回,没有就返回空。

优点:
- 实现简单,无需额外的服务器技术。
- 兼容性好,所有浏览器都支持。
缺点:
- 效率极低:会产生大量无效的 HTTP 请求,浪费服务器资源和带宽。
- 实时性差:消息推送的延迟取决于轮询间隔,无法做到真正的实时。
PHP 实现(后端):
假设我们有一个数据库表 messages,存储用户的消息。

// check_new_messages.php
header('Content-Type: application/json');
// 1. 获取当前用户ID (通常从Session中获取)
$userId = $_SESSION['user_id'] ?? null;
if (!$userId) {
echo json_encode(['has_new' => false]);
exit;
}
// 2. 查询该用户是否有未读消息
// 假设有一个 is_read = 0 的字段标记未读
require_once 'db.php'; // 包含数据库连接
$stmt = $pdo->prepare("SELECT COUNT(*) FROM messages WHERE receiver_id = ? AND is_read = 0");
$stmt->execute([$userId]);
$newMessageCount = $stmt->fetchColumn();
// 3. 返回结果
echo json_encode([
'has_new' => $newMessageCount > 0,
'count' => $newMessageCount
]);
前端实现:
// 使用 setInterval 定时轮询
setInterval(function() {
fetch('check_new_messages.php')
.then(response => response.json())
.then(data => {
if (data.has_new) {
// 显示一个红点或提示音
console.log(`您有 ${data.count} 条新消息!`);
// 这里可以调用一个函数来更新UI
updateNotificationUI(data.count);
}
})
.catch(error => console.error('Error:', error));
}, 5000); // 每5秒检查一次
Server-Sent Events (SSE)
SSE 是 HTML5 标准的一部分,它允许服务器向客户端单向推送数据,它基于 HTTP,比轮询高效得多。
原理: 客户端发起一个长连接请求,服务器保持这个连接打开,当有新消息时,服务器通过这个连接向客户端发送数据,连接断开后,客户端可以自动重连。
优点:
- 高效:只有有新数据时才发送请求,减少了不必要的网络流量。
- 真正半实时:服务器可以主动推送。
- 简单:基于标准 HTTP,实现比 WebSocket 简单。
缺点:
- 单向通信:只能服务器推送到客户端,客户端无法主动发送数据(除非再开一个 AJAX 连接)。
- 默认不支持断线重连(但前端 JS 可以轻松实现)。
PHP 实现(后端):
这里需要 PHP 脚本能长时间运行,为了演示,我们简化处理,实际项目中可能需要队列(如 Redis)来存储消息,由一个常驻的 PHP 进程(如 Swoole)来处理推送。
// push_sse.php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
header('Connection: keep-alive');
// 模拟数据库连接
require_once 'db.php';
// 模拟一个接收者ID
$receiverId = 123; // 假设是用户123
echo "data: Connected for user {$receiverId}\n\n";
ob_flush();
flush();
// 无限循环,模拟服务器持续监听
while (true) {
// 1. 查询该用户是否有新消息
$stmt = $pdo->prepare("SELECT * FROM messages WHERE receiver_id = ? AND is_pushed = 0 ORDER BY created_at ASC LIMIT 1");
$stmt->execute([$receiverId]);
$message = $stmt->fetch(PDO::FETCH_ASSOC);
if ($message) {
// 2. 如果有新消息,格式化为SSE格式并发送
$data = json_encode([
'id' => $message['id'],
'content' => $message['content'],
'sender_name' => 'John Doe'
]);
echo "data: {$data}\n\n"; // 数据前缀必须是 "data: "
ob_flush(); // 输出缓冲区
flush(); // 将缓冲内容发送到客户端
// 3. 标记消息为已推送,避免重复推送
$updateStmt = $pdo->prepare("UPDATE messages SET is_pushed = 1 WHERE id = ?");
$updateStmt->execute([$message['id']]);
}
// 暂停一下,避免CPU占用过高
sleep(2);
}
前端实现:
// 创建 EventSource 对象
const eventSource = new EventSource('push_sse.php');
// 监听消息事件
eventSource.onmessage = function(event) {
// event.data 包含服务器发送的数据
const message = JSON.parse(event.data);
console.log('New message:', message);
// 更新UI
displayNewMessage(message);
};
// 监听连接打开事件
eventSource.onopen = function(event) {
console.log('SSE connection opened.');
};
// 监听错误事件
eventSource.onerror = function(event) {
console.error('SSE connection error:', event);
// 连接断开后可以在这里实现重连逻辑
};
WebSocket (推荐)
WebSocket 是目前最主流、功能最强大的实时通信方案,它在 TCP 之上建立了一个全双工的通信通道,允许服务器和客户端之间进行实时、双向的数据传输。
PHP 在 WebSocket 中的角色: PHP 本身不能直接处理 WebSocket 连接(因为连接是长连接,而 PHP-FPM 是为短请求设计的),我们通常使用一个常驻内存的 PHP 扩展来作为 WebSocket 服务器,而普通的 PHP 脚本(如网站业务逻辑)则作为客户端去触发这个 WebSocket 服务器。
常用工具:
- Swoole: 一个强大的 PHP 协程框架,内置了高性能的 WebSocket 服务器。
- Ratchet: 一个基于 PHP 的 WebSocket 库,但性能不如 Swoole。
- Workerman: 另一个流行的 PHP Socket 服务器框架。
下面我们以 Swoole 为例,展示一个完整的流程。
步骤 1:安装 Swoole
pecl install swoole
并在 php.ini 中启用 extension=swoole。
步骤 2:创建 WebSocket 服务器 (server.php)
这个脚本将一直运行在服务器上,监听 WebSocket 连接。
<?php
// server.php
$server = new Swoole\WebSocket\Server("0.0.0.0", 9501);
// 存储所有在线的客户端连接
$connections = [];
// 当有新的WebSocket连接进入时
$server->on('open', function (Swoole\WebSocket\Server $server, $request) use (&$connections) {
echo "Client-{$request->fd} connected.\n";
// 将新连接存储起来
$connections[$request->fd] = $request->fd;
});
// 当收到客户端消息时
$server->on('message', function (Swoole\WebSocket\Server $server, $frame) {
echo "Received from {$frame->fd}: {$frame->data}\n";
// 这里可以处理业务逻辑,比如将消息存入数据库
// $messageData = json_decode($frame->data, true);
// ...
// 广播消息给所有其他客户端
foreach ($server->connections as $fd) {
if ($fd != $frame->fd) {
$server->push($fd, "User {$frame->fd} said: {$frame->data}");
}
}
});
// 当连接关闭时
$server->on('close', function ($ser, $fd) use (&$connections) {
echo "Client-{$fd} closed.\n";
unset($connections[$fd]);
});
// 启动服务器
$server->start();
步骤 3:用普通 PHP 脚本触发推送 (trigger_push.php)
这是网站业务逻辑所在的地方,当用户A发送消息时,这个脚本会被调用。
<?php
// trigger_push.php
// 假设这是用户A发送消息给用户B的业务逻辑
// 1. 获取要推送的数据
$receiverFd = 123; // 假设我们通过某种方式(如Redis哈希表)知道用户B的连接ID是123
$messageContent = 'Hello from PHP!';
// 2. 创建一个WebSocket客户端,连接到我们的服务器
$client = new Swoole\WebSocket\Client('ws://127.0.0.1', 9501);
if ($client->connect()) {
// 3. 发送消息给指定的接收者
// 注意:一个更健壮的系统会通过一个中间件(如Redis Pub/Sub)来广播,
// 而不是直接连接服务器,这里为了演示简化了。
$data = json_encode([
'type' => 'private_message',
'to_fd' => $receiverFd,
'content' => $messageContent
]);
$client->push($data);
echo "Pushed message to client {$receiverFd}\n";
} else {
echo "Failed to connect to WebSocket server.\n";
}
$client->close();
步骤 4:前端实现
前端使用 JavaScript 的 WebSocket API。
// 连接到 WebSocket 服务器
const socket = new WebSocket('ws://your-server-ip:9501');
// 连接打开时
socket.onopen = function(event) {
console.log('WebSocket connection established.');
// 可以在这里发送认证信息,比如用户ID
// socket.send(JSON.stringify({action: 'auth', user_id: 123}));
};
// 接收消息
socket.onmessage = function(event) {
const data = JSON.parse(event.data);
console.log('Message from server:', data);
// 根据消息类型更新UI
if (data.type === 'private_message') {
displayNewMessage(data.content);
}
};
// 连接关闭时
socket.onclose = function(event) {
console.log('WebSocket connection closed.');
};
// 连接出错时
socket.onerror = function(error) {
console.error('WebSocket Error:', error);
};
使用第三方推送服务
如果不想自己搭建和维护 WebSocket 服务器,可以使用成熟的第三方推送服务,如 极光推送、个推、Firebase Cloud Messaging (FCM) 等。
原理:
- 你的 PHP 后端调用第三方服务的 API。
- 第三服务负责将消息推送到用户的手机 App(通过 APNS/FCM)或网页(通过它们自己的 SDK 和通道)。
- 你的网站和 App 集成第三方服务的 SDK 来接收消息。
优点:
- 省心省力:无需关心底层推送技术,只需调用 API。
- 稳定可靠:服务提供商有强大的基础设施保障。
- 覆盖面广:可以轻松实现跨平台推送(Web, iOS, Android)。
PHP 实现(以极光推送为例):
你需要安装极光推送的 PHP SDK:composer require jpush/jpush
<?php
// trigger_jpush.php
require 'vendor/autoload.php';
use JPush\Client as JPush;
// 初始化
$appKey = 'your_app_key';
$masterSecret = 'your_master_secret';
$client = new JPush($appKey, $masterSecret);
// 推送目标(根据用户别名推送)
$pusher = $client->push()
->setPlatform('all') // 推送平台
->addAlias('user_alias_123') // 推送给别名为 user_alias_123 的所有设备
->setNotificationAlert('Hello from JPush!') // 通知内容
->addAndroidNotification('Hello from JPush!', 'notification_title', '1', ['key' => 'value'])
->addIosNotification('Hello from JPush!', 'notification_title', '1', true, 'sound.caf', ['key' => 'value']);
try {
$result = $pusher->send();
print_r($result);
} catch (\JPush\Exceptions\APIRequestException $e) {
// 错误处理
print_r($e);
}
总结与选择建议
| 方案 | 实时性 | 双向通信 | 实现复杂度 | 推荐场景 |
|---|---|---|---|---|
| AJAX 轮询 | 低 | 不支持 | 非常低 | 简单、低频的通知,如新邮件提示(不推荐用于聊天) |
| SSE | 高 | 单向(服务端->客户端) | 低 | 服务器到客户端的单向实时更新,如新闻流、日志监控 |
| WebSocket | 非常高 | 双向 | 高 | 对实时性要求高的应用,如在线聊天、实时协作、游戏 |
| 第三方服务 | 高 | 取决于服务 | 中 | 跨平台推送(Web, iOS, Android),如 App 内推送、营销短信 |
如何选择?
- 如果你的应用只是需要一些简单的、不频繁的通知,可以考虑 SSE,它比轮询高效得多,实现也相对简单。
- 如果你的应用是实时的、双向交互的,比如聊天室、在线游戏、实时协作工具,WebSocket (Swoole) 是不二之选,它是目前最强大、最灵活的方案。
- 如果你的主要目标是向移动 App 用户推送消息,或者不想自己维护服务器,那么使用 第三方推送服务 是最明智、最经济的选择。
