内容简介:童鞋们的作业完成情况如何呢?
Logic
类:
<?php ... class Logic { public function matchPlayer($playerId) { ... //发起一个Task尝试匹配 DataCenter::$server->task(['code' => TaskManager::TASK_CODE_FIND_PLAYER]); } } 复制代码
Server
类:
<?php ... class Server { ... public function onTask($server, $taskId, $srcWorkerId, $data) { DataCenter::log("onTask", $data); $result = []; switch ($data['code']) { case TaskManager::TASK_CODE_FIND_PLAYER: $ret = TaskManager::findPlayer(); if (!empty($ret)) { $result['data'] = $ret; } break; } if (!empty($result)) { $result['code'] = $data['code']; return $result; } } ... } ... 复制代码
童鞋们的作业完成情况如何呢?
我们来再次梳理一下目前的匹配功能进度:
- 前端连接时发送
player_id
- 服务端连接时保存玩家信息
- 前端发送
code
为600
的指令 - 服务端将
player_id
放入匹配队列 - 服务端发起一个
task
进行玩家匹配,当寻找到两个玩家时返回两个player_id
到worker
进程
那下一步就很明显了,就是创建游戏房间。
创建房间分析
- 在
Server
类的onFinish()
方法中,根据传入的code
,执行Logic
的createRoom()
方法。
Server
类:
<?php ... class Server { ... public function onFinish($server, $taskId, $data) { DataCenter::log("onFinish", $data); switch ($data['code']) { case TaskManager::TASK_CODE_FIND_PLAYER: $this->logic->createRoom($data['data']['red_player'], $data['data']['blue_player']); break; } } } ... 复制代码
显然,下一步就是完成这个 createRoom()
方法匹配机制就大功告成了。但是真的这么简单吗?下面我们要思考一件事情。
我们的匹配队列是存放在 Redis
中的,无论哪个 worker
都可以读取,但游戏数据是存放在内存中的,在启动 Swoole Worker
时设置了 'worker_num' => 4
, worker
是多进程的,这会产生什么效果呢?就是进程内存隔离。
-
Swoole
文档: wiki.swoole.com/wiki/page/8…
比如, A玩家
进入了 worker_1
,数据保存在 worker_1
进程内存中,而 B玩家
进入了 worker_2
,数据保存在 worker_2
进程内存中。他们的匹配队列用的却是同一个 Redis List
,假如我们选择了 worker_1
进行游戏数据存放,那么 B玩家
将会读取不到内存中的游戏数据。
要解决这个问题有几个容易的方法:
Redis worker
显然, A
方法过于粗暴,没想到竟说出如此 粗鄙之语 !而 B
方法扩展性不好,当有成千上万玩家的时候,我们的 Redis
分分钟就挂给你看。这样下来只能选择 C
方法来实践。
Swoole
为我们提供了一个 bind()
方法,就可将连接绑定到固定的一个 worker
来处理。不了解 bind()
方法的童鞋请先阅读一下官方文档,尤其是 时序问题
。
-
Swoole
文档: wiki.swoole.com/wiki/page/3…
那么我们创建房间的流程就是:
- 生成一个房间
room_id
- 将
task
寻找到的两位玩家连接的fd
绑定到room_id
算出的同一个int
值 - 通知玩家
room_id
- 前端获取到
room_id
后,发起开始游戏请求
绑定玩家连接
- 想要使用
bind()
方法,需先将dispatch_mode
设置为5
。 - 完成
Logic
的createRoom()
方法,生成一个room_id
,绑定连接fd
。 - 获取
$server
对象,向两个玩家分别发送房间room_id
。
Server
类:
<?php ... class Server { ... const CONFIG = [ ... 'dispatch_mode' => 5, ... ]; ... } ... 复制代码
Logic
类:
<?php ... class Logic { ... public function createRoom($redPlayer, $bluePlayer) { $roomId = uniqid('room_'); $this->bindRoomWorker($redPlayer, $roomId); $this->bindRoomWorker($bluePlayer, $roomId); } private function bindRoomWorker($playerId, $roomId) { $playerFd = DataCenter::getPlayerFd($playerId); DataCenter::$server->bind($playerFd, crc32($roomId)); DataCenter::$server->push($playerFd, $roomId); } } 复制代码
童鞋们发现问题了吗?
没错,我们的 push()
方法直接就把 room_id
发过去了。又是这种问题:接收方无法识别该消息是何种消息。那么我们要如何处理呢?还是老套路,加 code
协议码。一个更好的办法是,找一个类来专门管理发送相关的变量和方法。
在 Manager
文件夹下,新建 Sender
类文件。
Sender
类:
<?php namespace App\Manager; class Sender { } 复制代码
- 在
Sender
类中新增MSG_ROOM_ID
常量,作为发送room_id
的code
。 - 新增方法
sendMessage($playerId, $code, $data = [])
,通过传入的$playerId
发送固定格式的消息到客户端。比较常规的内容需要有:code
、msg
、data
。 - 将
bindRoomWorker()
中发送房间room_id
的代码改为使用Sender
发送。
Sender
类:
<?php ... class Sender { const MSG_ROOM_ID = 1001; const CODE_MSG = [ self::MSG_ROOM_ID => '房间ID', ]; public static function sendMessage($playerId, $code, $data = []) { $message = [ 'code' => $code, 'msg' => self::CODE_MSG[$code] ?? '', 'data' => $data ]; $playerFd = DataCenter::getPlayerFd($playerId); if (empty($playerFd)) { return; } DataCenter::$server->push($playerFd, json_encode($message)); } } 复制代码
Logic
类:
<?php ... class Logic { ... private function bindRoomWorker($playerId, $roomId) { $playerFd = DataCenter::getPlayerFd($playerId); DataCenter::$server->bind($playerFd, crc32($roomId)); Sender::sendMessage($playerId, Sender::MSG_ROOM_ID, ['room_id' => $roomId]); } } 复制代码
这下我们的前端就能通过接收的 code
来判断,究竟这条 message
是 房间ID
或者是 游戏数据
。
我们来测试一下目前为止的代码有没有问题。重启 Server
服务器,在浏览器打开两个游戏前端页面并点击匹配按钮。
[root@localhost app]# php Server.php master start (listening on 0.0.0.0:8811) server: onWorkStart,worker_id:4 server: onWorkStart,worker_id:5 server: onWorkStart,worker_id:6 server: onWorkStart,worker_id:7 server: onWorkStart,worker_id:0 server: onWorkStart,worker_id:1 server: onWorkStart,worker_id:2 server: onWorkStart,worker_id:3 [2019-04-21 15:59:46][INFO]: client open fd:3 [2019-04-21 15:59:50][INFO]: client open fd:3,message:{"code":600} [2019-04-21 15:59:50][INFO]: onTask {"code":1} [2019-04-21 15:59:50][INFO]: onFinish {"data":{"red_player":"player_177","blue_player":"player_181"},"code":1} PHP Warning: Swoole\WebSocket\Server::push(): the connected client of connection[9] is not a websocket client or closed. in /mnt/htdocs/HideAndSeek_teach/app/Manager/Sender.php on line 31 复制代码
显然,程序报错了。这是因为我们启动服务器时,没有清除之前的残余玩家信息,所以 push()
时报错了。
初始化玩家数据
- 在
DataCenter
中新增initDataCenter()
方法清除Redis
中的残余数据。 - 在
onStart
的时候调用initDataCenter()
方法。
DataCenter
类:
<?php ... class DataCenter { ... public static function initDataCenter() { //清空匹配队列 $key = self::PREFIX_KEY . ':player_wait_list'; self::redis()->del($key); //清空玩家ID $key = self::PREFIX_KEY . ':player_id*'; $values = self::redis()->keys($key); foreach ($values as $value) { self::redis()->del($value); } //清空玩家FD $key = self::PREFIX_KEY . ':player_fd*'; $values = self::redis()->keys($key); foreach ($values as $value) { self::redis()->del($value); } } ... } 复制代码
Server
类:
<?php ... class Server { ... public function onStart($server) { ... DataCenter::initDataCenter(); } ... } ... 复制代码
现在再来一次,重启 Server
服务器,在浏览器打开两个游戏前端页面并点击匹配按钮。
可以看到,服务端成功发送 room_id
。
发送开始游戏指令
- 在
Vue
的数据属性中新增roomId
,用于保存服务端发送的room_id
。 - 新增方法
startRoom()
,当服务端发来room_id
消息时,发送code
以及room_id
到服务端开始游戏。
本章留的Homework是前端功能,但是比较简单,请童鞋们尽力完成哦。
当前目录结构:
HideAndSeek ├── app │ ├── Lib │ │ └── Redis.php │ ├── Manager │ │ ├── DataCenter.php │ │ ├── Game.php │ │ ├── Logic.php │ │ ├── Sender.php │ │ └── TaskManager.php │ ├── Model │ │ ├── Map.php │ │ └── Player.php │ └── Server.php ├── composer.json ├── composer.lock ├── frontend │ └── index.html ├── test.php └── vendor ├── autoload.php └── composer 复制代码
以上所述就是小编给大家介绍的《用Swoole来写个联机对战游戏呀!(八)创建游戏房间》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 用Swoole来写个联机对战游戏呀!(五)联机初始化
- 用Swoole来写个联机对战游戏呀!(三)完善游戏功能
- 用Swoole来写个联机对战游戏呀!(四)游戏结束判断
- 用Swoole来写个联机对战游戏呀!(六)游戏匹配机制
- 从动物森友会聊主机游戏联机原理
- 用Swoole来写个联机对战游戏呀!(二)单机游戏架构
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
网络多人游戏架构与编程
格雷泽 (Joshua Glazer)、马达夫 (Sanjay Madhav) / 王晓慧、张国鑫 / 人民邮电出版社 / 2017-10-1 / CNY 109.00
本书是一本深入探讨关于网络多人游戏编程的图书。 全书分为13章,从网络游戏的基本概念、互联网、伯克利套接字、对象序列化、对象复制、网络拓扑和游戏案例、延迟、抖动和可靠性、改进的延迟处理、可扩展性、安全性、真实世界的引擎、玩家服务、云托管专用服务器等方面深入介绍了网络多人游戏开发的知识,既全面又详尽地剖析了众多核心概念。 本书的多数示例基于C++编写,适合对C++有一定了解的读者阅读。本......一起来看看 《网络多人游戏架构与编程》 这本书的介绍吧!
UNIX 时间戳转换
UNIX 时间戳转换
HEX HSV 转换工具
HEX HSV 互换工具