当前位置:首页>php>PHP 跨平台实时通讯框架 Socket.IO

PHP 跨平台实时通讯框架 Socket.IO

  • 2026-09-26 21:12:36
PHP 跨平台实时通讯框架 Socket.IO

概述

PHPSocket.IO 是一个 PHP 版本的 Socket.IO 服务端实现,由 Workerman 作者 walkor 开发,目的是让 PHP 开发者也能轻松构建实时应用,而无需切换到 Node.js。

它完全兼容 Socket.IO 客户端协议(支持 v1.3.0 ~ v2.x 的客户端),API 接口高度相似(on/emit/to/join 等),让前端 JS 代码几乎不用改,就能连接 PHP 后端。

核心特点

底层通信机制

  • 优先使用 WebSocket 协议(高效、低延迟)。
  • 如果浏览器/环境不支持 WebSocket,会自动降级到 HTTP 长轮询(long-polling)、JSONP 轮询、甚至 Adobe Flash Socket 等方式。
  • 所有降级对开发者完全透明,统一使用 Socket.IO 的 API。

设计目标

  • 用 PHP 构建跨浏览器、跨设备的实时应用,例如:
  • 在线聊天室
  • 实时客服系统
  • 评论/弹幕系统
  • WebIM
  • 实时数据仪表盘/在线分析
  • 充分利用 PHP 生态(Webman、ThinkPHP、Laravel 等框架集成也较容易)。

与 Workerman 区别

  • PHPSocket.IO 是基于 Workerman 开发的,所以继承了 Workerman 的所有高性能特性(异步、非阻塞、支持多进程)。
  • 最大优势:对各种浏览器的兼容性更好(自动 fallback 机制更完善),而 Workerman 本身更底层、更通用(支持 TCP/UDP/HTTP/WebSocket 等多种协议)。

与 Node.js Socket.IO 的区别

  • 官方版是 Node.js 写的,事件循环基于 libuv。
  • PHPSocket.IO 是 PHP 写的,事件循环基于 Workerman(底层用 libevent 或 select/epoll)。
  • 性能上在高并发场景下 PHP 版通常不如 Node.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>

最新文章

随机文章