首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >PHP 跨平台实时通讯框架 Socket.IO

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

作者头像
Tinywan
发布2026-07-01 13:21:19
发布2026-07-01 13:21:19
890
举报
文章被收录于专栏:开源技术小栈开源技术小栈

概述

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 部署更熟悉。

快速上手

安装

代码语言:javascript
复制
composer require workerman/phpsocket.io

服务端

代码语言:javascript
复制
<?php

namespaceapp\http\controller;

usePHPSocketIO\SocketIO;
usethink\facade\Log;
useWorkerman\Worker;

class Server
{
    /**
     * PHPSocket.IO 服务启动入口
     *
     * @author: Tinywan(Shaobo Wan)
     */
    publicfunction server()
    {
        // 用于记录每个 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();
    }
}

启动命令

代码语言:javascript
复制
php web_msg.php start-d

输出结果(守护进程模式)

代码语言:javascript
复制
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]

发送消息

代码语言:javascript
复制
/**
 * 推送消息到 PHPSocket.IO(带错误日志)
 */
function send_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';
}

客户端

代码语言:javascript
复制
<!-- 推荐使用 jsDelivr 或 cdnjs 替代 bootcss(更稳定、全球加速) -->
<!-- Socket.IO 客户端:原版用 2.0.3,已较旧;建议升级到 4.x(兼容 PHPSocket.IO v2.x) -->
<script src="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) -->
<script src="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 polling
        reconnection: 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 / warning
            autoHide: true,
            clickToHide: true,
            position: "top right",
            hideDuration: 3000
        });
    });
});
</script>
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-01-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 开源技术小栈 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 核心特点
    • 底层通信机制
    • 设计目标
    • 与 Workerman 区别
    • 与 Node.js Socket.IO 的区别
  • 快速上手
    • 安装
    • 服务端
    • 客户端
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档