topthink/think-swoole 擴展包的使用 之 WebSocket


嗯~ o(* ̄▽ ̄*)o,沒錯歡迎收看繼續爬坑系列233...話不多說直接開擼

今天的題材是websocket,沒有特殊說明的話默認環境都和此系列第一篇文章中申明一致,此后不再贅述。

websocket相關知識:

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

一、配置文件及源碼解讀

swoole.php

use Swoole\WebSocket\Server;
use think\facade\Env;// +----------------------------------------------------------------------
// | 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}\n";
    }
];

系統內定義了Message,Close回調,在此配置的是不會執行滴(上源碼)

 vendor\topthink\think-swoole\src\Http.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\topthink\think-swoole\src\Application.php

public function swooleWebSocket($server, $frame)
    {
        try {
            // 重置應用的開始時間和內存占用
            $this->beginTime = microtime(true);
            $this->beginMem  = memory_get_usage();

            // 銷毀當前請求對象實例
            $this->delete('think\Request');
            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 think\swoole;

use think\Container;

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 app\index\controller;

use think\Controller;
use think\facade\Hook;
use think\swoole\WebSocketFrame;

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}\n";
//        Hook::add('swoole_websocket_on_close', 'app\\http\\behavior\\SwooleWebsocketOnclose');
    }
}

 

自定義 onClose 

定義一個行為

<?php

namespace app\http\behavior;

class SwooleWebsocketOnClose
{
    public function run($params)
    {
        list($server, $fd, $reactorId) = $params;
        echo "client {$fd} closed\n";
    }
}

再行為綁定

當系統執行 

$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/ 啦。。。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM