PHPSocket.IO 是一个 PHP 版本的 Socket.IO 服务端实现,由 Workerman 作者 walkor 开发,目的是让 PHP 开发者也能轻松构建实时应用,而无需切换到 Node.js。
它完全兼容 Socket.IO 客户端协议(支持 v1.3.0 ~ v2.x 的客户端),API 接口高度相似(on/emit/to/join 等),让前端 JS 代码几乎不用改,就能连接 PHP 后端。
composer require workerman/phpsocket.io<?phpnamespaceapp\http\controller;usePHPSocketIO\SocketIO;usethink\facade\Log;useWorkerman\Worker;classServer{/** * PHPSocket.IO 服务启动入口 * * @author: Tinywan(Shaobo Wan) */publicfunctionserver(){// 用于记录每个 uid 的在线连接数(支持同一 uid 多端登录) $uidConnectionMap = [];// 记录上一次广播的在线用户数和页面数(当前代码中未使用,可用于后续扩展广播优化) $last_online_count = 0; $last_online_page_count = 0;// 创建 PHPSocketIO 服务,监听 2120 端口(WebSocket 主端口) $sender_io = new SocketIO(2120);// ------------------- 客户端连接事件 ------------------- $sender_io->on('connection', function($socket){ Log::info('客户端发起连接');// ------------------- 登录事件 ------------------- $socket->on('login', function($uid)use($socket){ Log::info("客户端登录 uid: {$uid}");global $uidConnectionMap;// 防止重复登录if (isset($socket->uid)) {return; } $uid = (string) $uid;// 初始化该 uid 的连接计数if (!isset($uidConnectionMap[$uid])) { $uidConnectionMap[$uid] = 0; }// 增加连接计数 ++$uidConnectionMap[$uid];// 将当前 socket 加入以 uid 为名的 room,便于定向推送 $socket->join($uid); $socket->uid = $uid; });// ------------------- 断开连接事件 ------------------- $socket->on('disconnect', function()use($socket){ Log::info('客户端断开: ' . json_encode($socket));if (!isset($socket->uid)) {return; }global $uidConnectionMap;// 减少连接计数,若 <=0 则移除该 uid 记录if (--$uidConnectionMap[$socket->uid] <= 0) {unset($uidConnectionMap[$socket->uid]); } }); });// ------------------- Worker 启动后添加 HTTP 推送接口 ------------------- $sender_io->on('workerStart', function()use($sender_io){// 创建一个独立的 HTTP Worker,用于接收其他 PHP 脚本的推送请求// 端口 2121(内网通讯,不能与 2120 冲突) $inner_http_worker = new Worker('http://0.0.0.0:2121'); $inner_http_worker->onMessage = function($http_connection, $data)use($sender_io){// 安全获取 POST 数据 $postData = $data['post'] ?? []; $to = $postData['to'] ?? ''; $content = htmlspecialchars($postData['content'] ?? '', ENT_QUOTES, 'UTF-8'); Log::info("推送目标: " . json_encode($to));if ($to) {// 定向推送给指定 uid(通过 room) $sender_io->to($to)->emit('new_msg', $content); } else {// 广播给所有连接 $sender_io->emit('new_msg', $content); }// 返回推送结果(离线用户返回 'offline')if ($to && !isset($uidConnectionMap[$to])) { $http_connection->send('offline'); } else { $http_connection->send('ok'); } };// 启动 HTTP Worker 监听 $inner_http_worker->listen(); });// 启动所有 Worker(包括 SocketIO 和 HTTP Worker) Worker::runAll(); }}启动命令
php web_msg.php start-d输出结果(守护进程模式)
Workerman[web_msg.php] start in DAEMON mode──────────────────────────────────────────── WORKERMAN ────────────────────────────────────────────Workerman version: 5.1.2 PHP version: 8.2.19───────────────────────────────────────────── WORKERS ─────────────────────────────────────────────proto user worker listen processes status───── ──── ────────── ───────────────────────── ───────── ──────tcp www PHPSocketIO socketIO://0.0.0.0:2120 1 [OK]发送消息
/** * 推送消息到 PHPSocket.IO(带错误日志) */functionsend_web_msg($to_uid = 1, string $content = ''): string|array{if (empty($content)) {return ['error_code' => 404, 'reason' => '缺少推送内容']; } $url = 'http://127.0.0.1:2121/'; $data = ['type' => 'publish','content' => $content,'to' => (string) $to_uid, ]; $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($data), // 更安全的 POST 方式 CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_CONNECTTIMEOUT => 2, CURLOPT_HTTPHEADER => ['Expect:'], ]); $response = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);if (curl_errno($ch) || $http_code !== 200) { $error = curl_error($ch) ?: "HTTP {$http_code}"; curl_close($ch);// 可记录日志// error_log("推送失败 to={$to_uid}: {$error}");return ['error_code' => 500, 'reason' => "推送失败:{$error}"]; } curl_close($ch);return trim($response) ?: 'ok';}<!-- 推荐使用 jsDelivr 或 cdnjs 替代 bootcss(更稳定、全球加速) --><!-- Socket.IO 客户端:原版用 2.0.3,已较旧;建议升级到 4.x(兼容 PHPSocket.IO v2.x) --><scriptsrc="https://cdn.jsdelivr.net/npm/socket.io-client@4.8.1/dist/socket.io.min.js"></script><!-- notify.js 替代方案:Bootstrap 5 原生 Toasts 或其他现代库(如 Notyf、SweetAlert2) --><!-- 这里保留原版,但强烈建议替换为 Bootstrap Toasts(无需额外 JS) --><scriptsrc="https://cdn.jsdelivr.net/npm/notifyjs@3.0.0/dist/notify.min.js"></script><script>/** * Socket.IO 客户端初始化脚本 * - 连接到服务器 * - 以 uid 登录 * - 监听 new_msg 事件,更新通知区域并弹出提示 * * @author Tinywan * @version 2026 更新:现代 CDN + 更好实践 */$(document).ready(function () {// 用户 ID(建议从后端动态注入或 localStorage 获取,避免硬编码)const uid = "1"; // 或从 PHP echo 或 meta 标签获取:const uid = document.querySelector('meta[name="user-uid"]').content;// Socket.IO 连接选项const socket = io("https://www.tinywan.com", {path: "/socket.io",transports: ["websocket", "polling"], // 优先 WebSocket,fallback pollingreconnection: true, // 自动重连reconnectionAttempts: 5,timeout: 10000 });// 连接成功事件 socket.on("connect", function () {console.log("Socket.IO 连接成功"); socket.emit("login", uid); // 发送登录事件 });// 连接错误处理(可选增强) socket.on("connect_error", function (err) {console.error("Socket.IO 连接失败:", err.message);// 可显示 UI 提示:如 $("#notice-content").html("连接失败,请刷新页面"); });// 接收新消息事件 socket.on("new_msg", function (msg) {console.log("收到系统消息:", msg);// 更新 DOM 显示 $("#notice-content").html(`系统提示:${msg}`);// 弹出通知(使用 notify.js) $(".notification.sticky").notify({// 可自定义选项className: "info", // 或 success / error / warningautoHide: true,clickToHide: true,position: "top right",hideDuration: 3000 }); });});</script>