找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

3928

积分

0

好友

516

主题
发表于 10 小时前 | 查看: 5| 回复: 0

这个库是一个针对 RFC6455 规范的协议处理工具,包含了用于服务器端和客户端握手以及消息传递协议协商的组件。

规范中那些存在歧义的部分,在这个库中同样也是不明确的。如何处理这些歧义,取决于具体的实现方式。这个库保持独立,不依赖于特定框架,也不处理任何输入/输出操作。HTTP 升级协商相关的功能则通过 PSR-7 接口来处理。

协议核心定义在 RFC 6455(The WebSocket Protocol)中,规定了以下关键部分:

  • 握手(Handshake):从 HTTP/1.1 升级为 WebSocket。
  • 帧(Frame):消息分帧(文本/二进制、掩码、控制帧:Ping/Pong/Close)。
  • 状态管理:连接、消息队列、分片、关闭。
  • 安全性:掩码(masking)、扩展(permessage-deflate)、子协议(Sec-WebSocket-Protocol)。

纯手写协议很容易在细节上出错,比如半包、碎片、掩码和超时处理。而 Ratchet RFC6455 正是 PHP 生态中相当成熟的 I/O 无关协议处理器,它只负责协议逻辑,完全不介入网络 I/O。

结合 Workerman(一个高性能异步 PHP 框架),可以轻松实现稳定、可扩展的 WebSocket 服务。想深入了解网络协议栈的更多细节,可以在 网络/系统 板块找到相关资料。

安装依赖

composer require workerman/workerman ratchet/rfc6455 guzzlehttp/psr7

完整实现代码(start.php)

下面这段代码将 Workerman 的高性能网络能力与 Ratchet 的协议解析能力结合在一起,实现了一个完整的自定义协议处理器。

<?php
/**
 * @desc WebSocket 服务 - 基于 Ratchet RFC6455 + Workerman
 * @author Tinywan(ShaoBo Wan)
 */

declare(strict_types=1);

require_once __DIR__ . '/vendor/autoload.php';

use Workerman\Worker;
use Workerman\Connection\TcpConnection;
use Ratchet\RFC6455\Handshake\ServerNegotiator;
use Ratchet\RFC6455\Handshake\RequestVerifier;
use Ratchet\RFC6455\Messaging\MessageBuffer;
use Ratchet\RFC6455\Messaging\Message;
use Ratchet\RFC6455\Messaging\Frame;
use Ratchet\RFC6455\Messaging\CloseFrameChecker;
use GuzzleHttp\Psr7\HttpFactory;

class WebSocketRFC6455
{
    public static function input($buffer, $connection): int
    {
        if (!isset($connection->handshaked)) {
            return str_contains($buffer, "\r\n\r\n") ? strlen($buffer) : 0;
        }
        // 握手后: 全部消费,MessageBuffer 内部处理分帧与缓冲
        return strlen($buffer);
    }

    public static function decode($buffer, $connection): string
    {
        if (!isset($connection->handshaked)) {
            return self::handleHandshake($buffer, $connection);
        }

        // 初始化消息队列
        if (!isset($connection->messageQueue)) {
            $connection->messageQueue = [];
        }

        // 交给 Ratchet MessageBuffer 解析(内部有 $leftovers 处理半包)
        $connection->messageBuffer->onData($buffer);

        // 返回队列中的第一条消息
        return array_shift($connection->messageQueue) ?? '';
    }

    public static function encode($data, $connection): string
    {
        if (!isset($connection->handshaked)) {
            return $data;   // 握手阶段透传 101 响应
        }
        // 封装为 WebSocket 文本帧
        $frame = new Frame($data, true, Frame::OP_TEXT);
        return $frame->getContents();
    }

    private static function handleHandshake($buffer, $connection): string
    {
        $request = \GuzzleHttp\Psr7\Message::parseRequest($buffer);

        $factory = new HttpFactory();
        $verifier = new RequestVerifier();

        try {
            $negotiator = new ServerNegotiator($verifier, $factory);
            $response = $negotiator->handshake($request);

            $connection->messageQueue = [];

            $connection->messageBuffer = new MessageBuffer(
                new CloseFrameChecker(),
                static function(Message $msg, MessageBuffer $buffer) use($connection) {
                    $connection->messageQueue[] = $msg->getPayload();
                },
                null, false, null, null, null, null, null
            );

            $connection->send(\GuzzleHttp\Psr7\Message::toString($response));
            $connection->handshaked = true;
            echo "WebSocket 握手成功: {$connection->getRemoteIp()}\n";

            return '';
        } catch (\Exception $e) {
            echo "握手失败: " . $e->getMessage() . "\n";
            $connection->close();
            return '';
        }
    }
}

// ==================== Worker 配置 ====================
$worker = new Worker('tcp://0.0.0.0:8486');
$worker->count = 4;
$worker->protocol = WebSocketRFC6455::class;

$worker->onMessage = function(TcpConnection $connection, $data) {
    echo "收到消息: [$data]\n";
    $connection->send("服务器回复: 收到你的消息 - $data");
};

$worker->onClose = function(TcpConnection $connection) {
    echo "连接关闭: {$connection->getRemoteIp()}\n";
};

Worker::runAll();

运行与测试

Step.1 启动服务器

php start.php start

Step.2 浏览器测试

打开浏览器的开发者工具(Console),运行以下 JavaScript:

var ws = new WebSocket('ws://127.0.0.1:8486'); 
ws.onmessage = function(event) {
    console.log('开源技术小栈接收消息: ' + event.data);
};

ws.send("开源技术小栈 你好!");

Step.3 调试输出

  • 连接成功:WebSocket 握手成功: 172.18.0.1
  • 收到消息:收到消息: [开源技术小栈 你好!]
  • 服务器回复:服务器回复: 收到你的消息 - 开源技术小栈 你好!

此时你的终端也会显示 Workerman 的启动信息和实时日志:

Workerman[start.php] start in DEBUG mode
-------------------------------------------- WORKERMAN --------------------------------------------
Workerman/5.2.2         PHP/8.4.15 (JIT off)          Linux/6.18.33.1-microsoft-standard-WSL2
--------------------------------------------- WORKERS ---------------------------------------------
event-loop  proto       user        worker      listen                count       state            
select      tcp         root        none        tcp://0.0.0.0:8486    4            [OK]            
---------------------------------------------------------------------------------------------------
Press Ctrl+C to stop. Start success.
WebSocket 握手成功: 172.18.0.1
收到消息: [开源技术小栈 你好!]

核心设计解析

  1. 握手阶段

    • decode() 返回空字符串,Workerman 会直接发送 101 响应,完成协议升级。
    • 握手成功后立即创建 MessageBuffer,这是 Ratchet 处理消息的核心组件。
  2. 消息处理

    • input() 告知 Workerman 可以消费完整的数据包。
    • decode() 调用 $connection->messageBuffer->onData($buffer),由其内部处理分片、掩码、Ping/Pong 等复杂逻辑。
    • onMessage 回调会自动触发,从队列中取出 getPayload(),得到的就是应用层可直接使用的消息内容。
  3. 编码

    • encode() 利用 Ratchet 的 Frame 类,将服务器要发送的纯文本数据封装成符合规范的 WebSocket 帧。
    • 握手阶段则透传 HTTP 响应,不做任何帧封装。

小结

Ratchet RFC6455 + Workerman 是 PHP 生态中相当优雅的 WebSocket 协议实现方式。它把协议层(RFC6455)与传输层(Workerman)完全解耦,让你能更专注于业务逻辑的开发。

当然,如果你只是想快速搭建一个聊天室或实时仪表盘,直接用 Workerman 内置的 websocket:// 协议会更快(大约 30 行代码即可搞定),实现更简单,稳定性也经过了大量验证。这是一种取舍——是用更底层的控制换取灵活性,还是用现成的封装换取开发效率。

结合 Ratchet 这种方式进行开源实战,能让你对 WebSocket 协议本身有更透彻的理解。更多关于协议细节的内容,则可以查阅相关的技术文档




上一篇:深夜惊醒的清洁老板:YC合伙人大骂多数人不懂的AI创业深耕法则
下一篇:开源终端AI编程工具OpenCode获7万Star:模型自由与终端原生体验的胜利
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-6-21 10:46 , Processed in 0.767492 second(s), 41 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表