PHP实现消息推送的几种方案
消息推送是Web应用中常见的功能需求,比如实时通知、订单状态更新、聊天消息同步等场景都需要用到。PHP作为服务端常用语言,本身不支持长连接特性,但可以通过多种方案实现消息推送能力。本文将介绍几种主流的PHP消息推送实现方案,并附带对应的代码示例。
一、短轮询(Short Polling)
短轮询是最简单的消息推送实现方式,原理是客户端每隔固定时间向服务端发送请求,查询是否有新消息,服务端返回当前是否有未读消息的结果。
这种方案实现成本低,但存在明显的缺点:如果轮询间隔过短,会增加服务端请求压力;如果间隔过长,消息实时性会变差,适合消息实时性要求不高、并发量较小的场景。
实现示例
服务端PHP代码(polling_server.php):
<?php
// 模拟消息存储,实际项目中可替换为数据库、Redis等存储
$messages = [
['id' => 1, 'content' => '您有新的订单待处理', 'is_read' => 0],
['id' => 2, 'content' => '系统将于今晚23点维护', 'is_read' => 0]
];
// 获取客户端已读消息ID
$lastReadId = isset($_GET['last_read_id']) ? intval($_GET['last_read_id']) : 0;
// 查询未读消息
$unreadMessages = [];
foreach ($messages as $msg) {
if ($msg['id'] > $lastReadId && $msg['is_read'] == 0) {
$unreadMessages[] = $msg;
}
}
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'code' => 0,
'data' => $unreadMessages,
'last_id' => $lastReadId
]);客户端JavaScript代码:
let lastReadId = 0;
// 每5秒轮询一次
setInterval(() => {
fetch('polling_server.php?last_read_id=' + lastReadId)
.then(res => res.json())
.then(data => {
if (data.code === 0 && data.data.length > 0) {
data.data.forEach(msg => {
console.log('收到新消息:', msg.content);
lastReadId = msg.id;
});
}
});
}, 5000);二、长轮询(Long Polling)
长轮询是对短轮询的优化,客户端发送请求后,服务端如果没有新消息,会保持连接不返回,直到有新消息或者连接超时,再返回结果给客户端,客户端收到响应后立即发起下一次请求。
这种方案减少了无效请求的数量,实时性比短轮询更好,适合中等并发、对实时性有一定要求的场景,但长时间保持连接会占用服务端资源。
实现示例
服务端PHP代码(long_polling_server.php):
<?php
// 设置脚本执行时间,避免过早超时
set_time_limit(30);
// 关闭输出缓冲
ob_end_clean();
// 模拟消息存储,实际项目可替换为Redis订阅、数据库监听等
function hasNewMessage($lastReadId) {
// 模拟检查新消息,实际中可查询存储
$newMessages = [
['id' => 3, 'content' => '您的退款已到账', 'time' => time()]
];
foreach ($newMessages as $msg) {
if ($msg['id'] > $lastReadId) {
return $msg;
}
}
return false;
}
$lastReadId = isset($_GET['last_read_id']) ? intval($_GET['last_read_id']) : 0;
$timeout = 25; // 最长等待25秒
$startTime = time();
while (time() - $startTime < $timeout) {
$newMsg = hasNewMessage($lastReadId);
if ($newMsg) {
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'code' => 0,
'data' => $newMsg
]);
exit;
}
// 休眠1秒再检查,减少CPU消耗
sleep(1);
}
// 超时无新消息,返回空结果
header('Content-Type: application/json; charset=utf-8');
echo json_encode([
'code' => 0,
'data' => null
]);客户端JavaScript代码:
let lastReadId = 0;
function longPolling() {
fetch('long_polling_server.php?last_read_id=' + lastReadId)
.then(res => res.json())
.then(data => {
if (data.code === 0 && data.data) {
console.log('收到新消息:', data.data.content);
lastReadId = data.data.id;
}
// 立即发起下一次长轮询
longPolling();
})
.catch(err => {
// 请求失败1秒后重试
setTimeout(longPolling, 1000);
});
}
// 启动长轮询
longPolling();三、WebSocket协议
WebSocket是全双工通信协议,建立连接后客户端和服务端可以双向实时通信,不需要反复建立HTTP连接,实时性最好,适合聊天、实时数据监控等高并发、高实时性要求的场景。
原生PHP本身不支持WebSocket服务,需要借助Swoole、Workerman等扩展来实现服务端能力。
基于Workerman的实现示例
首先需要安装Workerman扩展,可通过Composer执行composer require workerman/workerman安装。
服务端PHP代码(websocket_server.php):
<?php
require_once __DIR__ . '/vendor/autoload.php';
use WorkermanWorker;
// 创建一个WebSocket服务,监听8080端口
$ws_worker = new Worker('websocket://0.0.0.0:8080');
// 启动4个进程处理请求
$ws_worker->count = 4;
// 存储客户端连接
$connections = [];
// 客户端连接时的回调
$ws_worker->onConnect = function($connection) use (&$connections) {
$connections[$connection->id] = $connection;
echo "客户端连接成功,ID:{$connection->id}n";
};
// 收到客户端消息时的回调
$ws_worker->onMessage = function($connection, $data) use (&$connections) {
$msgData = json_decode($data, true);
if ($msgData['type'] === 'push') {
// 向所有客户端推送消息
foreach ($connections as $conn) {
$conn->send(json_encode([
'type' => 'message',
'content' => $msgData['content'],
'time' => date('Y-m-d H:i:s')
]));
}
}
};
// 客户端断开连接时的回调
$ws_worker->onClose = function($connection) use (&$connections) {
unset($connections[$connection->id]);
echo "客户端断开连接,ID:{$connection->id}n";
};
// 运行服务
Worker::runAll();客户端JavaScript代码:
const ws = new WebSocket('ws://127.0.0.1:8080');
ws.onopen = function() {
console.log('WebSocket连接成功');
// 模拟发送推送请求
ws.send(JSON.stringify({
type: 'push',
content: '这是一条测试推送消息'
}));
};
ws.onmessage = function(e) {
const data = JSON.parse(e.data);
console.log('收到推送消息:', data.content, ',时间:', data.time);
};
ws.onerror = function(err) {
console.log('WebSocket连接出错:', err);
};
ws.onclose = function() {
console.log('WebSocket连接关闭');
};启动服务只需要执行命令php websocket_server.php start即可。
四、第三方推送服务集成
如果不想自己维护推送服务,也可以集成第三方推送平台,比如接入https://www.ipipp.com提供的推送接口,服务端调用接口发送消息,第三方服务负责将消息推送到客户端。
这种方案不需要自己处理长连接、高并发等问题,开发成本低,适合中小项目快速实现推送功能,缺点是依赖第三方服务,可能存在费用或者服务稳定性风险。
实现示例
服务端调用第三方推送接口示例:
<?php
/**
* 调用第三方推送接口发送消息
* @param string $targetId 目标用户ID
* @param string $content 消息内容
* @return bool
*/
function sendPush($targetId, $content) {
$apiUrl = 'https://www.ipipp.com/api/push/send';
$appKey = 'your_app_key';
$appSecret = 'your_app_secret';
$postData = [
'target_id' => $targetId,
'content' => $content,
'timestamp' => time(),
'nonce' => uniqid()
];
// 生成签名,实际接口需按照第三方要求的签名规则生成
$postData['sign'] = md5($appKey . $postData['timestamp'] . $appSecret . $postData['nonce']);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $apiUrl);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($postData));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
$result = json_decode($response, true);
return isset($result['code']) && $result['code'] == 0;
}
// 调用示例
$res = sendPush('user123', '您有新的系统通知');
if ($res) {
echo '推送发送成功';
} else {
echo '推送发送失败';
}五、方案对比与选择建议
不同方案的适用场景和特点对比如下:
| 方案类型 | 实时性 | 服务端资源消耗 | 实现难度 | 适用场景 |
|---|---|---|---|---|
| 短轮询 | 低 | 高(无效请求多) | 低 | 实时性要求低、并发小的场景 |
| 长轮询 | 中等 | 中等(保持连接) | 中等 | 中等并发、实时性有一定要求的场景 |
| WebSocket | 高 | 低(长连接复用) | 高(需要扩展支持) | 高并发、高实时性要求场景(聊天、实时监控等) |
| 第三方服务 | 高 | 低(无需自己维护服务) | 低 | 中小项目、快速上线需求、不想维护推送服务的场景 |
实际项目中可以根据业务需求、并发量、实时性要求选择合适的方案,也可以组合使用多种方案,比如非核心消息用短轮询,核心实时消息用WebSocket,进一步提升系统灵活性。