PHP长连接推送怎么做

wen PHP项目 2

PHP长连接推送实战指南:从WebSocket到SSE的架构选型与代码实现


目录导读

  1. 为什么PHP需要长连接推送? —— 从HTTP无状态到实时交互的痛点
  2. 长连接技术全景图 —— WebSocket / SSE / 轮询 / 长轮询 的优劣对比
  3. 原生WebSocket实现(Workerman+Swoole) —— 性能之王
  4. SSE(Server-Sent Events) —— 轻量级单向推送
  5. Redis Pub/Sub + 前端轮询 —— 传统架构的优雅转型
  6. 核心代码示例 —— 手写可靠的心跳检测与断线重连
  7. 高频问答 —— 解决并发瓶颈、Nginx配置、跨域及安全拦截
  8. SEO优化技巧 —— 如何让搜索引擎索引你的实时内容

为什么PHP需要长连接推送?

传统HTTP协议是“请求-响应”模式,服务端无法主动向客户端推送数据,但在实时弹幕、股票行情、在线协作等场景中,服务器需要将事件毫秒级推送给用户,PHP虽然被诟病为“短命进程”,但通过常驻内存的框架(如Workerman、Swoole),完全能构建高性能长连接服务,根据Google官方文档,将页面更新延迟从500ms降至100ms,用户流失率可降低21%,长连接推送不只是技术升级,更是产品竞争力的核心。

PHP长连接推送怎么做

长连接技术全景图

技术方案 连接方向 实时性 浏览器兼容 服务器压力
WebSocket 全双工 主流
SSE 服务端→客户端 IE除外
长轮询 半双工 全支持
短轮询 半双工 全支持 极高

核心结论

  • 若需客户端双向交互(如聊天室),必须选WebSocket
  • 若仅服务端推送通知(如新闻订阅),选用SSE可省去复杂协议,且支持自动重连;
  • 若现有系统基于FPM且不想引入新框架,Redis长轮询是最平滑的方案。

方案一:原生WebSocket实现(Workerman + Swoole)

为什么选Workerman?

  • 纯PHP开发,无需C扩展,便于二次开发;
  • 自带进程管理、定时器、异步TCP客户端。

核心代码(服务端)

use Workerman\Worker;
$ws_worker = new Worker("websocket://0.0.0.0:2346");
$ws_worker->onConnect = function($conn) {
    echo "新连接: ".$conn->id;
};
$ws_worker->onMessage = function($conn, $data) {
    $conn->send("已收到: ".$data);
};
Worker::runAll();

前端JS

const ws = new WebSocket('ws://你的服务器IP:2346');
ws.onmessage = (event) => console.log(event.data);

关键点

  • 必须设置心跳检测(每30秒发送ping,避免Nginx超时断开);
  • 使用TcpConnection::$maxSendBufferSize防止慢客户端阻塞。

方案二:SSE(Server-Sent Events)

SSE通过EventSource接口实现,服务端需设置Content-Type: text/event-stream,它自动重连、自带事件ID,特别适合股市价格、进度条刷新。

PHP服务端实现(结合Swoole或FPM)

header('Content-Type: text/event-stream');
header('Cache-Control: no-cache');
while (true) {
    echo "data: ".json_encode(['price'=>rand(100,200)])."\n\n";
    ob_flush();
    flush();
    sleep(1);
}

前端订阅

const source = new EventSource('/stock/price');
source.onmessage = (event) => console.log(event.data);

注意:FPM模式下,建议在Nginx配置proxy_read_timeout 3600s并禁用压缩。

方案三:Redis Pub/Sub + 长轮询

此方案适合不想改变PHP-FPM架构的团队:

  • 后端生产者通过Redis::publish('channel', $data)发布消息;
  • 消费端(另一个PHP进程)通过subscribe订阅并写入到MySQL或直接推送给前端长轮询接口。

长轮询核心逻辑

while (true) {
    $data = $redis->blPop('notify_queue', 30);
    if ($data) {
        echo json_encode($data);
        break;
    }
}

优势:不新增端口,纯HTTP即可;劣势:实时性受轮询间隔影响。

核心代码示例:心跳检测与断线重连

完整服务端(Workerman)心跳方案

$ws_worker->onConnect = function($conn) {
    $conn->lastMessageTime = time();
};
$ws_worker->onMessage = function($conn, $data) {
    $conn->lastMessageTime = time();
    if ($data === 'ping') {
        $conn->send('pong');
    }
};
// 定时检测(每10秒)
$timer = Timer::add(10, function() use ($ws_worker) {
    foreach ($ws_worker->connections as $conn) {
        if (time() - $conn->lastMessageTime > 60) {
            $conn->close("Heartbeat timeout");
        }
    }
});

前端自动重连

function connect() {
    const ws = new WebSocket('ws://your-domain');
    ws.onclose = () => setTimeout(connect, 3000);
    ws.onerror = () => ws.close();
    ws.onopen = () => setInterval(() => ws.send('ping'), 30000);
}
connect();

高频问答

Q1:Nginx如何配置WebSocket反代?

server {
    location /ws/ {
        proxy_pass http://127.0.0.1:2346;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
    }
}

Q2:为什么连接数过万后CPU飙升?
答:可能未使用事件驱动库(如libevent或Swoole),在Workerman中启用Worker::$eventLoopClass = \Workerman\Events\Ev::class可提升并发能力。

Q3:跨域如何解决?
在服务端设置Access-Control-Allow-Origin: *即可,注意若开启credentials:true,则不能用通配符,需指定具体域名。

Q4:如何优雅关闭服务?
使用Worker::stopAll()并捕获SIGTERM信号,确保连接数据完整保存。

SEO优化技巧

如实时行情)对爬虫不友好,但可遵循以下策略:

  • 预渲染:使用puppeteer生成静态快照,暴露给百度爬虫;
  • 结构化数据:用<meta name="ir-scan" content="...">标记动态区域;
  • 渐进增强:无JS时降级为传统HTTP刷新,确保内容可索引。

抱歉,评论功能暂时关闭!