easyswoole实现websocket的步骤解析
配置 Websocket服务
1 2 3 4 5 6 7 'MAIN_SERVER' => [ 'SERVER_TYPE' => EASYSWOOLE_WEB_SOCKET_SERVER, 'SETTING' => [ // 该参数项为心跳检测,严格参考swoole 配置说明 'heartbeat_check_interval' => 60, ], ],
fd与uuid绑定类App/Support/FdManager.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 <?php namespace App\Support; use EasySwoole\Component\Singleton; use Swoole\Table; /** * Class FdManager * @package App\Support */ class FdManager { use Singleton; /** * @var Table */ private $fdUuid ; /** * @var Table */ private $uuidFd ; /** * FdManager constructor. * @param int $size */ public function __construct(int $size = 1024 * 256) { $this ->fdUuid = new Table($size ); $this ->fdUuid->column('uuid' , Table::TYPE_STRING, 25); $this ->fdUuid->create(); $this ->uuidFd = new Table($size ); $this ->uuidFd->column('fd' , Table::TYPE_INT, 10); $this ->uuidFd->create(); } /** * fd 绑定 * @param int $fd * @param string $uuid * @return mixed */ public function bind (int $fd , $uuid ) { // TODO: Implement bind () method. $this ->fdUuid->set ($fd , ['uuid' => $uuid ]); $this ->uuidFd->set ($uuid , ['fd' => $fd ]); } /** * 删除fd绑定关系 * @param int $fd * @return mixed */ public function delete(int $fd ) { // TODO: Implement delete() method. $uuid = $this ->fdUuid($fd ); if ($uuid ) { $this ->uuidFd->del($uuid ); } $this ->fdUuid->del($fd ); } /** * 通过fd找到绑定的uuid * @param int $fd * @return mixed */ public function fdUuid(int $fd ) { // TODO: Implement fdUuid() method. $ret = $this ->fdUuid->get($fd ); if ($ret ) { return $ret ['uuid' ]; } else { return null; } } /** * 通过uuid找到fd * @param $uuid * @return mixed */ public function uuidFd($uuid ) { // TODO: Implement uuidFd() method. $ret = $this ->uuidFd->get($uuid ); if ($ret ) { return $ret ['fd' ]; } else { return null; } } }
webscoekt 事件类/App/WebSocket/WebSocketEvent.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 <?php namespace App\WebSocket; use App\Support\FdManager; /** * Class WebSocketEvent * @package App\WebSocket */ class WebSocketEvent { /** * 握手事件 * 所有客户端建立连接时触发的方法 * @param \swoole_http_request $request * @param \swoole_http_response $response * @return bool */ public function onHandShake(\swoole_http_request $request , \swoole_http_response $response ) { /** 此处自定义握手规则 返回 false 时中止握手 */ if (!$this ->customHandShake($request , $response )) { $response ->end(); return false ; } /** 此处是 RFC规范中的WebSocket握手验证过程 必须执行 否则无法正确握手 */ if ($this ->secWebsocketAccept($request , $response )) { $response ->end(); return true ; } $response ->end(); return false ; } /** * 关闭事件 * 所有客户端关闭时触发的方法 * @param \swoole_server $server * @param int $fd * @param int $reactorId */ public function onClose(\swoole_server $server , int $fd , int $reactorId ) { /** @var array $info */ $info = $server ->getClientInfo($fd ); /** * 判断此fd 是否是一个有效的 websocket 连接 * 参见 https://wiki.swoole.com/wiki/page/490.html */ if ($info && $info ['websocket_status' ] === WEBSOCKET_STATUS_FRAME) { /** * 判断连接是否是 server 主动关闭 * 参见 https://wiki.swoole.com/wiki/page/p-event/onClose.html */ if ($reactorId < 0) { } //删除绑定关系 FdManager::getInstance()->delete($fd ); } } /** * RFC规范中的WebSocket握手验证过程 * 以下内容必须强制使用 * * @param \swoole_http_request $request * @param \swoole_http_response $response * @return bool */ protected function secWebsocketAccept(\swoole_http_request $request , \swoole_http_response $response ): bool { // ws rfc 规范中约定的验证过程 if (!isset($request ->header['sec-websocket-key' ])) { // 需要 Sec-WebSocket-Key 如果没有拒绝握手 return false ; } if (0 === preg_match('#^[+/0-9A-Za-z]{21}[AQgw]==$#' , $request ->header['sec-websocket-key' ]) || 16 !== strlen(base64_decode($request ->header['sec-websocket-key' ])) ) { //不接受握手 return false ; } $key = base64_encode(sha1($request ->header['sec-websocket-key' ] . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11' , true )); $headers = array( 'Upgrade' => 'websocket' , 'Connection' => 'Upgrade' , 'Sec-WebSocket-Accept' => $key , 'Sec-WebSocket-Version' => '13' , 'KeepAlive' => 'off' , ); if (isset($request ->header['sec-websocket-protocol' ])) { $headers ['Sec-WebSocket-Protocol' ] = $request ->header['sec-websocket-protocol' ]; } // 发送验证后的header foreach ($headers as $key => $val ) { $response ->header($key , $val ); } // 接受握手 还需要101状态码以切换状态 $response ->status(101); // fd 和 uuid 进行绑定 FdManager::getInstance()->bind ($request ->fd, $request ->get['uuid' ]); return true ; } /** * 自定义握手事件 * @param \swoole_http_request $request * @param \swoole_http_response $response * @return bool */ private function customHandShake(\swoole_http_request $request , \swoole_http_response $response ) { return true ; } }
Webcoket解析类App/WebSocket/WebSocketParser.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 <?php namespace App\WebSocket; use EasySwoole\Socket\AbstractInterface\ParserInterface; use EasySwoole\Socket\Bean\Caller; use EasySwoole\Socket\Bean\Response; class WebSocketParser implements ParserInterface { public function decode($raw , $client ): ?Caller { // TODO: Implement decode() method. $caller = new Caller; if ($raw !== 'PING' ) { $payload = json_decode($raw , true ); $class = isset($payload ['controller' ]) ? $payload ['controller' ] : 'index' ; $action = isset($payload ['action' ]) ? $payload ['action' ] : 'actionNotFound' ; $params = isset($payload ['params' ]) ? (array)$payload ['params' ] : []; $controllerClass = "\\App\\WebSocket\\Controller\\" . ucfirst($class ); if (!class_exists($controllerClass )) $controllerClass = "\\App\\WebSocket\\Controller\\Index" ; $caller ->setClient($caller ); $caller ->setControllerClass($controllerClass ); $caller ->setAction($action ); $caller ->setArgs($params ); } else { // 设置心跳执行的类和方法 $caller ->setControllerClass(\App\WebSocket\Controller\Base::class); $caller ->setAction('heartbeat' ); } return $caller ; } public function encode(Response $response , $client ): ?string { // TODO: Implement encode() method. return $response ->getMessage(); } }
注册 websocket服务 EasySwooleEvent.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public static function mainServerCreate(EventRegister $register ) { $conf = new \EasySwoole\Socket\Config(); //设置Dispatcher为WebSocket 模式 $conf ->setType(\EasySwoole\Socket\Config::WEB_SOCKET); try { $conf ->setParser(new \App\WebSocket\WebSocketParser());//设置解析器对象 $dispatch = new \EasySwoole\Socket\Dispatcher($conf );//创建Dispatcher对象并注入config对象 } catch (\Exception $e ) { Log::error($e ->getMessage()); } //给server注册相关事件在WebSocket模式下onMessage事件必须注册 并且交给Dispatcher对象处理 $register ->set (EventRegister::onMessage, function (\swoole_websocket_server $server , \swoole_websocket_frame $frame ) use ($dispatch ) { $dispatch ->dispatch($server , $frame ->data, $frame ); }); $websocketEvent = new \App\WebSocket\WebSocketEvent(); //自定义握手事件 $register ->set (EventRegister::onHandShake, function (\swoole_http_request $request , \swoole_http_response $response ) use ($websocketEvent ) { $websocketEvent ->onHandShake($request , $response ); }); //自定义关闭事件 $register ->set (EventRegister::onClose, function (\swoole_server $server , int $fd , int $reactorId ) use ($websocketEvent ) { $websocketEvent ->onClose($server , $fd , $reactorId ); }); }
创建控制器目录,让easyswoole 的websocket服务像http服务那样调用方便
基类控制器App/WebSocket/Controller/Base.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php namespace App\WebSocket\Controller; use EasySwoole\Socket\AbstractInterface\Controller; /** * Class Base * @package App\WebSocket\Controller */ class Base extends Controller { /** * 心跳执行的方法 * 该方法建议 迁移到 基类控制器 Base 中 * 推荐使用 easyswoole 自带的websocket客户端调试 * http://www.easyswoole.com/wstool.html */ public function heartbeat() { $this->response()->setMessage('心跳 heartbeat'); } /** * @param string|null $actionName */ protected function actionNotFound(?string $actionName) { $this->response()->setMessage($actionName . ' not find'); // 推送消息 } }
测试控制器App/WebSocket/Controller/Test.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 <?php namespace App\WebSocket\Controller; class Test extends Base { public function index () { $fd = $this ->caller ()->getClient()->getFd();// 请求用户的fd $data = $this ->caller ()->getArgs(); // 获取请求参数 // \EasySwoole\EasySwoole\ServerManager::getInstance()->getSwooleServer()->worker_id //发送响应消息 $this ->response()->setMessage("响应消息" ); $server = ServerManager::getInstance()->getSwooleServer(); //推送消息 $server ->push($fd , “要推送的消息”); } }