这篇文章 接着 前面的http://my.oschina.net/chenleijava/blog/423503,继续说说flatbuffers的应用
这里主要说的是flatbuffer 作为以cocos2dx引擎为基础 ,网络部分的序列化方案采用flatbuffers。
之所以考虑flatbuffers,依赖性低 ,性能较好。
这里只谈论设计方案,该设计方案对代码设计和结构上有一定的约定(相对订制):
客户端 会接受来自服务器的响应,解码到消息,进行数据处理。针对flatbuffers生成的类和对应处理该消息事的方案映射
在以前protbuffer中有提过(http://my.oschina.net/chenleijava/blog/376560)。这里依旧采用此方案:
注意以下所提及的类或者接口都是代码生成器生成(主要适用于这类方案 定制的,采用java编写),会解析fbs文件,生成对应的controller 文件。编写逻辑 只在
- virtualvoiddispatcherMessage(char*data);
中完成;
fbs测试:
- //1.使用genTools生成对应客户端代码你需要严格约定
- //2.table命名约定上下行消息xxRequestxxResponse
- //3.前后端交互消息存在一个消息ID;命名约定:msgID
- //4.命名空间使用gen---主要限制前端
- namespacegen;
- tableLoginFlatRequest{
- msgID:int=1;
- username:string;
- }
- tableLoginFlatResponse{
- msgID:int=2;
- time:long;
- }
- tableBattleRequest{
- msgID:int=3;
- }
- tableBattleResponse{
- msgID:int=4;
- }
接口类设计:IController.h
- //warnthisisgenfile,don'tmodifyit
- #ifndef_ICONTROLLER_H
- #define_ICONTROLLER_H
- #include<map>
- #include"cocos2d.h"
- #include"flatgen/Game_generated.h"
- usingnamespacecocos2d;
- usingnamespacegen;
- usingnamespacestd;
- #ifndefdelete_array
- #definedelete_array(p)do{if(p){delete[](p);(p)=nullptr;}}while(0)
- #endif//delete_array
- classIController{
- public:
- IController(){}
- /**
- 注册消息ID和对应处理controller的关系
- 以便于快速索引处理
- */
- staticstd::map<int,IController*>controller_map;
- /**
- 负责处理数据分发
- */
- virtualvoiddispatcherMessage(char*data);
- /**
- *获取对应的消息ID
- */
- virtualintmsgID()=0;
- /**
- 转化对应消息为flatbuffers实体
- data+4:消息ID占用4字节
- */
- template<typenameT>constT*getRoot(char*data);
- /**
- *@Author石头哥哥,15-06-0123:06:26
- *
- *@brief注册controolerandmapper
- */
- staticvoidregisterMapperController();
- };
- template<typenameT>
- inlineconstT*IController::getRoot(char*data){
- returnflatbuffers::GetRoot<T>(data+4);
- }
- #endif//_ICONTROLLER_H
.cpp
- //thisisgenfile,don'tmodifyit
- #include"IController.h"
- #include"LoginFlatController.h"
- #include"BattleController.h"
- map<int,IController*>IController::controller_map;
- voidIController::dispatcherMessage(char*data){}
- /**
- *注册controllermapper
- */
- voidIController::registerMapperController(){
- log("%s","registerMapperController开始注册controller......");
- controller_map[LoginFlatController::controller->msgID()]=LoginFlatController::controller;
- controller_map[BattleController::controller->msgID()]=BattleController::controller;
- }
生成对应的子类:LoginFlatController.h
- //thisisgenfile,don'tmodifyit
- #ifndefLoginFlatController_h
- #defineLoginFlatController_h
- #include"IController.h"
- classLoginFlatController:publicIController{
- public:
- staticLoginFlatController*controller;
- public:
- /**
- 负责处理数据分发处理来自服务器数据
- */
- virtualvoiddispatcherMessage(char*data);
- /**
- *获取对应的消息ID
- */
- virtualintmsgID()override;
- };
- #endif//LoginFlatController_h
.cpp
- //thisisgenfile,logiccontroler
- #include"LoginFlatController.h"
- LoginFlatController*LoginFlatController::controller=newLoginFlatController;
- intLoginFlatController::msgID(){
- flatbuffers::FlatBufferBuilderbuilder;
- builder.Finish(CreateLoginFlatResponse(builder));
- automsgID=flatbuffers::GetRoot<LoginFlatResponse>(builder.GetBufferPointer())->msgID();
- builder.ReleaseBufferPointer();
- returnmsgID;
- }
- /**
- 负责处理数据分发处理来自服务器数据
- */
- voidLoginFlatController::dispatcherMessage(char*data){
- autologinflatresponse=getRoot<LoginFlatResponse>(data);
- //TODO::处理来自服务器数据LoginFlatResponse
- }
因为生成代码的工具还在开发中。目前只完成了cpp生成 ,后期支持java(服务器端)---暂时只定制flatbuffer代码生成。
- packagecom.genflat.mapper;/*
- *Copyright(c)2015.
- *游戏服务器核心代码编写人石头哥哥拥有使用权
- *最终使用解释权归创心科技所有
- *联系方式:E-mail:13638363871@163.com;
- *个人博客主页:http://my.oschina.net/chenleijava
- *poweredby石头哥哥
- */
- importorg.apache.commons.io.FileUtils;
- importjava.io.File;
- importjava.io.FilenameFilter;
- importjava.io.IOException;
- importjava.io.RandomAccessFile;
- importjava.util.ArrayList;
- /**
- *@author石头哥哥
- *</P>
- *Date:2015/6/2
- *</P>
- *Time:9:35
- *</P>
- *Package:dcServer-parent
- *</P>
- *<p/>
- *注解:
- */
- publicclassGenFlatCpp{
- /**
- *客户端处理消息类型注意消息命名XXXResponse---服务器响应消息类型
- */
- privatestaticfinalStringclient="Response";
- /**
- *用于生成处理消息类型引用的变量名注意是静态类型
- */
- privatestaticfinalStringcontrollorname="controller";
- /**
- *对应客户端处理来自服务器端消息命名
- *xxxResponse
- *<p/>
- *tableLoginResponse{
- *objectID:int=2;
- *username:string;
- *}
- *<p/>
- *生成处理来自服务器端的Response类型消息<p/>
- *自动生成映射的mapper如:controller_map[msgID<LoginResponse>()]=LoginResponseController::controller;<p/>
- *同时生成对应的controller处理部分<p/>
- *逻辑编写部位:virtualvoiddispatcherMessage(char*data);<p/>
- *
- *@paramfbspathflatbufferfbs所在文件路径<p/>
- *@paramcontrollerpath对应controller生成路径<p/>
- *@throwsIOException
- */
- @SuppressWarnings("ResultOfMethodCallIgnored")
- publicstaticvoidgenCppMapper(Stringfbspath,Stringcontrollerpath)throwsException{
- Filefile;
- //getfbstablelist
- file=newFile(fbspath);
- String[]fbsNames=file.list(newFilenameFilter(){
- @Override
- publicbooleanaccept(Filedir,Stringname){
- returnname.endsWith(".fbs");
- }
- });
- if(fbsNames==null){
- thrownewException("fbsnotfoundinres!!!");
- }
- ArrayList<String>fbsPaths=newArrayList<String>();
- for(Stringfbsname:fbsNames){
- fbsPaths.add(fbspath+"/"+fbsname);
- }
- //读取所有fbs文件获取文件中的tablename
- ArrayList<String>mapperTable=newArrayList<String>();
- //loadingfbsandfiltertablename
- for(Stringfbs:fbsPaths){
- file=newFile(fbs);
- RandomAccessFilerandomAccessFile;
- try{
- randomAccessFile=newRandomAccessFile(file,"r");//O_RDONLY
- Stringtxt="";
- while(txt!=null){
- txt=randomAccessFile.readLine();
- if(txt!=null
- &&txt.startsWith("table")){
- Stringtemp=txt.substring(0,txt.lastIndexOf("{"))
- .replace("table","");
- if(temp.endsWith(client))mapperTable.add(temp.trim());
- }
- }
- randomAccessFile.close();//closerandomAccessFile
- }catch(IOExceptione){
- e.printStackTrace();
- }
- }
- fbsPaths.clear();
- ArrayList<String>controllerList=newArrayList<String>();
- //foricontroller
- StringBuildermapperBuilder=newStringBuilder();
- //controller_map[LoginFlatController::controller->msgID()]=LoginFlatController::controller;
- for(Stringtable:mapperTable){
- Stringcontrollername=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);//addcontroller
- }
- mapperTable.clear();
- ArrayList<String>genIcontrollerHeader=newArrayList<String>();
- genIcontrollerHeader.addAll(controllerList);
- /**
- *don'toverridehadgenxxxcontroller
- *becausemaybehadlogicinit!!!!
- */
- String[]exsiteController=newFile(controllerpath).list(newFilenameFilter(){
- @Override
- publicbooleanaccept(Filedir,Stringname){
- returnname.endsWith(".cpp")&&!name.endsWith("IController.cpp");
- }
- });
- for(Stringname:exsiteController){
- Stringtemp=name.substring(0,name.indexOf("."));
- if(controllerList.contains(temp)){
- controllerList.remove(temp);
- }
- }
- StringBuilderbuilderCpp=newStringBuilder();
- //gen.hfile
- for(Stringcontroller:controllerList){
- Stringdefine=controller+"_h";
- builderCpp.append("//thisisgenfile,don'tmodifyit\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("publicIController{")
- .append("\n")
- .append("public:")
- .append("\n")
- .append("static")
- .append(controller)
- .append("*")
- .append(controllorname)
- .append(";\n")
- .append("public:\n")
- .append("/**\n"+
- "负责处理数据分发处理来自服务器数据\n"+
- "*/\n")
- .append("virtualvoiddispatcherMessage(char*data);\n")
- .append("/**\n"+
- "*获取对应的消息ID\n"+
- "*/\n")
- .append("virtualintmsgID()override;\n")
- .append("};\n")
- .append("#endif")
- .append("//").append(define);
- byte[]cpp_h=builderCpp.toString().getBytes();
- builderCpp.delete(0,cpp_h.length);
- //writeheader
- FilecontrollerFile=newFile(controllerpath);
- if(!controllerFile.exists()){
- controllerFile.mkdirs();
- }
- FileUtils.writeByteArrayToFile(newFile(controllerpath+"/"+controller+".h"),cpp_h);
- //gencontrollercppfile
- StringtableName=controller.substring(0,controller.indexOf("Controller"))+client;
- StringlowTablename=tableName.toLowerCase();
- builderCpp.append("//thisisgenfile,logiccontroler\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::FlatBufferBuilderbuilder;\n"+
- "builder.Finish(Create"+tableName+"(builder));\n"+
- "automsgID=flatbuffers::GetRoot<"+tableName+">(builder.GetBufferPointer())->msgID();\n"+
- "builder.ReleaseBufferPointer();\n"+
- "returnmsgID;\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(newFile(controllerpath+"/"+controller+".cpp"),cpp);
- }
- //IControlleralwaysoverride
- //genIControllergeneratedheadername
- ArrayList<String>generatedHeaders=newArrayList<String>();
- Stringtempname;
- Stringtempnameup;
- charfirst;
- for(Stringname:fbsNames){
- tempname=name.substring(1,name.lastIndexOf("."));
- tempnameup=name.substring(0,name.lastIndexOf(".")).toUpperCase();
- first=tempnameup.charAt(0);
- generatedHeaders.add(first+tempname);
- }
- builderCpp.append("//warnthisisgenfile,don'tmodifyit\n")
- .append("#ifndef").append("_ICONTROLLER_H\n")
- .append("#define").append("_ICONTROLLER_H\n")
- .append("#include<map>\n")
- .append("#include\"cocos2d.h\"\n");
- //appendgeneratedheaderinclude
- for(Stringname:generatedHeaders){
- builderCpp.append("#include\"flatgen/").append(name)
- .append("_generated.h\"").append("\n");
- }
- generatedHeaders.clear();
- builderCpp.append("usingnamespacecocos2d;\n")
- .append("usingnamespacegen;\n")
- .append("usingnamespacestd;\n")
- .append("#ifndefdelete_array\n")
- .append("#definedelete_array(p)do{if(p){delete[](p);(p)=nullptr;}}while(0)\n")
- .append("#endif//delete_array\n")
- .append("classIController{\n")
- .append("\n")
- .append("public:\n")
- .append("IController(){}\n")
- .append("/**\n"+
- "注册消息ID和对应处理controller的关系\n"+
- "以便于快速索引处理\n"+
- "*/\n")
- .append("staticstd::map<int,IController*>controller_map;\n")
- .append("/**\n"+
- "负责处理数据分发\n"+
- "*/\n")
- .append("virtualvoiddispatcherMessage(char*data);\n")
- .append("/**\n"+
- "*获取对应的消息ID\n"+
- "*/\n")
- .append("virtualintmsgID()=0;\n")
- //.append("/**\n"+
- //"*@Author石头哥哥,15-06-0123:06:00\n"+
- //"*\n"+
- //"*@brief获取生成消息对应的msgID\n"+
- //"*\n"+
- //"*@return<#returnvaluedescription#>\n"+
- //"*/\n")
- //.append("template<typenameT>staticintmsgID();\n")
- .append("/**\n"+
- "转化对应消息为flatbuffers实体\n"+
- "data+4:消息ID占用4字节\n"+
- "*/\n")
- .append("template<typenameT>constT*getRoot(char*data);\n")
- .append("/**\n"+
- "*@Author石头哥哥,15-06-0123:06:26\n"+
- "*\n"+
- "*@brief注册controolerandmapper\n"+
- "*/\n")
- .append("staticvoidregisterMapperController();\n")
- .append("};\n")
- //.append("template<typenameT>\n")
- //.append("inlineintIController::msgID(){\n"+
- //"flatbuffers::FlatBufferBuilderbuilder;\n"+
- //"builder.Finish(CreateLoginRequest(builder));\n"+
- //"automsgID=flatbuffers::GetRoot<T>(builder.GetBufferPointer())->msgID();\n"+
- //"builder.ReleaseBufferPointer();\n"+
- //"returnmsgID;\n"+
- //"}\n")
- .append("template<typenameT>\n"+
- "inlineconstT*IController::getRoot(char*data){\n"+
- "returnflatbuffers::GetRoot<T>(data+4);\n"+
- "}")
- .append("\n").append("#endif//_ICONTROLLER_H");
- byte[]data=builderCpp.toString().getBytes();
- builderCpp.delete(0,data.length);
- FileUtils.writeByteArrayToFile(newFile(controllerpath+"/IController.h"),data);
- //genicontrollercpp
- builderCpp.append("//thisisgenfile,don'tmodifyit\n")
- .append("#include\"IController.h\"\n");
- for(Stringcontroller: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("voidIController::dispatcherMessage(char*data){}\n")
- .append("/**\n"+"*注册controllermapper\n"+"*/\n"+"voidIController::registerMapperController(){\n"+
- "log(\"%s\",\"registerMapperController开始注册controller......\");\n")
- .append(mapperBuilder.toString()).append("}\n");
- data=builderCpp.toString().getBytes();
- builderCpp.delete(0,data.length);
- FileUtils.writeByteArrayToFile(newFile(controllerpath+"/IController.cpp"),data);
- System.out.println("-------gensuccess!!!---------------------");
- }
- }
- packagecom.genflat.mapper;/*
- *Copyright(c)2015.
- *游戏服务器核心代码编写人石头哥哥拥有使用权
- *最终使用解释权归创心科技所有
- *联系方式:E-mail:13638363871@163.com;
- *个人博客主页:http://my.oschina.net/chenleijava
- *poweredby石头哥哥
- */
- importjava.io.File;
- importjava.io.IOException;
- /**
- *@author石头哥哥
- *</P>
- *Date:2015/6/2
- *</P>
- *Time:9:34
- *</P>
- *Package:dcServer-parent
- *</P>
- *<p/>
- *注解:
- */
- publicclassGenFlatMapper{
- /**
- *defaultgenc++
- *args[0]-l
- *args[1](java,cpp)
- *args[2]-p
- *args[3](controllerfilepath)
- *args[4]-o
- *args[5]fbsfilepath
- *
- *@paramargs
- */
- publicstaticvoidmain(String[]args)throwsIOException{
- Stringshow=">>>>flatbuffergen"+
- "\n该工具可以生成基于flatbuffers的代码,接口说明如下\n"+
- "使用命令:java-jargenflatMapper.jar-lcpp-ccontrollerpath-ffbspath\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("--------begingenclientcode----------------");
- if(args.length<6){
- System.err.println(show);
- System.exit(0);
- }
- Stringl=args[1];
- Stringcontrollerpath=args[3];
- Stringfbspath=args[5];
- if(l.equals("java")){
- System.err.println("onlysupportclient,c++\n");
- System.err.println("useeg:\n"+
- "java-jargenflatMapper.jar-lcpp"+
- "-ccontrollerpath-ffbspath");
- System.exit(0);
- }elseif(l.equals("cpp")){
- try{
- GenFlatCpp.genCppMapper(fbspath,controllerpath);
- }catch(Exceptione){
- e.printStackTrace();
- }
- }
- }else{
- try{
- //gencurrentfile
- System.out.println("toolswillgentocurrentfiles,ifyourfbsinresfile!!!");
- Filefile=newFile("./controller");
- if(!file.exists())file.mkdirs();
- //findfbs
- GenFlatCpp.genCppMapper("res","./controller");
- }catch(Exceptione){
- e.printStackTrace();
- }
- }
- }
- }
该生代码成器约定如下--如果你使用该方案,那么请严格遵循改约束,或许你有更好的idea:
- ###基于flatbuffers,使用fbs文件生成对应前端代码。
- ###完成对应的controller和消息ID映射,目前暂时支持c++。
- >1.使用genTools生成对应客户端代码你需要严格约定
- 2.table命名约定上下行消息xxRequestxxResponse
- 3.前后端交互消息存在一个消息ID;命名约定:msgID
- >4.命名空间使用gen---主要限制前端
- namespacegen;
- //下行消息xxRequest
- tableLoginRequest{
- msgID:int=1;//消息IDmsgID
- username:string;
- }
- //下行消息xxResponse
- tableLoginResponse{
- msgID:int=1;//消息IDmsgID
- username:string;
- }
- ####如何使用:
- 该工具可以生成基于flatbuffers的代码,接口说明如下
- 使用命令:
- java-jargenflatMapper.jar-lcpp-pcontrollerpath-ofbspath
- >-l:编程语言类型这里暂时支持c++;
- >-c:游戏中处理对应消息的controller文件路径;
- >-f:编写的fbs文件路径;
因为无法上传jar,如果你需要 留言你的邮箱 !