当前在线人数接口

接口开发

这一章我们将新增一个接口,用于返回服务器的在线人数信息

我们服务器目前使用的是Swoole WebSocket Server,如果有看过Swoole文档的童鞋,应该知道其实他继承自Swoole Http Server,也就是说,它同样是支持普通的HTTP请求的。

做题时间

  1. 请同学们为Server设置onRequest回调,并在onRequest回调中返回HelloWorld

Swoole Websocket Server:wiki.swoole.com/wiki/page/3…

Server类:

  1. <?php
  2. ...
  3. class Server
  4. {
  5. ...
  6. public function __construct()
  7. {
  8. ...
  9. $this->ws->on('request', [$this, 'onRequest']);
  10. ...
  11. }
  12. ...
  13. public function onRequest($request, $response)
  14. {
  15. $response->end('HelloWorld');
  16. }
  17. }
  18. ...

12 当前在线人数接口 - 图1

请求成功获取到了HelloWorld

目前我们的请求通了,但是怎么获取到在线人数呢?

新的问题

不知道童鞋们有没有发现,游戏现在有一个严重的问题,就是有许多的操作都依赖于player_id,如获取连接fd、获取房间room_id、移动玩家坐标。

虽然目前player_id是前端随机生成的,但是还是会有重复的可能,当同一个player_id再次连接服务器时,就会覆盖原有用户的一些信息,引发一些奇奇怪怪的bug,所以在客户端连接的时候需要对player_id做在线检测,当player_id已在线时,断开该连接。

综合上述来看,我们可以使用RedisHash结构,把每一个用户的player_id保存起来,这样既可以做在线检测,又可以统计人数。

做题时间

  1. DataCenter中新增一个Hash键的增删查方法,用于保存player_id
  2. 在服务端onOpen()回调中判断连接的player_id是否已在线,在线则主动断开连接,不在线则放入Hash表中。
  3. 当客户端断开时,删除player_id在线状态。
  4. 修改前端代码,获取url中的参数player_id作为玩家player_id,不存在时则自动生成。
  5. 当服务端主动断开时,判断是否player_id在线,进行弹框提示。

记得在DataCenter初始化的时候清理掉该键哦

DataCenter

  1. <?php
  2. ...
  3. class DataCenter
  4. {
  5. ...
  6. public static function setOnlinePlayer($playerId)
  7. {
  8. $key = self::PREFIX_KEY . ':online_player';
  9. self::redis()->hSet($key, $playerId, 1);
  10. }
  11. public static function getOnlinePlayer($playerId)
  12. {
  13. $key = self::PREFIX_KEY . ':online_player';
  14. return self::redis()->hGet($key, $playerId);
  15. }
  16. public static function delOnlinePlayer($playerId)
  17. {
  18. $key = self::PREFIX_KEY . ':online_player';
  19. self::redis()->hDel($key, $playerId);
  20. }
  21. ...
  22. public static function setPlayerInfo($playerId, $playerFd)
  23. {
  24. ...
  25. self::setOnlinePlayer($playerId);
  26. }
  27. public static function delPlayerInfo($playerFd)
  28. {
  29. ...
  30. self::delOnlinePlayer($playerId);
  31. }
  32. ...
  33. public static function initDataCenter()
  34. {
  35. ...
  36. //清空在线玩家
  37. $key = self::PREFIX_KEY . ':online_player';
  38. self::redis()->del($key);
  39. }
  40. }

Server

  1. <?php
  2. ...
  3. class Server
  4. {
  5. ...
  6. /**
  7. * @param $server \swoole_websocket_server
  8. * @param $request
  9. */
  10. public function onOpen($server, $request)
  11. {
  12. ...
  13. if (empty(DataCenter::getOnlinePlayer($playerId))) {
  14. DataCenter::setPlayerInfo($playerId, $request->fd);
  15. } else {
  16. $server->disconnect($request->fd, 4000, '该player_id已在线');
  17. }
  18. }
  19. ...
  20. }
  21. ...

index.html

  1. ...
  2. <script>
  3. var app = new Vue({
  4. ...
  5. data: {
  6. ...
  7. playerId: '',
  8. ...
  9. },
  10. created() {
  11. this.initPlayerId()
  12. ...
  13. },
  14. ...
  15. methods: {
  16. initPlayerId() {
  17. var inputPlayerId = this.getUrlParam('player_id')
  18. if (inputPlayerId !== '') {
  19. this.playerId = inputPlayerId
  20. } else {
  21. this.playerId = 'player_' + Math.round(Math.random() * 1000)
  22. }
  23. },
  24. getUrlParam(paramName) {
  25. var url = document.location.toString();
  26. var arrObj = url.split("?");
  27. if (arrObj.length > 1) {
  28. var arrPara = arrObj[1].split("&");
  29. var arr;
  30. for (var i = 0; i < arrPara.length; i++) {
  31. arr = arrPara[i].split("=");
  32. if (arr !== null && arr[0] === paramName) {
  33. return arr[1];
  34. }
  35. }
  36. return '';
  37. }
  38. else {
  39. return "";
  40. }
  41. },
  42. ...
  43. websocketclose(e) { //关闭
  44. console.log('断开连接', e);
  45. if (e.code === 4000) {
  46. alert('该player_id已在线')
  47. }
  48. },
  49. ...
  50. }
  51. })
  52. </script>
  53. </body>
  54. </html>

代码写完了,是时候试一下效果了,在浏览器中打开我们的前端页面,并在url中附带参数?player_id=test001

12 当前在线人数接口 - 图2

拦截成功,接下来就要回归原题了。

做题时间

  1. DataCenter中新增lenOnlinePlayer()方法,返回在线玩家数量。
  2. onRequest回调中返回在线玩家数量。
  3. 前端引入jQuery,通过Ajax调用接口获取在线玩家数量并渲染在页面上。

小提示:onRequest回调只有一个,如何区别不同请求的action呢?

DataCenter类:

  1. <?php
  2. ...
  3. class DataCenter
  4. {
  5. ...
  6. public static function lenOnlinePlayer()
  7. {
  8. $key = self::PREFIX_KEY . ':online_player';
  9. return self::redis()->hLen($key);
  10. }
  11. ...
  12. }

Server类:

  1. <?php
  2. ...
  3. class Server
  4. {
  5. ...
  6. public function onRequest($request, $response)
  7. {
  8. DataCenter::log("onRequest");
  9. $action = $request->get['a'];
  10. if ($action == 'get_online_player') {
  11. $data = [
  12. 'online_player' => DataCenter::lenOnlinePlayer()
  13. ];
  14. $response->end(json_encode($data));
  15. }
  16. }
  17. }
  18. ...

index.html

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. ...
  5. <script src="https://cdn.staticfile.org/jquery/1.10.2/jquery.min.js"></script>
  6. ...
  7. </head>
  8. <body>
  9. <div id="app">
  10. ...
  11. <div v-if="onlinePlayer">
  12. 当前在线玩家:{{onlinePlayer}}
  13. </div>
  14. ...
  15. </div>
  16. <script>
  17. var app = new Vue({
  18. ...
  19. data: {
  20. ...
  21. onlinePlayer: null,
  22. },
  23. created() {
  24. ...
  25. this.getServerInfo()
  26. },
  27. ...
  28. methods: {
  29. getServerInfo() {
  30. var that = this;
  31. $.ajax({
  32. url: 'http://192.168.3.41:8812',
  33. type: 'get',
  34. dataType: 'json',
  35. data: {
  36. 'a': 'get_online_player'
  37. },
  38. success: function (result) {
  39. that.onlinePlayer = result.online_player
  40. },
  41. error: function () {
  42. }
  43. })
  44. },
  45. ...
  46. }
  47. })
  48. </script>
  49. </body>
  50. </html>

12 当前在线人数接口 - 图3

Homework

请童鞋们尝试新增一个“改名”按钮,通过在玩家ID的输入框中输入姓名后,点击按钮进行改名,可以参考master分支的前端代码。

本章对应Github Commit扩展二:当前在线人数接口