关于swoole:2022112thinkswoole使用教程

3次阅读

共计 5470 个字符,预计需要花费 14 分钟才能阅读完成。

think-swoole 应用教程

核心思想是 swoole 只是作为一个音讯转发器,业务逻辑还是通过接口来实现,发送音讯也是应用接口,客户端 websocket 只负责创立和监听承受音讯即可。

环境

  1. centos8
  2. PHP7.4
  3. thinkphp6.0.10
  4. think-swoole4.0.6

开发过程

  1. 装置 think-swoole 扩大
  2. 为了不便咱们装置 think-view 扩大
  3. 配置 swoole.php 文件

    1. server.host 服务器 IP
    2. server.port 服务器端口
    3. server.options.daemonize 是否过程
    4. websocket.enable 关上 websocket
    5. websocket.handle 本人接管或者应用默认(默认的会给咱们发送 socket 音讯,不理睬即可)
    6. websocket.subscribe 创立事件订阅,我这里的文件名是 WebSocketEvent(也能够应用监听,只不过须要多个文件)
    7. 因为是多过程,咱们须要共享变量,能够用 MySQL、redis 等,咱们这里应用 swoole 的共享内容 Table,因为同一个用户可能是多端登录,咱们创立俩个 Table,一个是用户映射 fd,一个是 fd 映射用户,Table 的映射是一对一的,然而一个用户可能有多个 fd,所以用户映射 fd 的 Table 的值应用逗号分隔的多个值,例如用户 1 ->fd1,fd2
    8. 配置 tables 俩个 table,别离是 m2fd、fd2m,thinkphp 实现的 Table 如何应用请本人看代码

      'tables'     => [
          'm2fd' => [
              'size' => 102400,
              'columns' => [['name' => 'fd', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 50]
              ]
          ],
          'fd2m' => [
              'size' => 102400,
              'columns' => [['name' => 'member_id', 'type' => \Swoole\Table::TYPE_INT]
              ]
          ],
      ],
  4. 通过订阅实现 websocket 逻辑
  5. 把咱们须要应用的类通过构造函数依赖注入,方便使用
  6. 咱们须要 WebSocket 类实现通信逻辑,Table 类实现用户 fd 映射
  7. 如果咱们应用了 type 为 11 的绑定形式,则订阅 open 事件,发送给客户端
  8. message 事件办法体留空或者不写即可,咱们应用接口来实现逻辑
  9. close 事件移除用户和 fd 的映射关系
  10. 咱们定义一个事件,用于接口触发,从而实现发送音讯逻辑,事件名称叫做 ApiEvent,代码如下

    <?php
    declare (strict_types = 1);
    
    namespace app\subscribe;
    
    use app\Request;
    use Swoole\Server;
    use think\swoole\Table;
    use think\swoole\Websocket;
    
    class WebSocketEvent
    {
     private $websocket = null;
     private $m2fd = null;
     private $fd2m = null;
    
     public function __construct(Websocket $websocket, Table $table)
     {
         $this->websocket = $websocket;
         $this->m2fd = $table->get('m2fd');
         $this->fd2m = $table->get('fd2m');
     }
    
     // 这里之所以注入一个申请,是因为如果咱们不必 type=11 这种形式绑定,则能够通过 new WebSocket 的时候把用户 ID 传递过去,而后间接实现绑定
     public function onOpen(Request $request)
     {$currentFd = $this->websocket->getSender();
         $data = [
             'type' => 11,
             'fd' => $currentFd
         ];
         $this->websocket->push(json_encode($data));
     }
    
     public function onClose()
     {$currentFd = $this->websocket->getSender();
         // 通过 fd 找到用户 ID
         $memberId = $this->fd2m->get((string)$currentFd, 'member_id');
         // 如果没有找到映射,就阐明没有绑定过,就什么不做,找到的话就解除绑定
         if ($memberId) {$this->fd2m->del((string)$currentFd);
    
             // 依据用户 ID 找到映射的所有 fd,而后把存在的以后 fd 移除掉
             $fds = $this->m2fd->get((string)$memberId, 'fd');
             if ($fds) {$fdArray = explode(',', $fds);
                 $key = array_search($currentFd, $fdArray);
                 unset($fdArray[$key]);
                 if ($fdArray) {$resFds = implode(',', $fdArray);
                     $this->m2fd->set((string)$memberId, $resFds);
                 } else {$this->m2fd->del((string)$memberId);
                 }
             }
         }
     }
    
     public function onApiEvent($data)
     {
         // $data 是接口传递过去的参数,如果是 11 则实现绑定,是 5 就转发给 from_id 和 to_id
         if ($data['type'] == 11) {
             // m2fd、fd2m 俩个 Table 的映射
             $this->fd2m->set((string)$data['fd'], ['member_id' => $data['member_id']]);
             // 先查找该用户 ID 是否曾经绑定过其它 fd 了
             $fds = $this->m2fd->get((string)$data['member_id'], 'fd');
             if (!$fds) {$this->m2fd->set((string)$data['member_id'], ['fd' => $data['fd']]);
             } else {
                 // 看看 fd 是否在曾经映射的 fd 中,如果在就什么都不做,如果不在就追加到前面
                 $fdArray = explode(',', $fds);
                 if (!in_array($data['fd'], $fdArray)) {$this->m2fd->set((string)$data['member_id'], ['fd' => $fds . ',' . $data['fd']]);
                 }
             }
         }
    
         if ($data['type'] == 1) {
             // 依据 from_id 和 to_id 俩个用户 ID 找到对应的 fd,而后发送音讯
             $fromFds = $this->m2fd->get((string)$data['from_id'], 'fd');
             $toFds = $this->m2fd->get((string)$data['to_id'], 'fd');
             $fromFdArray = $toFdArray = [];
             if ($fromFds) {$fromFdArray = explode(',', $fromFds);
             }
             if ($toFds) {$toFdArray = explode(',', $toFds);
             }
             // 合并所有发送者 fd 和接受者 fd,之所以发送给发送者,一方面是简化前端工作,前端只须要承受 websocket 音讯即可,另一方面,多端的话其它端能够能够即时看到聊天记录
             $allFdArray = array_unique(array_merge($fromFdArray, $toFdArray));
             // 发送音讯
              $this->websocket->to($allFdArray)->push(json_encode($data));
         }
     }
    }
    
    
  11. 接口实现代码如下

    <?php
    namespace app\controller;
    
    class Index
    {
     // 为了演示不便咱们不应用路由了,应用控制器办法的形式拜访
     public function index()
     {
         // 聊天页面
         return view();}
    
     // 如果应用 type= 1 的绑定形式就要,否则这个能够不要
     public function bindMember()
     {
         // 用户 ID 原本是要从登录状态中获取的,这里咱们是模仿演示,就让前端传
         $params = request()->only(['member_id', 'fd']);
         // 触发 ApiEvent 事件,组装数据 type=11、member_id、fd
         $data = $params;
         $data['type'] = 11;
         event('swoole.websocket.ApiEvent', $data);
         // 只有不抛异样就是绑定胜利了
         $res = [
             'code' => 1,
             'msg' => '绑定胜利'
         ];
         return json($res);
     }
    
     // 咱们只写一个发送文字音讯的例子
     public function sendMessage()
     {
         // 其它须要的字段咱们就不写了,本人实现即可,长久化到数据库逻辑也不写了,只是写音讯发送
         $params = request()->only(['from_id', 'to_id', 'content']);
         $data = $params;
         $data['type'] = 1;
         event('swoole.websocket.ApiEvent', $data);
         // 不抛异样就是胜利了
         $res = [
             'code' => 1,
             'msg' => '发送音讯胜利'
         ];
         return json($res);
     }
    }
    
  12. 聊天页面代码

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport"
         content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title> 聊天页面 </title>
      <style>
     #chat {
       height: 400px;
       width: 400px;
       background: gray;
     }
      </style>
    </head>
    <body>
    <div id="chat"></div>
    <button id="connect"> 链接 websocket 服务端 </button>
    <input type="text" id="content" value="内容" placeholder="聊天内容">
    <input type="text" id="to" placeholder="指标对象" value="1">
    <input type="text" id="from" placeholder="发送对象" value="1">
    <button id="submit"> 发送 </button>
    
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script>
      var ws = null;
    
      // 链接 websocket
      $("#connect").click(function () {
     // TODO 改成本人的域名即可
     ws = new WebSocket("ws://swoole.dreamphp.com.cn:8282");
    
     ws.onmessage = function (res) {var data = JSON.parse(res.data);
       //  如果没有接管 Handler,则音讯格局就跟咱们的不一样,可能会报错,不必理睬的
       if (data.type == 11) {
         $.ajax({url: "{:url('index/bindMember')}",
           type: "post",
           data: {member_id: $("#from").val(), fd: data.fd},
           dataType: "json",
           success: function (res) {console.log(res);
           }
         });
       }
       if (data.type == 1) {$("#chat").append("用户" + data.from_id + ":" + data.content + "<br>");
       }
     };
     return false;
      });
    
      // 发送内容
      $("#submit").click(function () {var toId = $("#to").val();
     var fromId = $("#from").val();
     var content = $("#content").val();
     $.ajax({url: "{:url('index/sendMessage')}",
       type: "post",
       data: {from_id: fromId, to_id: toId, content: content},
       dataType: "json",
       success: function (res) {console.log(res)
       }
     });
     return false;
      });
    </script>
    </body>
    </html>

type 阐明(type 为 1 能够去掉,能够换成 new websocket 的时候就间接绑定)

type 阐明 额定阐明
11 告诉用户要绑定了 fd
1 聊天音讯 音讯类型具体阐明

音讯类型具体阐明

正文完
 0