这篇文章 接着 前面的http://my.oschina.net/chenleijava/blog/423503,继续说说flatbuffers的应用
这里主要说的是flatbuffer 作为以cocos2dx引擎为基础 ,网络部分的序列化方案采用flatbuffers。
之所以考虑flatbuffers,依赖性低 ,性能较好。
这里只谈论设计方案,该设计方案对代码设计和结构上有一定的约定(相对订制):
客户端 会接受来自服务器的响应,解码到消息,进行数据处理。针对flatbuffers生成的类和对应处理该消息事的方案映射
在以前protbuffer中有提过(http://my.oschina.net/chenleijava/blog/376560)。这里依旧采用此方案:
注意以下所提及的类或者接口都是代码生成器生成(主要适用于这类方案 定制的,采用java编写),会解析fbs文件,生成对应的controller 文件。编写逻辑 只在
virtual void dispatcherMessage(char *data);
中完成;
fbs测试:
//1.使用gen Tools 生成对应客户端代码你需要严格约定 //2.table命名约定 上下行消息 xxRequest xxResponse //3.前后端交互消息 存在一个消息ID;命名约定: msgID //4.命名空间 使用gen ---主要限制前端 namespace gen; table LoginFlatRequest{ msgID:int=1; username:string; } table LoginFlatResponse{ msgID:int=2; time:long; } table BattleRequest{ msgID:int=3; } table BattleResponse{ msgID:int=4; }
接口类设计:IController.h
//warn this is gen file,don't modify it #ifndef _ICONTROLLER_H #define _ICONTROLLER_H #include <map> #include "cocos2d.h" #include "flatgen/Game_generated.h" using namespace cocos2d; using namespace gen; using namespace std; #ifndef delete_array #define delete_array(p) do { if(p) { delete[] (p); (p) = nullptr; } } while(0) #endif //delete_array class IController { public: IController() { } /** 注册消息ID 和对应处理controller的关系 以便于快速索引处理 */ static std::map<int, IController *> controller_map; /** 负责处理数据分发 */ virtual void dispatcherMessage(char *data); /** * 获取对应的消息ID */ virtual int msgID()=0; /** 转化对应消息为flatbuffers实体 data+4: 消息ID 占用4 字节 */ template <typename T> const T* getRoot(char* data); /** * @Author 石头哥哥, 15-06-01 23:06:26 * * @brief 注册controoler and mapper */ static void registerMapperController(); }; template<typename T> inline const T *IController::getRoot(char *data) { return flatbuffers::GetRoot<T>(data+4); } #endif//_ICONTROLLER_H
.cpp
//this is gen file,don't modify it #include "IController.h" #include "LoginFlatController.h" #include "BattleController.h" map<int, IController *> IController::controller_map; void IController::dispatcherMessage(char *data) {} /** * 注册controller mapper */ void IController::registerMapperController() { log("%s", "registerMapperController开始注册controller... ..."); controller_map[LoginFlatController::controller->msgID()]=LoginFlatController::controller; controller_map[BattleController::controller->msgID()]=BattleController::controller; }
生成对应的子类:LoginFlatController.h
//this is gen file,don't modify it #ifndef LoginFlatController_h #define LoginFlatController_h #include "IController.h" class LoginFlatController:public IController{ public: static LoginFlatController *controller; public: /** 负责处理数据分发 处理来自服务器数据 */ virtual void dispatcherMessage(char *data); /** * 获取对应的消息ID */ virtual int msgID() override; }; #endif //LoginFlatController_h
.cpp
//this is gen file,logic controler #include "LoginFlatController.h" LoginFlatController*LoginFlatController::controller=new LoginFlatController; int LoginFlatController::msgID(){ flatbuffers::FlatBufferBuilder builder; builder.Finish(CreateLoginFlatResponse(builder)); auto msgID = flatbuffers::GetRoot<LoginFlatResponse>(builder.GetBufferPointer())->msgID(); builder.ReleaseBufferPointer(); return msgID; } /** 负责处理数据分发 处理来自服务器数据 */ void LoginFlatController::dispatcherMessage(char *data) { auto loginflatresponse=getRoot<LoginFlatResponse>(data); //TODO::处理来自服务器数据LoginFlatResponse }
因为生成代码的工具还在开发中。目前只完成了cpp生成 ,后期支持java(服务器端)---暂时只定制flatbuffer代码生成。
后期开源分享,这里提供java代码生成类:
package com.genflat.mapper;/* * Copyright (c) 2015. * 游戏服务器核心代码编写人石头哥哥拥有使用权 * 最终使用解释权归创心科技所有 * 联系方式:E-mail:[email protected] ; * 个人博客主页:http://my.oschina.net/chenleijava * powered by 石头哥哥 */ import org.apache.commons.io.FileUtils; import java.io.File; import java.io.FilenameFilter; import java.io.IOException; import java.io.RandomAccessFile; import java.util.ArrayList; /** * @author 石头哥哥 * </P> * Date: 2015/6/2 * </P> * Time: 9:35 * </P> * Package: dcServer-parent * </P> * <p/> * 注解: */ public class GenFlatCpp { /** * 客户端处理消息类型 注意消息命名XXXResponse--- 服务器响应消息类型 */ private static final String client = "Response"; /** * 用于生成处理消息类型引用的变量名 注意是静态类型 */ private static final String controllorname = "controller"; /** * 对应客户端处理来自服务器端消息命名 * xxxResponse * <p/> * table LoginResponse{ * objectID:int=2; * username:string; * } * <p/> * 生成处理来自服务器端的 Response 类型消息 <p/> * 自动生成映射的mapper 如:controller_map[msgID<LoginResponse>()]=LoginResponseController::controller; <p/> * 同时生成对应的controller处理部分 <p/> * 逻辑编写部位:virtual void dispatcherMessage(char *data); <p/> * * @param fbspath flatbuffer fbs 所在文件路径 <p/> * @param controllerpath 对应controller生成路径 <p/> * @throws IOException */ @SuppressWarnings("ResultOfMethodCallIgnored") public static void genCppMapper(String fbspath, String controllerpath) throws Exception { File file; //get fbs table list file = new File(fbspath); String[] fbsNames = file.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".fbs"); } }); if (fbsNames == null) { throw new Exception("fbs not found in res!!!"); } ArrayList<String> fbsPaths = new ArrayList<String>(); for (String fbsname : fbsNames) { fbsPaths.add(fbspath + "/" + fbsname); } //读取所有fbs文件 获取文件中的table name ArrayList<String> mapperTable = new ArrayList<String>(); //loading fbs and filter table name for (String fbs : fbsPaths) { file = new File(fbs); RandomAccessFile randomAccessFile; try { randomAccessFile = new RandomAccessFile(file, "r"); //O_RDONLY String txt = ""; while (txt != null) { txt = randomAccessFile.readLine(); if (txt != null && txt.startsWith("table")) { String temp = txt.substring(0, txt.lastIndexOf("{")) .replace("table", ""); if (temp.endsWith(client)) mapperTable.add(temp.trim()); } } randomAccessFile.close(); //close randomAccessFile } catch (IOException e) { e.printStackTrace(); } } fbsPaths.clear(); ArrayList<String> controllerList = new ArrayList<String>(); //for icontroller StringBuilder mapperBuilder = new StringBuilder(); // controller_map[LoginFlatController::controller->msgID()]=LoginFlatController::controller; for (String table : mapperTable) { String controllername = table + "Controller"; controllername = controllername.replace(client, ""); mapperBuilder.append(" controller_map[") .append(controllername) .append("::") .append(controllorname+"->msgID()") .append("]") .append("=") .append(controllername) .append("::") .append(controllorname) .append(";\n"); controllerList.add(controllername);//add controller } mapperTable.clear(); ArrayList<String> genIcontrollerHeader = new ArrayList<String>(); genIcontrollerHeader.addAll(controllerList); /** * don't override had gen xxxcontroller * because maybe had logic in it!!!! */ String[] exsiteController = new File(controllerpath).list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.endsWith(".cpp") && !name.endsWith("IController.cpp"); } }); for (String name : exsiteController) { String temp = name.substring(0, name.indexOf(".")); if (controllerList.contains(temp)) { controllerList.remove(temp); } } StringBuilder builderCpp = new StringBuilder(); //gen .h file for (String controller : controllerList) { String define = controller + "_h"; builderCpp.append("//this is gen file,don't modify it \n") .append("#ifndef") .append(" ").append(define).append("\n") .append("#define") .append(" ").append(define).append("\n") .append("#include \"IController.h\"") .append("\n") .append("class ") .append(controller) .append(":").append("public IController{") .append("\n") .append("public:") .append("\n") .append("static ") .append(controller) .append(" *") .append(controllorname) .append(";\n") .append("public:\n") .append("/**\n" + "负责处理数据分发 处理来自服务器数据\n" + "*/\n") .append("virtual void dispatcherMessage(char *data);\n") .append("/**\n" + " * 获取对应的消息ID\n" + " */\n") .append("virtual int msgID() override;\n") .append("};\n") .append("#endif") .append(" //").append(define); byte[] cpp_h = builderCpp.toString().getBytes(); builderCpp.delete(0, cpp_h.length); //write header File controllerFile = new File(controllerpath); if (!controllerFile.exists()) { controllerFile.mkdirs(); } FileUtils.writeByteArrayToFile(new File(controllerpath + "/" + controller + ".h"), cpp_h); //gen controller cpp file String tableName = controller.substring(0, controller.indexOf("Controller")) + client; String lowTablename = tableName.toLowerCase(); builderCpp.append("//this is gen file,logic controler \n") .append("\n") .append("#include") .append(" ") .append("\"") .append(controller).append(".h") .append("\"") .append("\n") .append(controller).append("") .append("*") .append(controller) .append("::") .append(controllorname).append("=") .append("new ").append(controller) .append(";\n") .append("\n") .append("int ") .append(controller+"::msgID(){\n") .append(" flatbuffers::FlatBufferBuilder builder;\n" + " builder.Finish(Create"+tableName+"(builder));\n" + " auto msgID = flatbuffers::GetRoot<"+tableName+">(builder.GetBufferPointer())->msgID();\n" + " builder.ReleaseBufferPointer();\n" + " return msgID;\n") .append("}\n\n") .append("/**\n" + "负责处理数据分发 处理来自服务器数据\n" + " */\n") .append("void ").append(controller).append("::") .append("dispatcherMessage(char *data) {\n\n").append(" auto ") .append(lowTablename) .append("=getRoot<") .append(tableName) .append(">(data);\n") .append(" //TODO::处理来自服务器数据") .append(tableName) .append("\n\n") .append("}"); byte[] cpp = builderCpp.toString().getBytes(); builderCpp.delete(0, cpp.length); FileUtils.writeByteArrayToFile(new File(controllerpath + "/" + controller + ".cpp"), cpp); } //IController always override // gen IController generated header name ArrayList<String> generatedHeaders = new ArrayList<String>(); String tempname; String tempnameup; char first; for (String name : fbsNames) { tempname = name.substring(1, name.lastIndexOf(".")); tempnameup = name.substring(0, name.lastIndexOf(".")).toUpperCase(); first = tempnameup.charAt(0); generatedHeaders.add(first + tempname); } builderCpp.append("//warn this is gen file,don't modify it \n") .append("#ifndef ").append("_ICONTROLLER_H\n") .append("#define ").append("_ICONTROLLER_H\n") .append("#include <map>\n") .append("#include \"cocos2d.h\"\n"); //append generated header include for (String name : generatedHeaders) { builderCpp.append("#include \"flatgen/").append(name) .append("_generated.h\"").append("\n"); } generatedHeaders.clear(); builderCpp.append("using namespace cocos2d;\n") .append("using namespace gen;\n") .append("using namespace std;\n") .append("#ifndef delete_array\n") .append("#define delete_array(p) do { if(p) { delete[] (p); (p) = nullptr; } } while(0)\n") .append("#endif //delete_array\n") .append("class IController {\n") .append("\n") .append("public:\n") .append(" IController() { }\n") .append(" /**\n" + " 注册消息ID 和对应处理controller的关系\n" + " 以便于快速索引处理\n" + " */\n") .append(" static std::map<int, IController *> controller_map;\n") .append(" /**\n" + " 负责处理数据分发\n" + " */\n") .append(" virtual void dispatcherMessage(char *data);\n") .append("/**\n" + " * 获取对应的消息ID\n" + " */\n") .append(" virtual int msgID()=0;\n") // .append(" /**\n" + // " * @Author 石头哥哥, 15-06-01 23:06:00\n" + // " *\n" + // " * @brief 获取生成消息对应的msgID\n" + // " *\n" + // " * @return <#return value description#>\n" + // " */\n") // .append(" template<typename T> static int msgID();\n") .append(" /**\n" + " 转化对应消息为flatbuffers实体\n" + " data+4: 消息ID 占用4 字节\n" + " */\n") .append(" template <typename T> const T* getRoot(char* data);\n") .append(" /**\n" + " * @Author 石头哥哥, 15-06-01 23:06:26\n" + " *\n" + " * @brief 注册controoler and mapper\n" + " */\n") .append(" static void registerMapperController();\n") .append("};\n") // .append(" template<typename T>\n") // .append("inline int IController::msgID() {\n" + // " flatbuffers::FlatBufferBuilder builder;\n" + // " builder.Finish(CreateLoginRequest(builder));\n" + // " auto msgID = flatbuffers::GetRoot<T>(builder.GetBufferPointer())->msgID();\n" + // " builder.ReleaseBufferPointer();\n" + // " return msgID;\n" + // "}\n") .append("template<typename T>\n" + "inline const T *IController::getRoot(char *data) {\n" + " return flatbuffers::GetRoot<T>(data+4);\n" + "}") .append("\n").append("#endif//_ICONTROLLER_H"); byte[] data = builderCpp.toString().getBytes(); builderCpp.delete(0, data.length); FileUtils.writeByteArrayToFile(new File(controllerpath + "/IController.h"), data); //gen icontroller cpp builderCpp.append("//this is gen file,don't modify it \n") .append("#include \"IController.h\"\n"); for (String controller : genIcontrollerHeader) { builderCpp.append("#include ") .append("\"").append(controller).append(".h") .append("\"\n"); } genIcontrollerHeader.clear(); controllerList.clear(); builderCpp.append("\n") .append("map<int, IController *> IController::controller_map;\n") .append("void IController::dispatcherMessage(char *data) {}\n") .append("/**\n" + " * 注册controller mapper\n" + " */\n" + "void IController::registerMapperController() {\n" + " log(\"%s\", \"registerMapperController开始注册controller... ...\");\n") .append(mapperBuilder.toString()).append("}\n"); data = builderCpp.toString().getBytes(); builderCpp.delete(0, data.length); FileUtils.writeByteArrayToFile(new File(controllerpath + "/IController.cpp"), data); System.out.println("-------gen success!!!---------------------"); } }
package com.genflat.mapper;/* * Copyright (c) 2015. * 游戏服务器核心代码编写人石头哥哥拥有使用权 * 最终使用解释权归创心科技所有 * 联系方式:E-mail:[email protected] ; * 个人博客主页:http://my.oschina.net/chenleijava * powered by 石头哥哥 */ import java.io.File; import java.io.IOException; /** * @author 石头哥哥 * </P> * Date: 2015/6/2 * </P> * Time: 9:34 * </P> * Package: dcServer-parent * </P> * <p/> * 注解: */ public class GenFlatMapper { /** * default gen c++ * args[0] -l * args[1] (java ,cpp) * args[2] -p * args[3] (controller file path) * args[4]-o * args[5] fbs file path * * @param args */ public static void main(String[] args) throws IOException { String show = ">>>>flatbuffer gen " + "\n该工具可以生成基于flatbuffers的代码,接口说明如下\n" + "使用命令:java -jar genflatMapper.jar -l cpp -c controllerpath -f fbspath \n" + "-l :编程语言类型 这里暂时支持 c++;\n" + "-p: 游戏中处理对应消息的controller文件路径;\n" + "-o: 编写的fbs文件路径;\n" + "游戏中fbs针对上行下行table命名规则有严格约定,如不使用该约定\n" + "使用该工具无效,详见readme! "; System.out.println(show); if (args.length != 0) { System.out.println("--------begin gen client code ----------------"); if (args.length < 6) { System.err.println(show); System.exit(0); } String l = args[1]; String controllerpath = args[3]; String fbspath = args[5]; if (l.equals("java")) { System.err.println("only support client , c++ \n"); System.err.println("use eg:\n" + "java -jar genflatMapper.jar -l cpp " + "-c controllerpath -f fbspath "); System.exit(0); }else if (l.equals("cpp")) { try { GenFlatCpp.genCppMapper(fbspath, controllerpath); } catch (Exception e) { e.printStackTrace(); } } } else { try { //gen current file System.out.println("tools will gen to current files,if your fbs in res file!!!"); File file = new File("./controller"); if (!file.exists()) file.mkdirs(); //find fbs GenFlatCpp.genCppMapper("res", "./controller"); } catch (Exception e) { e.printStackTrace(); } } } }
该生代码成器约定如下--如果你使用该方案,那么请严格遵循改约束,或许你有更好的idea:
###基于flatbuffers ,使用fbs文件生成对应前端代码。 ###完成对应的controller和消息ID映射,目前暂时支持c++。 >1.使用gen Tools 生成对应客户端代码你需要严格约定 2.table命名约定 上下行消息 xxRequest xxResponse 3.前后端交互消息 存在一个消息ID;命名约定: msgID >4.命名空间 使用gen ---主要限制前端 namespace gen; //下行消息 xxRequest table LoginRequest{ msgID:int=1; //消息ID msgID username:string; } //下行消息 xxResponse table LoginResponse{ msgID:int=1; //消息ID msgID username:string; } ####如何使用: 该工具可以生成基于flatbuffers的代码,接口说明如下 使用命令: java -jar genflatMapper.jar -l cpp -p controllerpath -o fbspath >-l :编程语言类型 这里暂时支持 c++; >-c: 游戏中处理对应消息的controller文件路径; >-f: 编写的fbs文件路径;
生成对应的文件如下:
上面代码都是生成的。
因为无法上传jar,如果你需要 留言你的邮箱 !