联机逻辑开发进度:■■■■■□□□□□□□
本章结束开发进度:■■■■■■■□□□□□
上一章的答案:
DataCenter
类:
<?php...class DataCenter{ const PREFIX_KEY = "game"; ... public static function getPlayerWaitListLen() { $key = self::PREFIX_KEY . ":player_wait_list"; return self::redis()->lLen($key); } public static function pushPlayerToWaitList($playerId) { $key = self::PREFIX_KEY . ":player_wait_list"; self::redis()->lPush($key, $playerId); } public static function popPlayerFromWaitList() { $key = self::PREFIX_KEY . ":player_wait_list"; return self::redis()->rPop($key); } public static function getPlayerFd($playerId) { $key = self::PREFIX_KEY . ":player_fd:" . $playerId; return self::redis()->get($key); } public static function setPlayerFd($playerId, $playerFd) { $key = self::PREFIX_KEY . ":player_fd:" . $playerId; self::redis()->set($key, $playerFd); } public static function delPlayerFd($playerId) { $key = self::PREFIX_KEY . ":player_fd:" . $playerId; self::redis()->del($key); } public static function getPlayerId($playerFd) { $key = self::PREFIX_KEY . ":player_id:" . $playerFd; return self::redis()->get($key); } public static function setPlayerId($playerFd, $playerId) { $key = self::PREFIX_KEY . ":player_id:" . $playerFd; self::redis()->set($key, $playerId); } public static function delPlayerId($playerFd) { $key = self::PREFIX_KEY . ":player_id:" . $playerFd; self::redis()->del($key); } public static function setPlayerInfo($playerId, $playerFd) { self::setPlayerId($playerFd, $playerId); self::setPlayerFd($playerId, $playerFd); }}
我们先来测试一下,前面所写的代码有没有问题,重新运行Server.php
,并在浏览器打开游戏前端页面。
查看Redis
中的键值:
127.0.0.1:6379> keys *1) "game:player_fd:player_177"2) "game:player_id:9"
可以看到,player_id
和player_fd
都已经保存下来了。
发送一个匹配
请求,并查看Redis
中的键值:
127.0.0.1:6379> keys *1) "game:player_fd:player_177"2) "game:player_wait_list"3) "game:player_id:9"127.0.0.1:6379> lrange game:player_wait_list 0 -11) "player_177"
可以看到,匹配队列game:player_wait_list
中已经成功存入了player_177
。
目前我们的匹配机制已经完成了:
- 前端连接时发送
player_id
- 服务端连接时保存玩家信息
- 前端发送
code
为600
的指令 - 服务端将
player_id
放入匹配队列
剩下的操作就是:
- 检测匹配队列长度,当长度大于等于2时,创建游戏房间
异步检测匹配队列
我们大部分游戏逻辑都是运行在worker
里的,异步的玩家匹配可以减轻主程序worker
的负担。关于Task
机制不了解的童鞋,请先熟悉一下官方文档。
Swoole Task
文档:https://wiki.swoole.com/wiki/...
- 根据官方文档,在
Server
类中完成Task
机制的初始化。
<?php...class Server{ ... const CONFIG = [ ... 'task_worker_num' => 4, ... ]; public function __construct() { ... $this->ws->on('task', [$this, 'onTask']); $this->ws->on('finish', [$this, 'onFinish']); ... } ... public function onTask($server, $taskId, $srcWorkerId, $data) { } public function onFinish($server, $taskId, $data) { }}...
我们什么时候会用Task
进行匹配队列检测呢?其实就是把玩家放入匹配队列后。
Logic
类:
<?php...class Logic{ public function matchPlayer($playerId) { //将用户放入队列中 DataCenter::pushPlayerToWaitList($playerId); //发起一个Task尝试匹配 //swoole_server->task(xxx); }}
Server
类:
<?php...class Server{ ... public function onTask($server, $taskId, $srcWorkerId, $data) { DataCenter::log("onTask", $data); //执行某些逻辑 } ...}...
可以发现,onTask
方法只是接收传递的$data
,当我们有多种Task
任务(匹配玩家、在线检测、游戏状态检查)
时,我们的worker
怎么区分每一个Task
任务呢?其实就和客户端与服务端通信一样,我们可以根据一个code
来区分。
Logic
类:
<?php...class Logic{ public function matchPlayer($playerId) { //将用户放入队列中 DataCenter::pushPlayerToWaitList($playerId); //发起一个Task尝试匹配 //swoole_server->task(['code'=>'xxx']); }}
Server
类:
<?php...class Server{ ... public function onTask($server, $taskId, $srcWorkerId, $data) { DataCenter::log("onTask", $data); switch ($data['code']) { //执行task方法 case 'xxx': //task->xxx(); break; case 'yyy': //task->yyy(); break; } } ...}...
从代码可以看出,我们现在缺了两种机制:
- 全局获取Server对象:在
Logic
中获取swoole_server
从而调用task()
方法。 - 增加Task管理类:需要一个类管理
Task
的code
和Task
需要执行的逻辑方法。
全局获取Server对象
第一个比较好处理,我们在onWorkerStart
的时候就能获取到swoole_server
。
- 有童鞋可以会问,为什么不在
onStart
的时候获取?这是因为onStart
回调的是Master
进程,而onWorkerStart
回调的是Worker
进程,只有Worker
进程才可以发起Task
任务。有兴趣的童鞋请查阅文档:https://wiki.swoole.com/wiki/...
- 在
DataCenter
中新增静态变量$server
。 - 在
onWorkerStart
回调函数中,将$server
保存到DataCenter
中。
DataCenter
类:
<?php...class DataCenter{ ... public static $server; ...}
Server
类:
<?php...class Server{ ... public function onWorkerStart($server, $workerId) { ... DataCenter::$server = $server; } ...}...
这样就解决了第一种问题,下面轮到第二个问题。
增加Task管理类
在项目Manager
文件夹下,创建TaskManager
类文件。
TaskManager
类:
<?phpnamespace App\Manager;class TaskManager{}
后续所有跟task
有关的常量、方法都归于这个类来管理。
- 设置一个常量
TASK_CODE_FIND_PLAYER
,用于发起寻找玩家task
任务。 - 新增静态方法
findPlayer()
,当匹配队列长度大于等于2
时,弹出队列前两个玩家的player_id
并返回。
TaskManager
类:
<?phpnamespace App\Manager;class TaskManager{ const TASK_CODE_FIND_PLAYER = 1; public static function findPlayer() { $playerListLen = DataCenter::getPlayerWaitListLen(); if ($playerListLen >= 2) { $redPlayer = DataCenter::popPlayerFromWaitList(); $bluePlayer = DataCenter::popPlayerFromWaitList(); return [ 'red_player' => $redPlayer, 'blue_player' => $bluePlayer ]; } return false; }}
现在前置准备就绪,可以将上面写过的伪代码改成真实代码啦~
- 在
Logic
类的matchPlayer()
方法中,发起一个Task
任务尝试匹配。 - 在
Server
类中根据传入的code
,执行TaskManager
的findPlayer()
方法。 - 当
findPlayer()
方法有值返回的时候,返回执行结果并携带上code
到worker
进程。
本章就到这里结束了,这次留的Homework可能有点难度,请童鞋们尽力完成。
当前目录结构:
HideAndSeek├── app│ ├── Lib│ │ └── Redis.php│ ├── Manager│ │ ├── DataCenter.php│ │ ├── Game.php│ │ ├── Logic.php│ │ └── TaskManager.php│ ├── Model│ │ ├── Map.php│ │ └── Player.php│ └── Server.php├── composer.json├── composer.lock├── frontend│ └── index.html├── test.php└── vendor ├── autoload.php └── composer