zoukankan      html  css  js  c++  java
  • topthink/think-swoole 扩展包的使用 之 WebSocket

    嗯~ o(* ̄▽ ̄*)o,没错欢迎收看继续爬坑系列233...话不多说直接开撸

    今天的题材是websocket,没有特殊说明的话默认环境都和此系列第一篇文章中申明一致,此后不再赘述。

    websocket相关知识:

    http://www.ruanyifeng.com/blog/2017/05/websocket.html

    一、配置文件及源码解读

    swoole.php

    use SwooleWebSocketServer;
    use thinkfacadeEnv;// +----------------------------------------------------------------------
    // | Swoole设置 php think swoole命令行下有效
    // +----------------------------------------------------------------------
    return [
        // 扩展自身配置
        'host'                  => '0.0.0.0', // 监听地址
        'port'                  => 9501, // 监听端口
        'mode'                  => '', // 运行模式 默认为SWOOLE_PROCESS
        'sock_type'             => '', // sock type 默认为SWOOLE_SOCK_TCP
        'server_type'           => 'websocket', // 服务类型 支持 http websocket
        'app_path'              => '', // 应用地址 如果开启了 'daemonize'=>true 必须设置(使用绝对路径)
        'file_monitor'          => false, // 是否开启PHP文件更改监控(调试模式下自动开启)
        'file_monitor_interval' => 2, // 文件变化监控检测时间间隔(秒)
        'file_monitor_path'     => [], // 文件监控目录 默认监控application和config目录
    
        // 可以支持swoole的所有配置参数
        'pid_file'              => Env::get('runtime_path') . 'swoole.pid',
        'log_file'              => Env::get('runtime_path') . 'swoole.log',
        'document_root'         => Env::get('root_path') . 'public',
        'enable_static_handler' => true,
        'timer'                 => true,//是否开启系统定时器
        'interval'              => 500,//系统定时器 时间间隔
        'task_worker_num'       => 1,//swoole 任务工作进程数量
    
        /**
         * 注意:系统内定义了Message,Close回调,在此配置的是不会执行滴
         */
        'Open' => function (Server $server, $request) {
            echo "server: handshake success with fd {$request->fd}
    ";
        }
    ];

    系统内定义了Message,Close回调,在此配置的是不会执行滴(上源码)

     vendor opthink hink-swoolesrcHttp.php

    <?php
    
        public function option(array $option)
        {
            // 设置参数
            if (!empty($option)) {
                $this->swoole->set($option);
            }
    
            foreach ($this->event as $event) {
                // 自定义回调
                if (!empty($option[$event])) {
                    $this->swoole->on($event, $option[$event]);
                } elseif (method_exists($this, 'on' . $event)) {
                    $this->swoole->on($event, [$this, 'on' . $event]);
                }
            }
            if ("websocket" == $this->server_type) {
                foreach ($this->event as $event) {
                    if (method_exists($this, 'Websocketon' . $event)) {
                        $this->swoole->on($event, [$this, 'Websocketon' . $event]);
                    }
                }
            }
        }
        
        /**
         * Message回调
         * @param $server
         * @param $frame
         */
        public function WebsocketonMessage($server, $frame)
        {
            // 执行应用并响应
            $this->app->swooleWebSocket($server, $frame);
        }
    
        /**
         * Close
         */
        public function WebsocketonClose($server, $fd, $reactorId)
        {
            $data = [$server, $fd, $reactorId];
            $hook = Container::get('hook');
            $hook->listen('swoole_websocket_on_close', $data);
        }

    看到标红的那段没,就算你设置了也会被覆盖掉,而是用内置的  WebsocketonMessage() 、WebsocketonClose() 这两个方法,不过不用担心,既然直接定义不可以那就绕一下呗。

    先看 WebsocketonMessage() 会执行swooleWebSocket()方法:

    vendor opthink hink-swoolesrcApplication.php

    public function swooleWebSocket($server, $frame)
        {
            try {
                // 重置应用的开始时间和内存占用
                $this->beginTime = microtime(true);
                $this->beginMem  = memory_get_usage();
    
                // 销毁当前请求对象实例
                $this->delete('thinkRequest');
                WebSocketFrame::destroy();
                $request = $frame->data;
                $request = json_decode($request, true);
    
                // 重置应用的开始时间和内存占用
                $this->beginTime = microtime(true);
                $this->beginMem  = memory_get_usage();
                WebSocketFrame::getInstance($server, $frame);
    
                $_COOKIE = isset($request['arguments']['cookie']) ? $request['arguments']['cookie'] : [];
                $_GET    = isset($request['arguments']['get']) ? $request['arguments']['get'] : [];
                $_POST   = isset($request['arguments']['post']) ? $request['arguments']['post'] : [];
                $_FILES  = isset($request['arguments']['files']) ? $request['arguments']['files'] : [];
    
                $_SERVER["PATH_INFO"]       = $request['url'] ?: '/';
                $_SERVER["REQUEST_URI"]     = $request['url'] ?: '/';
                $_SERVER["SERVER_PROTOCOL"] = 'http';
                $_SERVER["REQUEST_METHOD"]  = 'post';
    
                // 重新实例化请求对象 处理swoole请求数据
                $this->request
                    ->withServer($_SERVER)
                    ->withGet($_GET)
                    ->withPost($_POST)
                    ->withCookie($_COOKIE)
                    ->withFiles($_FILES)
                    ->setBaseUrl($request['url'])
                    ->setUrl($request['url'])
                    ->setHost(Config::get("app_host"))
                    ->setPathinfo(ltrim($request['url'], '/'));
    
                // 更新请求对象实例
                $this->route->setRequest($this->request);
    
                $resp = $this->run();
                $resp->send();
    
            } catch (HttpException $e) {
                $this->webSocketException($server, $frame, $e);
            } catch (Exception $e) {
                $this->webSocketException($server, $frame, $e);
            } catch (Throwable $e) {
                $this->webSocketException($server, $frame, $e);
            }
        }

    还是注意标红标粗的那几段,他把onMessage的参数传给了 WebSocketFrame 类,且从获取的参数看,传递参数是有要求的,继续走着

    <?php
    /**
     * 参考think-swoole2.0开发
     * author:xavier
     * email:49987958@qq.com
     * 可以配合https://github.com/xavieryang007/xavier-swoole/blob/master/src/example/websocketclient.js 使用
     */
    namespace thinkswoole;
    
    use thinkContainer;
    
    class WebSocketFrame implements ArrayAccess
    {
        private static $instance = null;
        private $server;
        private $frame;
        private $data;
    
        public function __construct($server, $frame)
        {
            $this->server = $server;
            $this->data   = null;
            if (!empty($frame)) {
                $this->frame  = $frame;
                $this->data   = json_decode($this->frame->data, true);
            }
        }
    
        public static function getInstance($server = null, $frame = null)
        {
            if (empty(self::$instance)) {
                if (empty($server)) {
                    $swoole = Container::get('swoole');
                    $server = $swoole;
                }
                self::$instance = new static($server, $frame);
            }
            return self::$instance;
        }
    
        public static function destroy()
        {
            self::$instance = null;
        }
    
        public function getServer()
        {
            return $this->server;
        }
    
        public function getFrame()
        {
            return $this->frame;
        }
    
        public function getData()
        {
            return $this->data;
        }
    
        public function getArgs()
        {
            return isset($this->data['arguments']) ? $this->data['arguments'] : null;
        }
    
        public function __call($method, $params)
        {
            return call_user_func_array([$this->server, $method], $params);
        }
    
        public function pushToClient($data, $event = true)
        {
            if ($event) {
                $eventname = isset($this->data['event']) ? $this->data['event'] : false;
                if ($eventname) {
                    $data['event'] = $eventname;
                }
            }
            $this->sendToClient($this->frame->fd, $data);
        }
    
        public function sendToClient($fd, $data)
        {
            if (is_string($data)) {
                $this->server->push($fd, $data);
            } elseif (is_array($data)) {
                $this->server->push($fd, json_encode($data));
            }
        }
    
        public function pushToClients($data)
        {
            foreach ($this->server->connections as $fd) {
                $this->sendToClient($fd, $data);
            }
        }
    
        public function offsetSet($offset, $value)
        {
            $this->data[$offset] = $value;
        }
    
        public function offsetExists($offset)
        {
            return isset($this->data[$offset]) ? true : false;
        }
    
        public function offsetUnset($offset)
        {
            unset($this->data[$offset]);
        }
    
        public function offsetGet($offset)
        {
            return isset($this->data[$offset]) ? $this->data[$offset] : null;
        }
    }

    看源码后你发现,你要获取onMessage的数据,就需要用这个类咯。

    再看 WebsocketonClose() 方法:

    $hook->listen('swoole_websocket_on_close', $data);

    直接是调用了 一个 swoole_websocket_on_close 钩子,那么这就好办了,咱只要添加这个钩子就能获取 onClose 的数据了。

    二、实操

    至此,就可以变向的自定义 onMessage, onClose 了,上个 demo....

    Index.php 控制器 (自定义onMessage)

    <?php
    
    namespace appindexcontroller;
    
    use thinkController;
    use thinkfacadeHook;
    use thinkswooleWebSocketFrame;
    
    class Index extends Controller
    {
        // 自定义 onMessage 
        public function websocket()
        {
            $server = WebSocketFrame::getInstance()->getServer();
            $frame = WebSocketFrame::getInstance()->getFrame();
            $data = WebSocketFrame::getInstance()->getData();
            $server->push($frame->fd, json_encode($data));
            echo "receive from {$frame->fd}:{$frame->data},opcode:{$frame->opcode},fin:{$frame->finish}
    ";
    //        Hook::add('swoole_websocket_on_close', 'app\http\behavior\SwooleWebsocketOnclose');
        }
    }

    自定义 onClose 

    定义一个行为

    <?php
    
    namespace apphttpehavior;
    
    class SwooleWebsocketOnClose
    {
        public function run($params)
        {
            list($server, $fd, $reactorId) = $params;
            echo "client {$fd} closed
    ";
        }
    }

    再行为绑定

    当系统执行 

    $hook->listen('swoole_websocket_on_close', $data);

     时,就会调用行为中的 run() 方法。

    三、执行

    首先需要一个客户端:

    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>websocket client</title>
    </head>
    <button type="button" id="send">发送</button>
    <body>
    <h1>swoole-ws测试</h1>
    <script>
        var wsUrl = 'ws://tp-live.test:9501';
        var websocket = new WebSocket(wsUrl);
    
        websocket.onopen = function (evt) {
            console.log("ws client 连接成功!");
        };
    
        document.getElementById('send').onclick = function () {
            websocket.send('{"url":"index/index/websocket"}');
            console.log('ws client 发送数据');
        };
    
        websocket.onmessage = function (evt) {
            console.log("server return data: " + evt.data);
        };
    
        websocket.onclose = function (evt) {
            console.log("connect close");
        };
    
        websocket.onerror = function (evt, e) {
            console.log("error: " + evt.data);
        }
    </script>
    </body>
    
    </html>

    整个过程,服务端输出结果:

     四、结束语

    还有一种方法就是使用 swoole_server.php 的配置

    具体方法可参照 : https://somsan.cc/archives/5a303a8b/ 

    咳咳,保温杯里泡枸杞,书写不易,转载请申明出处 https://www.cnblogs.com/cshaptx4869/ 啦。。。

  • 相关阅读:
    fiddler教程--拦截并篡改
    string
    希尔排序(缩小增量排序)
    k-选取问题
    完善版本散列表
    c++ BST继承自二叉树
    DEDECMS与DZ论坛如何整合,DEDE网站调用DZ内容
    DEDECMS如何实现定时发布文章
    DEDECMS如何设置图片上传加水印
    DEDE如何制作不同背景的列表
  • 原文地址:https://www.cnblogs.com/cshaptx4869/p/11367137.html
Copyright © 2011-2022 走看看