• Swoole从入门到入土(20)——WebSocket服务器[协程版本]


    本篇让我们先用一段示例代码开路:

    <?php
    Co
    un(function () {
        $server = new CoHttpServer('0.0.0.0', 9501, false);
        $server->handle('/', function ($request, $ws) {
            $ws->upgrade();
            while (true) {
                $frame = $ws->recv();
                if ($frame === '') {
                    $ws->push("bye bye");
                    $ws->close();
                    break;
                } else if ($frame === false) {
                    echo "error : " . swoole_last_error() . "
    ";
                    break;
                } else {
                    if ($frame->data == 'close' || get_class($frame) === SwooleWebSocketCloseFrame::class) {
                        $ws->push("bye bye");
                        $ws->close();
                        return;
                    }
                    $ws->push("Hello {$frame->data}!");
                    $ws->push("How are you, {$frame->data}?");
                }
            }
        });
    
        $server->start();
    });

    这一段代码中,我们以协程的形式实例化了协程版本的http服务器。什么?你忘了什么是协程?那请先看这里==》传送门(Swoole从入门到入土(8)——协程初探)

    每当有个新的客户端使用websocket向服务端发起连接请求时,服务端都会调用handle中使用的回调函数(即,有几个客户端连接,就有几次回调函数的调用),这些函数共同运行在一个协程容器内部。在回调函数内部,基本的处理流程如下:

    · $ws->upgrade():向客户端发送 WebSocket 握手消息

    · while(true) 循环处理消息的接收和发送

    · $ws->recv() 接收 WebSocket 消息帧

    · $ws->push() 向对端发送数据帧

    · $ws->close() 关闭连接

    其中:$ws 是一个 SwooleHttpResponse 对象。

    接下来,让我们一起看一下$ws中每个成员函数的使用方法。

    1) 函数upgrade():发送 WebSocket 握手成功信息。此方法不要用于异步风格的服务器中。

    SwooleHttpResponse->upgrade(): bool

    2) 函数recv():接收 WebSocket 消息。此方法不要用于异步风格的服务器中,调用 recv 方法时会挂起当前协程,等待数据到来时再恢复协程的执行。

    SwooleHttpResponse->recv(double timeout = -1): SwooleWebSocketFrame | false | string

    返回值:

    成功收到消息,返回 SwooleWebSocketFrame 对象

    object(SwooleWebSocketFrame)#1 (4) {
      ["fd"]      =>  int(0)
      ["data"]    =>  NULL
      ["opcode"]  =>  int(1)
      ["finish"]  =>  bool(true)
    }

    失败返回 false,请使用 swoole_last_error() 获取错误码
    连接关闭返回空字符串

    3) 函数push():发送 WebSocket 数据帧。此方法不要用于异步风格的服务器中,发送大数据包时,需要监听可写,因此会引起多次协程切换。

    SwooleHttpResponse->push(string|object $data, int $opcode = 1, bool $finish = true): bool

    · string|object $data:要发送的内容。若传入的 $data 是 SwooleWebSocketFrame 对象则其后续参数会被忽略,支持发送各种帧类型

    · int $opcode:指定发送数据内容的格式 【默认为文本。发送二进制内容 $opcode 参数需要设置为 WEBSOCKET_OPCODE_BINARY】

    · bool $finish:是否发送完成

    4) 函数close():关闭 WebSocket 连接。此方法不要用于异步风格的服务器中,在 v4.4.15 以前版本会误报 Warning 忽略即可。

    SwooleHttpResponse->close(): bool

    最后,我们用群发的代码,来结束本篇内容。

    看下面这一段代码时,我们要注意,因为所有的回调函数是运行在同一个协程容器内部,即存在同一个进程中(甚至是同一个线程中),所以全局变量在这里是可以安心使用的

    function getObjectId(SwooleHttpResponse $response) {
        if (PHP_VERSION_ID < 70200) {
            $id = spl_object_hash($response);
        } else {
            $id = spl_object_id($response);
        }
        return $id;
    }
    
    Co
    un(function () {
        $server = new CoHttpServer('127.0.0.1', 9502, false);
        $server->handle('/websocket', function ($request, $ws) {
            $ws->upgrade();
            global $wsObjects;
            $objectId = getObjectId($ws);
            $wsObjects[$objectId] = $ws;
            while (true) {
                $frame = $ws->recv();
                if ($frame === '') {
                    unset($wsObjects[$objectId]);
                    $ws->close();
                    break;
                } else if ($frame === false) {
                    echo 'error : ' . swoole_last_error() . "
    ";
                    break;
                } else {
                    if ($frame->data == 'close' || get_class($frame) === SwooleWebSocketCloseFrame::class) {
                        unset($wsObjects[$objectId]);
                        $ws->close();
                        return;
                    }
                    foreach ($wsObjects as $obj) {
                        $obj->push("Server:{$frame->data}");
                    }
                }
            }
        });
        $server->start();
    });

    本篇内容到此结束,下一篇我们将一起了解swoole的毫秒定时器。

    ---------------------------  我是可爱的分割线  ----------------------------

    最后博主借地宣传一下,漳州编程小组招新了,这是一个面向漳州青少年信息学/软件设计的学习小组,有意向的同学点击链接,联系我吧。

  • 相关阅读:
    四则运算
    读书计划
    典型用户和场景分析
    课堂练习--电梯调度
    重力解锁--用户需求调研
    书籍促销活动优惠问题
    小组开发项目--NABC分析
    梦断代码读后感之终结篇
    结对开发-求环状二维数组最大子数组
    结对开发之大数溢出
  • 原文地址:https://www.cnblogs.com/ddcoder/p/14206971.html
Copyright © 2020-2023  润新知