WebSocket控制器

EasySwoole 2.x支持以控制器模式来开发你的代码。

首先,修改项目根目录下配置文件Config.php,修改SERVER_TYPE为:

  1. \EasySwoole\Core\Swoole\ServerManager::TYPE_WEB_SOCKET_SERVER

新人帮助

  • 本文遵循PSR-4自动加载类规范,如果你还不了解这个规范,请先学习相关规则。
  • 本节基础命名空间App 默认指项目根目录下Application文件夹,如果你的App指向不同,请自行替换。
  • 只要遵循PSR-4规范,无论你怎么组织文件结构都没问题,本节只做简单示例。

实现命令解析

新人提示

这里的命令解析,其意思为根据请求信息解析为具体的执行命令;

在easyswoole中,可以让WebSocket像传统框架那样按照控制器->方法这样去解析请求;

需要实现EasySwoole\Core\Socket\AbstractInterface\ParserInterface接口中的decode 和encode方法;

创建App/Parser.php文件,写入以下代码

  1. namespace App;
  2. use EasySwoole\Core\Socket\AbstractInterface\ParserInterface;
  3. use EasySwoole\Core\Socket\Common\CommandBean;
  4. class Parser implements ParserInterface
  5. {
  6. public static function decode($raw, $client)
  7. {
  8. // TODO: Implement decode() method.
  9. $command = new CommandBean();
  10. $json = json_decode($raw,1);
  11. $command->setControllerClass(\App\WebSocket\Test::class);
  12. $command->setAction($json['action']);
  13. $command->setArg('content',$json['content']);
  14. return $command;
  15. }
  16. public static function encode(string $raw, $client): ?string
  17. {
  18. // TODO: Implement encode() method.
  19. return $raw;
  20. }
  21. }

注意,请按照你实际的规则实现,本测试代码与前端代码对应。

注册服务

新人提示

如果你尚未明白easyswoole运行机制,那么这里你简单理解为,当easyswoole运行到一定时刻,会执行以下方法。

这里是指注册你上面实现的解析器。

在根目录下EasySwooleEvent.php文件mainServerCreate方法下加入以下代码

  1. //注意:在此文件引入以下命名空间
  2. use \EasySwoole\Core\Swoole\EventHelper;
  3. public static function mainServerCreate(ServerManager $server,EventRegister $register): void
  4. {
  5. // TODO: Implement mainServerCreate() method.
  6. EventHelper::registerDefaultOnMessage($register,\App\Parser::class);
  7. }

在EasySwooleEvent中注册该服务。

测试前端代码

友情提示

easyswoole 提供了更强大的WebSocket调试工具,[foo]: http://www.evalor.cn/websocket.html ‘WEBSOCKET CLIENT’;

创建Application/HttpController/websocket.html文件,写入以下代码

  1. <html>
  2. <head>
  3. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  4. </head>
  5. <body>
  6. <div>
  7. <div>
  8. <p>info below</p>
  9. <ul id="line">
  10. </ul>
  11. </div>
  12. <div>
  13. <select id="action">
  14. <option value="who">who</option>
  15. <option value="hello">hello</option>
  16. <option value="delay">delay</option>
  17. <option value="404">404</option>
  18. </select>
  19. <input type="text" id="says">
  20. <button onclick="say()">发送</button>
  21. </div>
  22. </div>
  23. </body>
  24. <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
  25. <script>
  26. var wsServer = 'ws://127.0.0.1:9501';
  27. var websocket = new WebSocket(wsServer);
  28. window.onload = function () {
  29. websocket.onopen = function (evt) {
  30. addLine("Connected to WebSocket server.");
  31. };
  32. websocket.onclose = function (evt) {
  33. addLine("Disconnected");
  34. };
  35. websocket.onmessage = function (evt) {
  36. addLine('Retrieved data from server: ' + evt.data);
  37. };
  38. websocket.onerror = function (evt, e) {
  39. addLine('Error occured: ' + evt.data);
  40. };
  41. };
  42. function addLine(data) {
  43. $("#line").append("<li>"+data+"</li>");
  44. }
  45. function say() {
  46. var content = $("#says").val();
  47. var action = $("#action").val();
  48. $("#says").val('');
  49. websocket.send(JSON.stringify({
  50. action:action,
  51. content:content
  52. }));
  53. }
  54. </script>
  55. </html>

测试用HttpController 视图控制器

新人提示

这里仅提供了前端基本的示例代码,更多需求根据自己业务逻辑设计

创建Application/HttpController/Index.php文件,写入以下代码

  1. namespace App\HttpController;
  2. use EasySwoole\Core\Http\AbstractInterface\Controller;
  3. use EasySwoole\Core\Swoole\ServerManager;
  4. class Index extends Controller
  5. {
  6. function index()
  7. {
  8. // TODO: Implement index() method.
  9. $content = file_get_contents(__DIR__.'/websocket.html');
  10. $this->response()->write($content);
  11. }
  12. /*
  13. * 请调用who,获取fd
  14. * http://ip:9501/push/index.html?fd=xxxx
  15. */
  16. function push()
  17. {
  18. $fd = intval($this->request()->getRequestParam('fd'));
  19. $info = ServerManager::getInstance()->getServer()->connection_info($fd);
  20. if(is_array($info)){
  21. ServerManager::getInstance()->getServer()->push($fd,'push in http at '.time());
  22. }else{
  23. $this->response()->write("fd {$fd} not exist");
  24. }
  25. }
  26. }

本控制器主要为方便你获得前端页面和从HTTP请求中对websocket 做推送。

WebSocket 控制器

新人提示

WebSocket控制器必须继承EasySwoole\Core\Socket\AbstractInterface\WebSocketController;

actionNotFound方法提供了当找不到该方法时的返回信息,默认会传入本次请求的actionName。

创建Application/WebSocket/Test.php文件,写入以下内容

  1. namespace App\WebSocket;
  2. use EasySwoole\Core\Socket\Response;
  3. use EasySwoole\Core\Socket\AbstractInterface\WebSocketController;
  4. use EasySwoole\Core\Swoole\Task\TaskManager;
  5. class Test extends WebSocketController
  6. {
  7. function actionNotFound(?string $actionName)
  8. {
  9. $this->response()->write("action call {$actionName} not found");
  10. }
  11. function hello()
  12. {
  13. $this->response()->write('call hello with arg:'.$this->request()->getArg('content'));
  14. }
  15. public function who(){
  16. $this->response()->write('your fd is '.$this->client()->getFd());
  17. }
  18. function delay()
  19. {
  20. $this->response()->write('this is delay action');
  21. $client = $this->client();
  22. //测试异步推送
  23. TaskManager::async(function ()use($client){
  24. sleep(1);
  25. Response::response($client,'this is async task res'.time());
  26. });
  27. }
  28. }

测试

如果你按照本文配置,那么你的文件结构应该是以下形式

Application
|—-|HttpController
|—-|—-|Index.php
|—-|—-|websocket.html
|—-|WebSocket
|—-|—-|Test.php
|—-|Parser.php

首先在根目录运行easyswoole

php easyswoole start

如果没有错误此时已经启动了easyswoole服务;
访问127.0.0.1:9501/Index/index 可以看到之前写的测试html文件;
新人提示:这种访问方式会请求HttpController控制器下Index.php中的index方法

扩展

自定义解析器

在上文的Parser.php中,已经实现了一个简单解析器;
我们可以通过自定义解析器,实现自己需要的场景。

  1. public function decode($raw, $client)
  2. {
  3. // TODO: Implement decode() method.
  4. $CommandBean = new CommandBean();
  5. //这里的$raw是请求服务器的信息,你可以自行设计,这里使用了JSON字符串的形式。
  6. $commandLine = json_decode($raw, true);
  7. //这里会获取JSON数据中class键对应的值,并且设置一些默认值
  8. //当用户传递class键的时候,会去App/WebSocket命名空间下寻找类
  9. $control = isset($commandLine['class']) ? 'App\\WebSocket\\'. ucfirst($commandLine['class']) : '';
  10. $action = $commandLine['action'] ?? 'none';
  11. $data = $commandLine['data'] ?? null;
  12. //先检查这个类是否存在,如果不存在则使用Index默认类
  13. $CommandBean->setControllerClass(class_exists($control) ? $control : Index::class);
  14. //检查传递的action键是否存在,如果不存在则访问默认方法
  15. $CommandBean->setAction(class_exists($control) ? $action : 'controllerNotFound');
  16. $CommandBean->setArg('data', $data);
  17. return $CommandBean;
  18. }

例如{“class”:”Test”,”action”:”hello”}
则会访问Application/WebSocket/Test.php 并执行hello方法

当然这里是举例,你可以根据自己的业务场景进行设计