ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零构建PHP WebSocket推送系统:Workerman实战与高并发架构设计

从零构建PHP WebSocket推送系统:Workerman实战与高并发架构设计 最近在开发一个社区类项目时遇到了一个典型的需求当用户发布新帖子、收到评论或被点赞时需要及时通知到相关用户。最初我们采用了简单的数据库轮询但随着用户量增长服务器压力剧增实时性也大打折扣。为了解决这个问题我们决定引入一个高效、可靠的实时消息推送系统。本文将手把手带你从零搭建一个基于 PHP 的 WebSocket 推送系统。内容涵盖从最基础的 Socket 编程概念到使用 Workerman 框架构建高性能长连接服务再到前端如何建立连接并接收实时消息。无论你是想为现有项目增加实时通知功能还是学习 WebSocket 和长连接编程这篇文章都能提供一套完整、可落地的解决方案。我们将从原理讲起逐步深入到代码实现、生产环境部署和常见问题排查确保你学完就能用上。1. 背景与核心概念为什么需要推送系统在传统的 Web 开发中客户端浏览器与服务器之间的通信主要基于 HTTP 协议这是一种“请求-响应”模型。客户端发起请求服务器处理并返回响应然后连接立即关闭。这种模式简单明了但对于需要服务器主动向客户端推送数据的场景如聊天、实时通知、股票行情就显得力不从心了。常见的“伪实时”方案及其局限性短轮询 (Polling)客户端每隔几秒向服务器发送一次请求询问是否有新消息。这种方式实现简单但会产生大量无效请求浪费服务器和网络资源实时性差有延迟。长轮询 (Long Polling)客户端发起请求服务器持有这个连接直到有数据可返回或超时。虽然减少了请求次数但每个连接仍然占用服务器资源并且在消息频繁时效率不高。Server-Sent Events (SSE)允许服务器向客户端单向推送数据基于 HTTP 协议。它比轮询高效但缺点是只能由服务器向客户端单向通信且兼容性并非完美。WebSocket 协议的出现完美解决了上述问题。它通过在单个 TCP 连接上提供全双工通信通道允许服务器和客户端在任何时候主动向对方发送数据。其核心优势在于真正的双向通信服务器可以随时主动推送。低延迟建立连接后数据传输头部开销极小。减少带宽消耗避免了 HTTP 头部的重复传输。持久连接一个连接可以持续复用。因此构建一个基于 WebSocket 的推送系统是实现高实时性、低资源消耗应用的最佳选择。我们的目标就是利用 PHP 构建这样一个系统的服务端。2. 环境准备与版本说明在开始编码之前我们需要准备好开发环境。本文将使用Workerman框架来简化 WebSocket 服务器的开发。Workerman 是一个纯 PHP 开发的高性能 Socket 服务器框架它使得 PHP 开发者无需深入理解复杂的 Socket 和进程编程就能轻松构建出稳定的长连接服务。核心环境要求操作系统Linux (推荐) 或 Windows。生产环境强烈建议使用 Linux。PHP 版本 7.3 (Workerman 4.x 要求)。本文示例使用 PHP 7.4 或 8.0。扩展要求pcntl(进程控制),posix(Linux 下),event或libevent扩展 (可选用于提升性能)。Composer用于管理 PHP 依赖。检查环境在终端中执行以下命令确保环境符合要求。# 查看 PHP 版本 php -v # 查看必要的扩展是否已安装 php -m | grep -E “pcntl|posix” # 安装 Composer (如果未安装) # 参考官方指南https://getcomposer.org/download/项目初始化创建一个新的项目目录并使用 Composer 初始化项目安装 Workerman。mkdir php-push-system cd php-push-system composer init -n composer require workerman/workerman安装完成后你的composer.json文件应该包含了对 workerman 的依赖。项目结构初步如下php-push-system/ ├── composer.json ├── composer.lock ├── vendor/ # Composer 依赖目录 └── start.php # 我们将在这里编写启动脚本3. 核心原理与 Workerman 基础在动手写代码前理解 Workerman 的基本运行模式至关重要。与传统的基于 Apache/Nginx PHP-FPM 的 Web 应用不同Workerman 是一个常驻内存的 CLI (命令行接口) 应用程序。核心概念Worker代表一个监听端口的进程。一个 Worker 可以处理多个客户端的连接。主进程与子进程Workerman 启动时会创建一个主进程Master来管理多个子进程Worker。主进程负责监控子进程子进程负责处理具体的业务连接。这种模式提高了服务的稳定性和并发能力。事件驱动Workerman 基于 Reactor 模型使用事件循环来处理连接、接收数据、发送数据等事件性能非常高。一个最小的 WebSocket 服务器让我们先创建一个最简单的 WebSocket 服务器理解其生命周期。创建start.php文件?php // start.php require_once __DIR__ . ‘/vendor/autoload.php’; use Workerman\Worker; use Workerman\Connection\TcpConnection; // 创建一个 Worker 监听 2345 端口使用 websocket 协议通讯 $ws_worker new Worker(“websocket://0.0.0.0:2345”); // 启动 4 个进程对外提供服务 $ws_worker-count 4; // 当客户端连接时 $ws_worker-onConnect function(TcpConnection $connection) { echo “New connection from ” . $connection-getRemoteIp() . “\n”; // 可以向新连接的客户端发送一条欢迎消息 $connection-send(‘Welcome to the push server!’); }; // 当客户端发来消息时 $ws_worker-onMessage function(TcpConnection $connection, $data) { // 这里简单地将客户端发来的消息原样返回 echo “Received message: $data\n”; $connection-send(“You said: $data”); }; // 当客户端连接断开时 $ws_worker-onClose function(TcpConnection $connection) { echo “Connection closed\n”; }; // 运行 worker Worker::runAll();代码解释我们创建了一个Worker对象指定协议为websocket监听所有网络接口 (0.0.0.0) 的2345端口。count 4表示启动 4 个子进程充分利用多核 CPU。我们定义了三个核心的回调事件onConnect客户端成功建立 WebSocket 连接时触发。onMessage服务器收到客户端发送的消息时触发。$data就是消息内容。onClose客户端断开连接时触发。Worker::runAll()启动所有 Worker开始事件循环。运行与测试在命令行中启动服务器php start.php start你会看到类似下面的输出表示服务器已启动Workerman[start.php] start in DEBUG mode —————————— WORKERMAN —————————— Workerman version:4.1.0 PHP version:8.0.15 —————————— WORKERS —————————— worker listen processes status none websocket://0.0.0.0:2345 4 [OK] ——————————————————————————————— Press CtrlC to stop. Start success.现在你可以使用在线的 WebSocket 测试工具如 http://www.websocket.org/echo.html 或编写一个简单的 HTML 页面来连接ws://你的服务器IP:2345测试消息的发送和接收。4. 构建完整的推送系统实战上面的例子只是一个回声服务器。一个完整的推送系统需要解决几个关键问题连接管理如何识别不同的用户客户端消息路由如何将消息准确地推送给指定的一个或一组用户业务集成如何与现有的 PHP 业务代码如 Laravel、ThinkPHP 项目交互触发推送接下来我们将一步步构建一个具备用户绑定、定向推送和外部触发功能的系统。4.1 连接管理与用户绑定在 WebSocket 中每个TcpConnection对象代表一个客户端连接。我们需要一种方式将连接与具体的用户 ID例如从数据库读取的用户主键关联起来。思路在客户端连接成功后首先需要发送一个“认证”消息包含其身份标识如user_id或token。服务器收到后将该标识与当前的$connection对象绑定。修改start.php?php // start.php require_once __DIR__ . ‘/vendor/autoload.php’; use Workerman\Worker; use Workerman\Connection\TcpConnection; // 全局数组用于存储 uid 到 connection 的映射 // 注意在多进程模式下这个数组无法在进程间共享。这里先用于单进程演示后续会解决。 $userConnections []; $ws_worker new Worker(“websocket://0.0.0.0:2345”); // 先使用单进程便于理解 $ws_worker-count 1; $ws_worker-onConnect function(TcpConnection $connection) { global $userConnections; echo “New connection established. Connection ID: ” . $connection-id . “\n”; }; $ws_worker-onMessage function(TcpConnection $connection, $data) { global $userConnections; echo “Received: $data\n”; // 解析客户端发送的 JSON 数据 $messageData json_decode($data, true); if (!$messageData) { $connection-send(json_encode([‘type’ ‘error’, ‘msg’ ‘Invalid JSON’])); return; } $type $messageData[‘type’] ?? ‘’; $uid $messageData[‘uid’] ?? null; switch ($type) { case ‘bind’: // 用户绑定逻辑 if (!$uid) { $connection-send(json_encode([‘type’ ‘error’, ‘msg’ ‘UID is required for bind’])); return; } // 将 uid 绑定到当前连接 $userConnections[$uid] $connection; // 也可以将 connection 对象上存储 uid $connection-uid $uid; echo “User $uid bound to connection {$connection-id}\n”; $connection-send(json_encode([‘type’ ‘bind_success’, ‘uid’ $uid])); break; case ‘chat’: // 简单的聊天消息演示广播或私聊 $toUid $messageData[‘to_uid’] ?? null; $content $messageData[‘content’] ?? ‘’; if ($toUid isset($userConnections[$toUid])) { // 私聊 $userConnections[$toUid]-send(json_encode([ ‘type’ ‘chat’, ‘from_uid’ $connection-uid ?? ‘unknown’, ‘content’ $content, ‘time’ date(‘Y-m-d H:i:s’) ])); } else { // 广播给所有绑定的用户除了自己 foreach ($userConnections as $userUid $userConn) { if ($userConn ! $connection) { $userConn-send(json_encode([ ‘type’ ‘broadcast’, ‘from_uid’ $connection-uid ?? ‘unknown’, ‘content’ $content, ‘time’ date(‘Y-m-d H:i:s’) ])); } } } break; default: $connection-send(json_encode([‘type’ ‘error’, ‘msg’ ‘Unknown message type’])); } }; $ws_worker-onClose function(TcpConnection $connection) { global $userConnections; echo “Connection {$connection-id} closed\n”; // 连接断开时从全局数组中移除 if (!empty($connection-uid)) { unset($userConnections[$connection-uid]); echo “User {$connection-uid} unbound\n”; } }; Worker::runAll();关键点说明我们定义了一个协议格式客户端发送的必须是 JSON 字符串包含type字段来区分消息类型。bind类型消息用于将用户 ID (uid) 与当前连接绑定。我们将连接对象存储在全局数组$userConnections中键是uid。在onClose事件中清理该连接在全局数组中的记录防止内存泄漏。重要限制上面的$userConnections是 PHP 全局变量仅在单个进程内有效。当我们设置$ws_worker-count 4时会有 4 个独立的进程它们的变量不共享。用户 A 连接到进程 1其连接信息在进程 2 中是无法获取的导致无法跨进程推送。这是下一步要解决的核心问题。4.2 解决多进程数据共享使用 Channel 组件Workerman 提供了Channel组件用于解决多进程包括多台服务器间的通信问题。我们可以利用它来维护一个全局的“用户-连接”映射关系或者更简单点用它来广播消息。首先安装 Channel 组件composer require workerman/channel接下来我们设计一个新的架构每个 Worker 进程仍然维护自己进程内的$userConnections。当需要向某个特定用户推送消息时我们不知道他在哪个进程。因此我们通过 Channel 将消息“广播”给所有 Worker 进程。每个 Worker 进程收到 Channel 广播的消息后检查目标用户是否连接在自己进程上如果是则进行推送。创建push_server.php?php // push_server.php require_once __DIR__ . ‘/vendor/autoload.php’; use Workerman\Worker; use Workerman\Connection\TcpConnection; use Workerman\Channel\Server as ChannelServer; // 启动一个 Channel 服务器用于进程间通信 $channel_server new ChannelServer(“0.0.0.0”, 2206); // 创建 WebSocket Worker $ws_worker new Worker(“websocket://0.0.0.0:2345”); $ws_worker-count 4; // 启动4个进程 // 每个进程内部的用户连接映射 $ws_worker-userConnections []; // Worker 进程启动时连接上 Channel 客户端 $ws_worker-onWorkerStart function($worker) { // 连接到 Channel 服务器 $channel_client new \Workerman\Channel\Client(‘127.0.0.1’, 2206); $channel_client-connect(); // 订阅一个全局频道用于接收推送任务 $channel_client-on(‘push’, function($event_data) use ($worker) { $to_uid $event_data[‘to_uid’] ?? null; $message $event_data[‘message’] ?? []; if ($to_uid isset($worker-userConnections[$to_uid])) { // 如果目标用户连接在当前进程则推送 $worker-userConnections[$to_uid]-send(json_encode($message)); } // 如果是广播消息 (to_uid 为 null 或特定标识) if ($event_data[‘broadcast’] ?? false) { foreach ($worker-userConnections as $connection) { $connection-send(json_encode($message)); } } }); }; $ws_worker-onConnect function(TcpConnection $connection) use ($ws_worker) { echo “New connection on worker {$ws_worker-id}\n”; }; $ws_worker-onMessage function(TcpConnection $connection, $data) use ($ws_worker) { $messageData json_decode($data, true); if (!$messageData) return; $type $messageData[‘type’] ?? ‘’; $uid $messageData[‘uid’] ?? null; switch ($type) { case ‘bind’: if ($uid) { // 绑定用户到当前进程的映射 $ws_worker-userConnections[$uid] $connection; $connection-uid $uid; $connection-send(json_encode([‘type’ ‘system’, ‘msg’ “Bind success for $uid”])); echo “Worker {$ws_worker-id}: User $uid bound.\n”; } break; // … 可以处理其他类型的客户端消息如 chat } }; $ws_worker-onClose function(TcpConnection $connection) use ($ws_worker) { if (!empty($connection-uid)) { unset($ws_worker-userConnections[$connection-uid]); echo “Worker {$ws_worker-id}: User {$connection-uid} disconnected.\n”; } }; // 运行所有服务 Worker::runAll();架构解析我们启动了一个独立的ChannelServer服务端口 2206作为消息总线。每个 WebSocket Worker 进程在启动 (onWorkerStart) 时会创建一个Channel\Client连接到总线并订阅push频道。当外部需要推送消息时例如从网站后台向push频道发布一个事件。所有订阅了push频道的 Worker 进程都会收到这个事件并检查目标用户是否在自己进程内如果是则执行推送。这样我们就实现了在多进程环境下向任意用户推送消息的能力。4.3 外部业务触发推送HTTP API我们的 WebSocket 服务器是常驻内存的而传统的 Web 业务如 Laravel运行在 FPM 模式下。如何让 FPM 中的代码触发推送呢我们可以为 WebSocket 服务器增加一个简单的HTTP 接口专门用于接收推送请求。在push_server.php中增加一个 HTTP Worker// … 前面的 ChannelServer 和 WebSocket Worker 代码不变 … // 新增一个 HTTP Worker用于接收外部推送请求 $http_worker new Worker(“http://0.0.0.0:2346”); $http_worker-name ‘PushHttp’; $http_worker-count 2; // HTTP 服务一般不需要太多进程 $http_worker-onMessage function($connection, $request) { // 简单的路由和验证生产环境需要更严格的验证如 API Token $path $request-path(); if ($path ‘/push’ $request-method() ‘POST’) { $postData json_decode($request-rawBody(), true); $to_uid $postData[‘to_uid’] ?? null; $message $postData[‘message’] ?? []; if (empty($message)) { $connection-send(json_encode([‘code’ 400, ‘msg’ ‘Message is empty’])); return; } // 连接到 Channel 服务器发布推送事件 $channel_client new \Workerman\Channel\Client(‘127.0.0.1’, 2206); $channel_client-connect(); $channel_client-publish(‘push’, [ ‘to_uid’ $to_uid, // 如果为 null 或空可设计为广播 ‘message’ $message // 要推送的消息内容 ]); $connection-send(json_encode([‘code’ 200, ‘msg’ ‘Push task sent’])); } else { $connection-send(json_encode([‘code’ 404, ‘msg’ ‘Not Found’])); } }; // 注意Worker::runAll() 要放在所有 Worker 定义之后 Worker::runAll();现在你的推送系统就有了一个 HTTP 端点http://你的服务器IP:2346/push。你的 Laravel、ThinkPHP 或其他任何语言编写的业务代码都可以通过发起一个 HTTP POST 请求来触发实时推送。业务代码示例PHP cURL?php // 在你的业务代码中比如当用户发表评论后 $pushData [ ‘to_uid’ 123, // 要通知的用户ID ‘message’ [ ‘type’ ‘notification’, ‘title’ ‘您有新的评论’, ‘content’ ‘用户A回复了您的帖子’, ‘link’ ‘/post/456’, ‘time’ time() ] ]; $ch curl_init(‘http://127.0.0.1:2346/push’); curl_setopt_array($ch, [ CURLOPT_POST true, CURLOPT_RETURNTRANSFER true, CURLOPT_HTTPHEADER [‘Content-Type: application/json’], CURLOPT_POSTFIELDS json_encode($pushData) ]); $response curl_exec($ch); curl_close($ch); // 处理 $response …4.4 前端连接与消息处理服务端完成后我们需要一个前端页面来连接 WebSocket 并处理消息。创建一个public/client.html文件!DOCTYPE html html lang“zh-CN” head meta charset“UTF-8” titleWebSocket 推送测试客户端/title style body { font-family: sans-serif; margin: 20px; } #output { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; margin-bottom: 10px; } .message { margin: 5px 0; padding: 5px; background: #f0f0f0; border-radius: 3px; } .system { color: blue; } .notification { color: green; font-weight: bold; } .chat { color: #333; } /style /head body h2WebSocket 推送测试/h2 div label用户ID: /label input type“text” id“uidInput” placeholder“输入数字UID” value“1” button onclick“bindUser()”绑定用户/button button onclick“connect()”连接WS/button button onclick“disconnect()”断开连接/button /div div input type“text” id“messageInput” placeholder“输入消息” style“width: 300px;” button onclick“sendMessage()”发送/button 发送给UID: input type“text” id“toUidInput” placeholder“留空为广播” style“width: 80px;” /div div id“output”/div script let ws null; const serverUrl ‘ws://’ window.location.hostname ‘:2345’; // 根据你的服务器地址修改 const outputDiv document.getElementById(‘output’); function log(msg, className‘’) { const msgDiv document.createElement(‘div’); msgDiv.className message ${className}; msgDiv.textContent [${new Date().toLocaleTimeString()}] ${msg}; outputDiv.appendChild(msgDiv); outputDiv.scrollTop outputDiv.scrollHeight; } function connect() { if (ws ws.readyState WebSocket.OPEN) { log(‘已经连接了’, ‘system’); return; } ws new WebSocket(serverUrl); ws.onopen function() { log(‘WebSocket 连接已建立’, ‘system’); }; ws.onmessage function(event) { try { const data JSON.parse(event.data); switch(data.type) { case ‘system’: log(系统: ${data.msg}, ‘system’); break; case ‘notification’: log(通知: ${data.title} - ${data.content}, ‘notification’); // 可以在这里显示浏览器通知 if (Notification.permission ‘granted’) { new Notification(data.title, { body: data.content }); } break; case ‘chat’: case ‘broadcast’: log(${data.from_uid} 说: ${data.content}, ‘chat’); break; default: log(收到消息: ${event.data}); } } catch (e) { log(收到非JSON消息: ${event.data}); } }; ws.onerror function(error) { log(连接错误: ${error}, ‘system’); }; ws.onclose function() { log(‘连接已关闭’, ‘system’); }; } function bindUser() { if (!ws || ws.readyState ! WebSocket.OPEN) { alert(‘请先建立WebSocket连接’); return; } const uid document.getElementById(‘uidInput’).value.trim(); if (!uid) { alert(‘请输入用户ID’); return; } const bindMsg JSON.stringify({ type: ‘bind’, uid: uid }); ws.send(bindMsg); } function sendMessage() { if (!ws || ws.readyState ! WebSocket.OPEN) { alert(‘请先建立WebSocket连接并绑定用户’); return; } const content document.getElementById(‘messageInput’).value.trim(); const toUid document.getElementById(‘toUidInput’).value.trim(); if (!content) return; let msgToSend; if (toUid) { msgToSend { type: ‘chat’, to_uid: toUid, content: content }; } else { msgToSend { type: ‘chat’, content: content }; // 服务器逻辑会处理为广播 } ws.send(JSON.stringify(msgToSend)); document.getElementById(‘messageInput’).value ‘’; } function disconnect() { if (ws) { ws.close(); ws null; } } // 页面加载后自动连接可选 window.onload connect; /script /body /html这个前端页面提供了连接、绑定用户、发送消息和显示接收消息的基本功能。你可以将其部署到你的 Web 服务器如 Nginx下并通过浏览器访问来测试整个推送流程。5. 生产环境部署与优化将开发环境的代码部署到生产环境需要考虑稳定性、性能和可维护性。5.1 使用 GatewayWorkerWorkerman 有一个更上层的封装框架叫GatewayWorker它专门为长连接应用设计内置了完整的分布式架构、心跳检测、断线重连、客户端分组等特性非常适合直接用于生产环境的推送、聊天等系统。如果你的项目对稳定性要求高建议直接使用 GatewayWorker。其核心概念是Gateway网关和BusinessWorker业务进程天然支持多机部署。5.2 进程管理与守护在 Linux 下我们需要让 Workerman 在后台以守护进程方式运行。# 启动 (守护进程模式) php push_server.php start -d # 查看状态 php push_server.php status # 平滑重启 (只重启业务进程不断开现有连接) php push_server.php reload # 停止 php push_server.php stop5.3 使用 Nginx 反向代理直接暴露 2345 (WS) 和 2346 (HTTP) 端口可能不太规范且无法使用域名。通常使用 Nginx 进行反向代理。WebSocket 代理配置# 在 Nginx 配置文件中 server { listen 80; server_name ws.yourdomain.com; location / { proxy_pass http://127.0.0.1:2345; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection “upgrade”; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 防止 proxy 断开连接 proxy_read_timeout 3600s; proxy_send_timeout 3600s; } }HTTP API 代理配置server { listen 80; server_name api-push.yourdomain.com; location / { proxy_pass http://127.0.0.1:2346; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }配置后前端连接地址变为ws://ws.yourdomain.com业务 API 调用地址为http://api-push.yourdomain.com/push。5.4 安全性增强WSS (WebSocket Secure)生产环境务必使用 HTTPS/WSS。可以通过 Nginx 配置 SSL 证书然后代理到后端的 WS 服务。连接认证示例中的bind操作非常简陋。生产环境应该使用 JWT (JSON Web Token) 或 Session 等机制。客户端连接后首先发送 Token服务器端验证 Token 有效性并解析出用户信息后再进行绑定。API 鉴权HTTP 推送接口/push必须添加鉴权例如使用 API Key/Secret或者验证请求来源 IP防止被恶意调用。输入验证与过滤对所有客户端发送的数据进行严格的验证和过滤防止 XSS 或注入攻击。6. 常见问题与排查思路在开发和运维推送系统时你可能会遇到以下问题问题现象可能原因排查思路与解决方案前端无法连接 WebSocket1. 服务器防火墙未开放端口。2. Workerman 服务未启动。3. Nginx 代理配置错误。4. 使用了ws://但页面是https://浏览器会阻止。1. 检查 netstat -tlnp连接建立后立即断开1. 前端未及时发送心跳包被服务器防火墙或负载均衡器断开。2. Workerman 心跳配置问题。1. 前端实现心跳机制定期发送 ping/pong。2. 配置 Workerman 的心跳$ws_worker-pingInterval 25;。消息推送延迟或丢失1. Channel 服务器连接失败。2. 目标用户不在线连接已断开。3. 网络波动或服务器负载过高。1. 检查 ChannelServer 是否运行端口 2206 是否通畅。2. 在推送前业务层应先判断用户在线状态需要维护在线状态表。3. 监控服务器资源优化代码考虑使用更快的序列化方式如 MessagePack。多进程下用户绑定异常使用了进程内变量存储连接映射导致用户重连后可能绑定到不同进程旧进程的映射未清理。使用 Channel 组件进行跨进程通信是正确方向。确保onClose事件中正确清理映射。更健壮的做法是使用 Redis 等外部存储记录uid到worker_id的映射。HTTP API 调用返回 4041. HTTP Worker 未启动或端口被占用。2. 请求路径或方法不正确。1. 检查进程状态确认 2346 端口在监听。2. 使用 Postman 等工具测试确保是 POST 请求到/push路径且Content-Type: application/json。内存使用量不断增长1. 连接断开后资源未释放内存泄漏。2. 全局数组或属性中积累了过多数据。1. 仔细检查onClose回调确保从所有存储连接的地方移除引用。2. 定期清理无用的数据。使用gc_mem_caches()可能有助于 PHP 回收内存。7. 最佳实践与工程建议连接保活与心跳网络中的 NAT 网关、防火墙或负载均衡器可能会清除长时间空闲的连接。必须在客户端和服务端实现心跳机制。Workerman 可以设置pingInterval和pingNotResponseLimit客户端也需要定时发送 ping 帧或特定业务心跳包。离线消息处理当用户离线时推送的消息会丢失。对于重要的通知需要设计离线存储如存入数据库或 Redis 队列待用户下次上线时拉取或推送。分布式部署当单台服务器无法支撑海量连接时需要分布式部署。GatewayWorker 原生支持。核心思想是多台 Gateway 服务器暴露给客户端通过注册中心如 Register 服务让 BusinessWorker 和 Gateway 相互发现消息通过内部网络转发。监控与日志记录关键日志如连接数、绑定事件、消息流量、错误信息。可以使用 Workerman 的Worker::$stdoutFile将标准输出重定向到文件方便日志收集和分析。同时监控服务器的 CPU、内存、网络流量。优雅重启与发布使用php your_file.php reload进行平滑重启可以避免服务中断。对于重大更新可以考虑蓝绿部署或逐个服务器重启。协议设计定义清晰、可扩展的客户端-服务器通信协议。使用 JSON 是一个好起点但可以考虑更高效的二进制协议如 protobuf以减少带宽。消息类型 (type) 要明确如auth,heartbeat,chat,notification,error等。前端重连机制前端 WebSocket 连接可能会因为网络问题断开必须实现自动重连逻辑并在重连后重新进行用户身份绑定。通过本文的讲解你应该已经掌握了使用 PHP 和 Workerman 构建一个高可用、可扩展的实时消息推送系统的核心方法。从单进程演示到多进程架构从内部通信到对外提供 HTTP API我们覆盖了一个生产级系统所需的主要环节。
返回列表