嗯~ 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/ 啦。。。