手游服务端框架之后台管理工具
后台管理工具在游戏运营中的作用
手游版本的更新迭代是非常频繁的,有些项目甚至每个星期都会进行停服更新。也就是说,对于生产环境的游戏进程,我们必须有工具能够对游戏服务进行维护。例如更新维护,或者对游戏内部各种资源进行管理。
后台管理工具的通信方式
典型地,完成这种任务的系统被称为后台管理工具。那么,后台管理工具怎么和游戏进程进行通信呢?
主要有两种方式。一种是通过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¶ms={name=kingston}
网站的后台网站请参考 ->> 管理后台web工程
文章预告:下一篇主要介绍如何使用组合包优化客户端协议
手游服务端开源框架系列完整的代码请移步github ->> jforgame
手游服务端框架之后台管理工具相关推荐
- 手游服务端框架之自定义orm持久化工具
前面一篇文章手游服务端框架之关于玩家数据的解决方案,介绍了当今游戏服务端对玩家数据进行持久化的两种方案.一种是将玩家数据通过json等格式统一打包成字符串或二进制流:另一种是根据模块功能拆分,一个模块 ...
- 修改手游服务器端数据库,手游服务端框架之配置与玩家数据库设计
该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 用户数据异步持久化 当玩家的数据发生变动时,我们需要将最新的数据保存到数据库.这里有一个问题,当玩家数据有部分变动的时候,我们不可能即使保存到数据库的,这 ...
- 手游服务端框架之合区工程
如今的游戏服务器运营策略一般为"滚服模式".简单来说,就是运营商不停的开新区,不活跃的旧区就进行合区.这样一来,运营商既可以降低服务器硬件配置,缓解大服人数压力,也可以利用新区拉活 ...
- 【手游服务端】梦幻西游十五门派端+教程+GM物品后台
下载链接:https://pan.baidu.com/s/1ds_xFq1Rd1_xC4515BRGXw 提取码:soho [手游服务端]梦幻西游十五门派端+教程+GM物品后台
- 端游、手游服务端游戏服务器架构 介绍
端游.手游服务端常用的架构是什么样的? http://www.zhihu.com/question/29779732 根据知乎问答文章整理而成. 作者:韦易笑 谢邀,手游页游和端游的服务端本质上没区别 ...
- Go游戏服务端框架从零搭建(一)— 架构设计
本教程以Go语言分区游戏服务端框架搭建为例. Go语言是Google开发的一种静态强类型.编译型.并发型.具有垃圾回收功能的编程语言.语法上近似C语言,支持接口.可通过struct包含另一个struc ...
- 阿拉德手游服务端Centos搭建教程
阿拉德手游服务端Centos搭建教程 大家好我是艾西,又有几天没有更新文章了.这几天看了看还是有不少人对手游感兴趣,今天给大家分享一款早些年大火的pc游戏,现在也有手游了"阿拉德" ...
- 顶级 Swift 服务端框架对决 Node.js
本文讲的是顶级 Swift 服务端框架对决 Node.js, 前言 最近我在做服务端 Swift 工作时,我被问到这样的问题: 「在服务端 Swift 能否击败 Node.js?」 Swift 是一个 ...
- 删除战神引擎手游服务端里面自带的病毒文件教程
前几天在服务器上架设了一个战神引擎的服务端,今天突然发现进不去游戏,通过服务器后台修改了密码,发现也不行,看了一下服务器日志,提示有病毒,病毒把服务器的用户名和密码都修改了,最后只能从装系统才解决了进 ...
最新文章
- h265player开发
- 解决windows 10英文版操作系统中VS2017控制台程序打印中文乱码问题
- 甲骨文否认将收购IT咨询巨头埃森哲 称从未考虑过
- OSChina 周三乱弹 ——是不是兄弟?是兄弟就帮我解决下!
- 视频播放器的界面设计并实现播放器
- 《开源思索集》一如何更有效地学习开源项目的代码?
- wxWidgets之wxGrid控件
- JS-[IIFE闭包]
- 小白学数据分析-----聚类分析理论之TwoSteps理论篇+实践篇
- kibana-7.15.2 一分钟下载、安装、部署 linux
- html富文本编辑器插件_vue中使用vuequilleditor富文本编辑器
- rect函数_Python基础入门(9):从函数到高级魔法方法--阿里云天池
- Spring Cloud(Greenwich版)-06-Spring Cloud集成Feign
- 程序人生 - 王者荣耀重名代码
- cherry pick用法
- easypanel默认php版本设置,easypanel面板+kangle
- OpenCV+Python 彩色图片的 BGR、灰度图、HSV分量图显示的程序
- 我是如何用一行代码表白学妹~❤520情人节送女朋友的3D樱花雨相册礼物❤~(程序员表白专属)...
- Android studio3.6.3的jdk版本设置在哪里?
- 芬兰的诺基亚发布了可以访问互联网的诺基亚9000 Communicator
热门文章
- sourcetree如何修改用户名
- 蔬菜类别二级计算机,蔬菜计算机编码 蔬菜商品分类和代码.pdf
- mysql 5.7.23用户权限_MySQL权限的详细解答
- delphi 裁剪mp3_MP3剪切器下载-MP3剪切器正式版下载[电脑版]-PC下载网
- Deep Learning for Weakly-Supervised Object Detection and Object Localization: A Survey
- windows系统怎么开启/禁用驱动程序强制签名
- spring日志加载代码解析
- [mysql]-日期-时间-riqi-shijian
- CuteEditor6完整汉化包(更新到6.1)
- (转)天道酬勤,上善若水—八字真言让您立于“不败之地”