flatbuffers 可以应用到游戏中。媲美google protobuffer。
https://github.com/google/flatbuffers
http://google.github.io/flatbuffers/index.html
下载flatbuffers 编译对应平台的flatc。
接下来是编写fbs:
namespace com.dc.gameserver.genflat; table LoginRequest{ msgID:int=1; username:string; password:string; time:long; } /// table LoginResponse{ msgID:int=2; time:long; uid:string; }
详细说明见:http://google.github.io/flatbuffers/md__schemas.html
flatc脚本方式:http://google.github.io/flatbuffers/md__compiler.html
flatc.exe -j -o ../../java/ game.fbs
会生成对应的java文件.
// automatically generated, do not modify package com.dc.gameserver.genflat; import com.google.flatbuffers.FlatBufferBuilder; import com.google.flatbuffers.Table; import java.nio.ByteBuffer; import java.nio.ByteOrder; public class LoginRequest extends Table { public static LoginRequest getRootAsLoginRequest(ByteBuffer _bb) { return getRootAsLoginRequest(_bb, new LoginRequest()); } public static LoginRequest getRootAsLoginRequest(ByteBuffer _bb, LoginRequest obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__init(_bb.getInt(_bb.position()) + _bb.position(), _bb)); } public LoginRequest __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; } public int msgID() { int o = __offset(4); return o != 0 ? bb.getInt(o + bb_pos) : 1; } public String username() { int o = __offset(6); return o != 0 ? __string(o + bb_pos) : null; } public ByteBuffer usernameAsByteBuffer() { return __vector_as_bytebuffer(6, 1); } public String password() { int o = __offset(8); return o != 0 ? __string(o + bb_pos) : null; } public ByteBuffer passwordAsByteBuffer() { return __vector_as_bytebuffer(8, 1); } public long time() { int o = __offset(10); return o != 0 ? bb.getLong(o + bb_pos) : 0; } public static int createLoginRequest(FlatBufferBuilder builder, int msgID, int username, int password, long time) { builder.startObject(4); LoginRequest.addTime(builder, time); LoginRequest.addPassword(builder, password); LoginRequest.addUsername(builder, username); LoginRequest.addMsgID(builder, msgID); return LoginRequest.endLoginRequest(builder); } public static void startLoginRequest(FlatBufferBuilder builder) { builder.startObject(4); } public static void addMsgID(FlatBufferBuilder builder, int msgID) { builder.addInt(0, msgID, 1); } public static void addUsername(FlatBufferBuilder builder, int usernameOffset) { builder.addOffset(1, usernameOffset, 0); } public static void addPassword(FlatBufferBuilder builder, int passwordOffset) { builder.addOffset(2, passwordOffset, 0); } public static void addTime(FlatBufferBuilder builder, long time) { builder.addLong(3, time, 0); } public static int endLoginRequest(FlatBufferBuilder builder) { int o = builder.endObject(); return o; } };
// automatically generated, do not modify package com.dc.gameserver.genflat; import com.google.flatbuffers.FlatBufferBuilder; import com.google.flatbuffers.Table; import java.nio.ByteBuffer; import java.nio.ByteOrder; /// public class LoginResponse extends Table { public static LoginResponse getRootAsLoginResponse(ByteBuffer _bb) { return getRootAsLoginResponse(_bb, new LoginResponse()); } public static LoginResponse getRootAsLoginResponse(ByteBuffer _bb, LoginResponse obj) { _bb.order(ByteOrder.LITTLE_ENDIAN); return (obj.__init(_bb.getInt(_bb.position()) + _bb.position(), _bb)); } public LoginResponse __init(int _i, ByteBuffer _bb) { bb_pos = _i; bb = _bb; return this; } public int msgID() { int o = __offset(4); return o != 0 ? bb.getInt(o + bb_pos) : 2; } public long time() { int o = __offset(6); return o != 0 ? bb.getLong(o + bb_pos) : 0; } public String uid() { int o = __offset(8); return o != 0 ? __string(o + bb_pos) : null; } public ByteBuffer uidAsByteBuffer() { return __vector_as_bytebuffer(8, 1); } public static int createLoginResponse(FlatBufferBuilder builder, int msgID, long time, int uid) { builder.startObject(3); LoginResponse.addTime(builder, time); LoginResponse.addUid(builder, uid); LoginResponse.addMsgID(builder, msgID); return LoginResponse.endLoginResponse(builder); } public static void startLoginResponse(FlatBufferBuilder builder) { builder.startObject(3); } public static void addMsgID(FlatBufferBuilder builder, int msgID) { builder.addInt(0, msgID, 2); } public static void addTime(FlatBufferBuilder builder, long time) { builder.addLong(1, time, 0); } public static void addUid(FlatBufferBuilder builder, int uidOffset) { builder.addOffset(2, uidOffset, 0); } public static int endLoginResponse(FlatBufferBuilder builder) { int o = builder.endObject(); return o; } };
消息设计原则除了基础的游戏数据会有一个消息ID。
获取生成Table消息ID可以这样:
LoginResponse.getRootAsLoginResponse(ByteBuffer.allocate(4)).msgID();
修改了下源码(Table),避免new一个ByteBuffer,可以这样
// Look up a field in the vtable, return an offset into the object, or 0 if the field is not // present. protected int __offset(int vtable_offset) { if (bb == null) return 0; // get msgID ,ignore allocate ByteBuffer int vtable = bb_pos - bb.getInt(bb_pos); return vtable_offset < bb.getShort(vtable) ? bb.getShort(vtable + vtable_offset) : 0; }
new LoginResponse().msgID();
flatbuffer中有对默认的数值填充,将会不对其存储!。如没有默认值的字段都为0,不管基础数据类型还是string.;存在默认值如id:int=1;在填充数据value==1时,不会被存储。
反正默认值不被存储就对了,详见代码Table;当然可以强制进行存储,builder.setForcexx(true);
netty只负责网络传输。解析网络字节码就对了,根据协议包长+消息体(ID+游戏操作数据)。
针对java,采用多态的方式进行处理对应的消息体。
static final IController game_controllers[] = new IController[2];
存储消息处理类的引用。
Controller接口设计:
/** * 基于flatBuffers 结构数据传输 * * @param data cast subclass of table . * @param player game session * @throws Exception */ void DispatchFlatBuffer(final byte[] data, final PlayerInstance player) throws Exception;
实现类:
/* * Copyright (c) 2015. * 游戏服务器核心代码编写人石头哥哥拥有使用权 * 最终使用解释权归创心科技所有 * 联系方式:E-mail:[email protected] ; * 个人博客主页:http://my.oschina.net/chenleijava * powered by 石头哥哥 */ package com.dc.gameserver.servercore.controller.testflatbuffercontroller; import com.dc.gameserver.genflat.LoginRequest; import com.dc.gameserver.genflat.LoginResponse; import com.dc.gameserver.model.character.PlayerInstance; import com.dc.gameserver.servercore.controller.abstractController.AbstractController; import com.google.flatbuffers.FlatBufferBuilder; import com.google.protobuf.MessageLite; import org.springframework.stereotype.Service; import java.nio.ByteBuffer; import java.util.UUID; /** * @author 石头哥哥 * </P> * Date: 2015/6/1 * </P> * Time: 14:36 * </P> * Package: dcServer-parent * </P> * <p/> * 注解: 测试flatbuf 转化到游戏逻辑处理 */ @Service public class TestFlatBufferController extends AbstractController { /** * spring 容器初始化 加载并初始化相应控制器处理句柄 * 非spring环境下 可以采用静态的初始化方案 */ @Override public void PostConstruct() { game_controllers[new LoginRequest().msgID()] = this; } /** * messageLite数据结构体分发 * * @param messageLite 数据载体 * @param player active object * @throws Exception 注意messageLite应该递交给GC直接处理 ,set null */ @Override @Deprecated public void DispatchMessageLit(MessageLite messageLite, PlayerInstance player) throws Exception { } /** * 基于flatBuffers 结构数据传输 * * @param data cast subclass of table . msgID + 游戏数据(可反序列化为flatbuffers Table 子类 这里如LoginRequest) * @param player game session * @throws Exception */ @Override public void DispatchFlatBuffer(byte[] data, PlayerInstance player) throws Exception { //4 为消息ID 所占用的位置 LoginRequest login = LoginRequest.getRootAsLoginRequest(ByteBuffer.wrap(data, 4, data.length - 4)); String username = login.username(); String password = login.password(); // do something // get msgID int msgID = new LoginResponse().msgID(); FlatBufferBuilder builder = new FlatBufferBuilder(8); builder.finish(LoginResponse.createLoginResponse(builder, msgID , System.currentTimeMillis() , builder.createString(UUID.randomUUID().toString()))); wrappedBufferInt(msgID, builder); } public static void main(String[] args) { //builder data and finish it int msgID = new LoginResponse().msgID(); FlatBufferBuilder builder = new FlatBufferBuilder(8); builder.finish(LoginResponse.createLoginResponse(builder, msgID , System.currentTimeMillis() , builder.createString(UUID.randomUUID().toString()))); wrappedBufferInt(msgID, builder); } }
由于flatbuf相对protobuffer 体积较小 在cocos2dx中应用 相对较为方便。 目前已经有一套完整的应用解决方案思路(前后端代码生成器)。欢迎交流!