后台管理工具在游戏运营中的作用

手游版本的更新迭代是非常频繁的,有些项目甚至每个星期都会进行停服更新。也就是说,对于生产环境的游戏进程,我们必须有工具能够对游戏服务进行维护。例如更新维护,或者对游戏内部各种资源进行管理。

后台管理工具的通信方式

典型地,完成这种任务的系统被称为后台管理工具。那么,后台管理工具怎么和游戏进程进行通信呢?

主要有两种方式。一种是通过socket,从管理后台建立一条socket连接到游戏进程,并且使用短连接即可。另一种是通过http请求,管理后台发出一条http命令到游戏进程。

两种通信方式的对比
使用http 使用socket
基于请求-响应模式 “全双工”,双方可以同时主动推送消息
使用“短链接”,请求无状态 可以是“短链接”,也可以是“长链接”
任务执行很难切换线程 切换线程后,异步处理任务

本文选择的是,后台管理工具采用http方式与游戏进程通信。采用这种方式其中一个原因就是,一般后台管理工具都是web网站服务,很容易发送http请求。

后台命令与GM命令的区别

后台命令与gm命令是很相像的,都是预留一些接口来对游戏进行管理。但两者还是有几点不同:

1. 目的性不同。后台命令是为了游戏运营或者游戏运维而产生的,而GM命令是为了方便项目人员调试游戏功能。部分功能甚至会在两种命令分别实现一次。

2. 通信方式不同。后台命令一般是由网站后台程序发出请求的,而GM命令相当于特殊的客户端请求,走的是客户端通信协议。

Mina使用http服务

mina对http服务的支持非常到位,有一个独立的jar包(mina-http)就是为了解决http服务的。

其实,http服务与socket服务只有消息的编解码不同,其他都是非常相似的。

所以,实现一个http服务,只需采用mina-http提供的HttpServerCodec编解码器就可以了。如下:

public class HttpServer {  public void start() throws Exception {  IoAcceptor acceptor = new NioSocketAcceptor();  acceptor.getFilterChain().addLast("codec", new HttpServerCodec());  acceptor.setHandler(new HttpServerHandle()); //http端口int port = ServerConfig.getInstance().getHttpPort();acceptor.bind(new InetSocketAddress(port));  }
}  

后台管理命令的设计

1. 后台命令的类型是比较多的,而且会随着游戏的更新逐渐扩充。为了管理所有类型,我们用一个常量类来保存所有命令类型(HttpCommands.java)。如下:

/*** 后台命令类型枚举* @author kingston*/
public final class HttpCommands {/**  停服 */public static final int CLOSE_SERVER = 1;/**  查看开服时间 */public static final int QUERY_SERVER_OPEN_TIME = 2;}

2. 不同命令所需要的参数也是不同的,所以我们需要一个参数类(HttpCommandParams.java),用于表征后台命令的参数。该参数由http的请求参数转换而来。

public class HttpCommandParams {/**  命令类型 {@link HttpCommands} */private int cmd;private Map<String, String> params;public static HttpCommandParams valueOf(int cmd, Map<String, String> params) {HttpCommandParams one = new HttpCommandParams();one.cmd    = cmd;one.params = params;return one;}public int getCmd() {return cmd;}public Map<String, String> getParams() {return params;}public void setParams(Map<String, String> params) {this.params = params;}public String getString(String key) {return params.get(key);}public int getInt(String key) {if (params.containsKey(key)) {return Integer.parseInt(params.get(key));}return 0;}@Overridepublic String toString() {return "HttpCommandParams [cmd=" + cmd + ", params=" + params+ "]";}}

3.不同后台命令的执行逻辑是不同的。对后台命令处理者,我们建立一个抽象类

/*** 抽象后台命令处理者* @author kingston*/
public abstract class HttpCommandHandler {/*** 处理后台命令* @param httpParams* @return*/public abstract HttpCommandResponse action(HttpCommandParams httpParams);}

4. 其中,HttpCommandResponse表示命令的执行结果

public class HttpCommandResponse {/**  执行成功 */public static final byte SUCC = 1;/**  执行失败 */public static final byte FAILED = 2;/** 执行结果状态码 */private byte code;/** 额外消息 */private String message;public static HttpCommandResponse valueOfSucc() {HttpCommandResponse response = new HttpCommandResponse();response.code = SUCC;return response;}public static HttpCommandResponse valueOfFailed() {HttpCommandResponse response = new HttpCommandResponse();response.code = FAILED;return response;}public byte getCode() {return code;}public void setCode(byte code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}@Overridepublic String toString() {return "HttpCommandResponse [code=" + code + ", message="+ message + "]";}}

5. 对于具体的后台命令,例如停服命令(CloseServerCommandHandler.java),只需继承自HttpCommandHandler即可。同时,命令的参数申明由注解CommandHandler绑定

@CommandHandler(cmd=HttpCommands.CLOSE_SERVER)
public class CloseServerCommandHandler extends HttpCommandHandler {@Overridepublic HttpCommandResponse action(HttpCommandParams httpParams) {return HttpCommandResponse.valueOfSucc();}}

6. 接下来,我们还需要一个工具类(HttpCommandManager.java)来缓存管理命令与对应的处理者之间的映射关系。

public class HttpCommandManager {private static volatile HttpCommandManager instance;private static Map<Integer, HttpCommandHandler> handlers = new HashMap<>();public static HttpCommandManager getInstance() {if (instance != null) {return instance;}synchronized (HttpCommandManager.class) {if (instance == null) {instance = new HttpCommandManager();instance.initialize();}return instance;}}private void initialize() {Set<Class<?>> handleClazzs = ClassScanner.getClasses("com.kingston.http", new ClassFilter() {  @Override  public boolean accept(Class<?> clazz) {  return clazz.getAnnotation(CommandHandler.class) != null;  }  });  for (Class<?> clazz: handleClazzs) {  try {  HttpCommandHandler handler = (HttpCommandHandler) clazz.newInstance(); CommandHandler annotation = handler.getClass().getAnnotation(CommandHandler.class);handlers.put(annotation.cmd(), handler);}catch(Exception e) {  LoggerUtils.error("", e);}  }  }/*** 处理后台命令* @param httpParams* @return*/public HttpCommandResponse handleCommand(HttpCommandParams httpParams) {HttpCommandHandler handler = handlers.get(httpParams.getCmd());if (handler != null) {return handler.action(httpParams);}return null;}
}

7. 最后,在HttpServer接受http请求的时候,将参数封装成HttpCommandParams对象,由HttpCommandManager找到对应的Handler,将处理结果封装成HttpCommandResponse,返回给客户端。如下:

class HttpServerHandle extends IoHandlerAdapter {  private static Logger logger = LoggerFactory.getLogger(HttpServer.class);@Override  public void exceptionCaught(IoSession session, Throwable cause)  throws Exception {  cause.printStackTrace();  }  @Override  public void messageReceived(IoSession session, Object message)  throws Exception {  if (message instanceof HttpRequest) {  // 请求,解码器将请求转换成HttpRequest对象  HttpRequest request = (HttpRequest) message;  HttpCommandResponse commandResponse = handleCommand(request);// 响应HTML  String responseHtml = new Gson().toJson(commandResponse);  byte[] responseBytes = responseHtml.getBytes("UTF-8");  int contentLength = responseBytes.length;  // 构造HttpResponse对象,HttpResponse只包含响应的status line和header部分  Map<String, String> headers = new HashMap<String, String>();  headers.put("Content-Type", "text/html; charset=utf-8");  headers.put("Content-Length", Integer.toString(contentLength));  HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpStatus.SUCCESS_OK, headers);  // 响应BODY  IoBuffer responseIoBuffer = IoBuffer.allocate(contentLength);  responseIoBuffer.put(responseBytes);  responseIoBuffer.flip();  session.write(response); // 响应的status line和header部分  session.write(responseIoBuffer); // 响应body部分  }  }  private HttpCommandResponse handleCommand(HttpRequest request) {HttpCommandParams httpParams = toHttpParams(request);if (httpParams == null) {HttpCommandResponse failed = HttpCommandResponse.valueOfFailed();failed.setMessage("参数错误");return failed;}logger.info("收到http后台命令,参数为{}", httpParams);HttpCommandResponse commandResponse = HttpCommandManager.getInstance().handleCommand(httpParams);if (commandResponse == null) {HttpCommandResponse failed = HttpCommandResponse.valueOfFailed();failed.setMessage("该后台命令不存在");return failed;}return commandResponse;}private HttpCommandParams toHttpParams(HttpRequest httpReq) {String cmd = httpReq.getParameter("cmd"); if (StringUtils.isEmpty(cmd)) {return null;}String paramJson = httpReq.getParameter("params"); if (StringUtils.isNotEmpty(paramJson)) {try{Map<String, String> params = new Gson().fromJson(paramJson, HashMap.class);return HttpCommandParams.valueOf(Integer.parseInt(cmd), params);}catch(Exception e) {}}return null;}}  

代码示例

游戏启动后,在浏览器输入,即可看到执行成功的提示

http://localhost:8080/?cmd=1&params={name=kingston}

网站的后台网站请参考 ->> 管理后台web工程

文章预告:下一篇主要介绍如何使用组合包优化客户端协议

手游服务端开源框架系列完整的代码请移步github ->> jforgame

手游服务端框架之后台管理工具相关推荐

  1. 手游服务端框架之自定义orm持久化工具

    前面一篇文章手游服务端框架之关于玩家数据的解决方案,介绍了当今游戏服务端对玩家数据进行持久化的两种方案.一种是将玩家数据通过json等格式统一打包成字符串或二进制流:另一种是根据模块功能拆分,一个模块 ...

  2. 修改手游服务器端数据库,手游服务端框架之配置与玩家数据库设计

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 用户数据异步持久化 当玩家的数据发生变动时,我们需要将最新的数据保存到数据库.这里有一个问题,当玩家数据有部分变动的时候,我们不可能即使保存到数据库的,这 ...

  3. 手游服务端框架之合区工程

    如今的游戏服务器运营策略一般为"滚服模式".简单来说,就是运营商不停的开新区,不活跃的旧区就进行合区.这样一来,运营商既可以降低服务器硬件配置,缓解大服人数压力,也可以利用新区拉活 ...

  4. 【手游服务端】梦幻西游十五门派端+教程+GM物品后台

    下载链接:https://pan.baidu.com/s/1ds_xFq1Rd1_xC4515BRGXw  提取码:soho  [手游服务端]梦幻西游十五门派端+教程+GM物品后台

  5. 端游、手游服务端游戏服务器架构 介绍

    端游.手游服务端常用的架构是什么样的? http://www.zhihu.com/question/29779732 根据知乎问答文章整理而成. 作者:韦易笑 谢邀,手游页游和端游的服务端本质上没区别 ...

  6. Go游戏服务端框架从零搭建(一)— 架构设计

    本教程以Go语言分区游戏服务端框架搭建为例. Go语言是Google开发的一种静态强类型.编译型.并发型.具有垃圾回收功能的编程语言.语法上近似C语言,支持接口.可通过struct包含另一个struc ...

  7. 阿拉德手游服务端Centos搭建教程

    阿拉德手游服务端Centos搭建教程 大家好我是艾西,又有几天没有更新文章了.这几天看了看还是有不少人对手游感兴趣,今天给大家分享一款早些年大火的pc游戏,现在也有手游了"阿拉德" ...

  8. 顶级 Swift 服务端框架对决 Node.js

    本文讲的是顶级 Swift 服务端框架对决 Node.js, 前言 最近我在做服务端 Swift 工作时,我被问到这样的问题: 「在服务端 Swift 能否击败 Node.js?」 Swift 是一个 ...

  9. 删除战神引擎手游服务端里面自带的病毒文件教程

    前几天在服务器上架设了一个战神引擎的服务端,今天突然发现进不去游戏,通过服务器后台修改了密码,发现也不行,看了一下服务器日志,提示有病毒,病毒把服务器的用户名和密码都修改了,最后只能从装系统才解决了进 ...

最新文章

  1. h265player开发
  2. 解决windows 10英文版操作系统中VS2017控制台程序打印中文乱码问题
  3. 甲骨文否认将收购IT咨询巨头埃森哲 称从未考虑过
  4. OSChina 周三乱弹 ——是不是兄弟?是兄弟就帮我解决下!
  5. 视频播放器的界面设计并实现播放器
  6. 《开源思索集》一如何更有效地学习开源项目的代码?
  7. wxWidgets之wxGrid控件
  8. JS-[IIFE闭包]
  9. 小白学数据分析-----聚类分析理论之TwoSteps理论篇+实践篇
  10. kibana-7.15.2 一分钟下载、安装、部署 linux
  11. html富文本编辑器插件_vue中使用vuequilleditor富文本编辑器
  12. rect函数_Python基础入门(9):从函数到高级魔法方法--阿里云天池
  13. Spring Cloud(Greenwich版)-06-Spring Cloud集成Feign
  14. 程序人生 - 王者荣耀重名代码
  15. cherry pick用法
  16. easypanel默认php版本设置,easypanel面板+kangle
  17. OpenCV+Python 彩色图片的 BGR、灰度图、HSV分量图显示的程序
  18. 我是如何用一行代码表白学妹~❤520情人节送女朋友的3D樱花雨相册礼物❤~(程序员表白专属)...
  19. Android studio3.6.3的jdk版本设置在哪里?
  20. 芬兰的诺基亚发布了可以访问互联网的诺基亚9000 Communicator

热门文章

  1. sourcetree如何修改用户名
  2. 蔬菜类别二级计算机,蔬菜计算机编码 蔬菜商品分类和代码.pdf
  3. mysql 5.7.23用户权限_MySQL权限的详细解答
  4. delphi 裁剪mp3_MP3剪切器下载-MP3剪切器正式版下载[电脑版]-PC下载网
  5. Deep Learning for Weakly-Supervised Object Detection and Object Localization: A Survey
  6. windows系统怎么开启/禁用驱动程序强制签名
  7. spring日志加载代码解析
  8. [mysql]-日期-时间-riqi-shijian
  9. CuteEditor6完整汉化包(更新到6.1)
  10. (转)天道酬勤,上善若水—八字真言让您立于“不败之地”