Golang游戏服务器Leaf接入FlatBuffer步骤记录(附微信开发者平台接入FlatBuffer步骤3)

1.为什么不用Json,效率低

2.为什么不用ProtoBuffer,Laya发布的微信游戏不支持,在官方社区问了一万年官方也没给出个具体的实现方案后,转向了FlatBuffer。开始并不知道能不能用,会不会出现各种问题,因为微信是阉割版的H5所以刚开始并不确定能不能用。在测试过后,明确的告诉各位看客,是可行的。

3.什么是Golang,什么是Leaf这里就不回答了,给出Leaf的连接。Leaffffffffffffffffffffffffff

FlatBuffer作为通讯协议,要满足以下几个条件。

1.要接入Golang编写的服务器要支持Golang,

2.要接入微信开发者平台要支持JavaScript

FlatBuffer这两个都支持,剩下的就是遇水搭桥,逢山开路,一点一点跑通整个流程。

Leaf是个框架有一整套的流程其中,其中对于来自于客户端的请求消息都是由Processor来处理的,发送也是由Processor来发送。

Process在处理完毕序列化和反序列化后,把数据要么通过channal传给其他模块去处理,要么通过建立的客户端连接connection把数据发送出去。

其中Leaf提供了2种数据的序列化处理 1.Json 2.ProtoBuffer

这两种都有序列化和反序列化的处理,如果再写一个也不难,

难就难在FlatBuffer不需要序列化,也不需要反序列化,如果走框架就很麻烦。

为什么麻烦?因为如果想要在Processor Unmarshal这一步拿到FlatBuffer的具体类型,根本不可能啊?FlatBuffer的对象都是这么来的,MyGame.Monster.getRootAsMonster(data),意味着每一个类都有一个自己的“构造”方法了,没有办法通用处理。

但是如果把数据能进入到具体处理那个消息自己的处理函数,这样是不是就可以在那里再具体的调用各自的构造方法去实例化这些数据所代表的对象呢?

最后发现在Route分发数据的时候,数据分为两种格式一种是MsgRaw的格式,一种是当前正在应用的格式。比如如果用的是Json那么就是Json.Unmashal序列化出来的类型。如果是ProtoBuffer那么就是proto.Message这种类型。

MsgRaw说穿了,就是[]byte,没有经过任何处理。

但是FlatBuffer自己本身就是二进制,根本不用序列化和反序列化,是不是会进入MsgRaw的处理流程呢?并不确定。

一开始,用Leaf自带的Json试了下,发现Json传输的时候,如果用

ws.send(JSON.stringify({AppReq: {
				            openid: 'leaf',
				
			}}))

v这种格式,就会走正常的路径,Unmarhal->Route->msgRouter.Go.

但是如果使用

function str2ab(str) {
			console.log("str2ab", str, str.length*2)
			  var buf = new ArrayBuffer(str.length); // 每个字符占用2个字节
			  var bufView = new Uint8Array(buf);
			  for (var i = 0, strLen = str.length; i < strLen; i++) {
			  console.log(i, str.charCodeAt(i))
				bufView[i] = str.charCodeAt(i);
			  }
			  return buf;
		}
			
			var data1 = str2ab(JSON.stringify({"AppReq":{"Purpose":"getApp"}}));
			

            ws.send(data1);

这种格式框架就执行MsgRaw那一条路径,说明猜测是正确的。

但走了MsgRaw的处理流程 调用msgRawHandler([]interface{})完毕就返回了。

所以这个msgRawHandler是重点,也许能在这个函数里实现和之后msgRouter的数据分发效果,于是尝试写了个msgRawHandler函数给MsgInfo赋值,赋值的先不说,先说函数实现:

func (p *Processor) RawRoute(args []interface{}) {
	msgID := args[0].(string)
	//msgRawData := args[1].([]byte)

	i, ok := p.msgInfo[msgID]
	if !ok {
		fmt.Errorf("json.go RawRoute: message %v not registered", msgID)
	}
	
	if i.msgRouter != nil {
		i.msgRouter.Go(i.msgType, args[1], args[2])
	}
	//
}
在函数的末尾同样调用了i.msgRouter.Go进行消息分发,和Route里的处理是一模一样的。

在哪里用这个函数给MsgInfo的msgRawHandler赋值呢?肯定是在注册MsgInfo的时候,因为要给MsgInfo的各个属性赋值。而msgRawHandler也是它的属性之一,也要在那里赋值。

但是注意到一件事,msgID没错,而在Processor.Register(&Monster{})之后返回的就是msgID,于是给MsgInfo添加msgRawHandler的代码和时机就是这样

msgID := Processor.Register(&Monster{})
	Processor.SetRawHandler(msgID, Processor.RawRoute)

忘记提了一件事FlatBuffer的Processor在Register的时候该函数以什么类型作为参数呢?一开始我用的是Register(FlatBuffer.Table,因为fbs文件里定义的结构体就是Table,而且,类比ProtoBuffer用的是proto.Message,而.proto文件里,定义结构体用的是Message,结果发现不行,只能用Json的msg interface{},但仅仅这个还不够,还有接下来的Marshal,SetRouter。最后点开proto.Message的定义,发现它定义在proto/lib.go文件里。灵机一动,找了下FlatBuffer的lib.go文件,还真有,里面是FlatBuffer.

// Message is implemented by generated protocol buffer messages.
type Message interface {
	Reset()
	String() string
	ProtoMessage()
}

// FlatBuffer is the interface that represents a flatbuffer.
type FlatBuffer interface {
	Table() Table
	Init(buf []byte, i UOffsetT)
}

k看上面//那两句话应该差不了,proto.Message 替换为 FlatBuffers.FlatBuffer成了。


下面记录一下几个点:

1.微信里是直接引入js文件,是读取不到flatbuffer的,所以会出现,虽然var flatbuffer = require("flatbuffer.js")但是读取不到 flatBuffer,从网上看的解决方案是因为微信的限制,把flatbuffer.js 里最后一行 this.flatbuffer改为window.protobuffer,因为window是全局可见的,这样就可以访问到了。

2.因为Leaf框架收到消息时ID+Message这种格式,ID是Leaf自动生成的,要保持一致,要使用Leaf提供的Range方法把注册的FlatBuffer的Table所对应的ID导出给客户端使用的。

这意味着客户端发送数据之前,要把该ID加在要发送的FB.Table之前一起发送到服务器。

以下是代码:

var builder = new flatbuffers.Builder(0);

// Create some weapons for our Monster ('Sword' and 'Axe').
var weaponOne = builder.createString('Sword');
var weaponTwo = builder.createString('Axe');

MyGame.Sample.Weapon.startWeapon(builder);
MyGame.Sample.Weapon.addName(builder, weaponOne);
MyGame.Sample.Weapon.addDamage(builder, 3);
var sword = MyGame.Sample.Weapon.endWeapon(builder);

MyGame.Sample.Weapon.startWeapon(builder);
MyGame.Sample.Weapon.addName(builder, weaponTwo);
MyGame.Sample.Weapon.addDamage(builder, 5);
var axe = MyGame.Sample.Weapon.endWeapon(builder);

// Serialize the FlatBuffer data.
var name = builder.createString('Orc');

var treasure = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
var inv = MyGame.Sample.Monster.createInventoryVector(builder, treasure);

var weaps = [sword, axe];
var weapons = MyGame.Sample.Monster.createWeaponsVector(builder, weaps);

  MyGame.Sample.Vec3.startVec3(builder)
MyGame.Sample.Vec3.addX(builder, 1.0)
MyGame.Sample.Vec3.addY(builder, 2.0)
MyGame.Sample.Vec3.addZ(builder, 3.0)
var pos = MyGame.Sample.Vec3.endVec3(builder)


MyGame.Sample.Monster.startMonster(builder);
MyGame.Sample.Monster.addPos(builder, pos);
MyGame.Sample.Monster.addHp(builder, 389);
MyGame.Sample.Monster.addColor(builder, MyGame.Sample.Color.Red)
MyGame.Sample.Monster.addName(builder, name);
MyGame.Sample.Monster.addInventory(builder, inv);
MyGame.Sample.Monster.addWeapons(builder, weapons);
MyGame.Sample.Monster.addEquippedType(builder, MyGame.Sample.Equipment.Weapon);
MyGame.Sample.Monster.addEquipped(builder, weaps[1]);
var orc = MyGame.Sample.Monster.endMonster(builder);

builder.finish(orc);

function intTobytes2(n) {
  var bytes = [];

  for (var i = 0; i < 2; i++) {
    bytes[i] = n >> (8 - i * 8);

  }
  return bytes;
}

var buf = builder.dataBuffer()
//我们暂且将msgId约定为两个字节
var temp1 = new ArrayBuffer(buf.bytes().length + 2)
var newbuf = new Uint8Array(temp1)
//假设msgid为257,将msgid转为2个字节的byte数组
var mId = intTobytes2(2)
console.log(mId)
//将msgid放到buf的头两个字节字节中
newbuf.set([mId[0], mId[1]], 0)
//将之前生成的buffer追加到后边
newbuf.set(builder.asUint8Array(), 2)


function str2ab(str) {
  console.log("str2ab", str, str.length * 2)
  var buf = new ArrayBuffer(str.length); // 每个字符占用2个字节
  var bufView = new Uint8Array(buf);
  for (var i = 0, strLen = str.length; i < strLen; i++) {
    console.log(i, str.charCodeAt(i))
    bufView[i] = str.charCodeAt(i);
  }
  return buf;
}


GameGlobal.wsTask128 = wx.connectSocket({
  url: 'ws://127.0.0.1:80',
        // data: {
        //   AppReq:{'Purpose': 'getApp',}
        // },
        header: {
          'content-type': 'application/json',
        },
        // Sec-Websocket-Protocol : 'protocol1',
        // subprotocol: 'protocol1',
        // protocols: ['protocol1'],
        method: "GET",
        success: function(res){
          console.log("$$LLLLLLLLconnectSocket success", res)
        },
        fail:function(res){
          console.log("$$LLLLLLLLconnectSocket Fail", res)
        },
      })

GameGlobal.wsTask128.onOpen(function (res) {
  console.log("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOPCCCC", GameGlobal.wsTask)
  GameGlobal.wsTask128.send({
    data: newbuf.buffer,
    success: function (res) {
      console.log("LLLLLLLLSSSSuccess789")
      console.log(res)
    },

    fail: function (res) {
      console.log("LLLLLLLLFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFail789", res)
      GameGlobal.webconn = undefined;
    }
  })
})

GameGlobal.wsTask128.onMessage(function (res) {
  console.log("OOOOGlobal.wsTask3.onMessage128_", res, res.data instanceof ArrayBuffer)
  var data = res.data
  var i8Array = new Uint8Array(data.slice(2))
  var fb = new flatbuffers.ByteBuffer(i8Array)

  var monster = MyGame.Sample.Monster.getRootAsMonster(fb)
  console.log(monster.hp())
  var i16Array = new Uint16Array(data.slice(0, 2))
  console.log(i16Array[0])

  const dv = new DataView(data.slice(0, 2));
  console.log(dv.getUint16(0))

})

注意的要点有几个

1.newbuf怎么来的,newbuf做了什么?怎么把ID加进去的,怎么返回二进制给ws.send调用

2.解析从服务器下来的数据时,怎么解析ID,怎么转化成flatbuffer.ByteBuffer再怎么取出monster

3.为什么是发送的时候 设置两字节为0, 接收数据的时候使用Uint16Array()解析ID?因为服务器里规定的ID的最大值就是Uint16,所以给ID预留的空间就是Uint16这么大。至于为什么用DataView来处理,而又不用Uint16Array处理,是因为大小端的问题,Leaf默认大端,网络字节序也是默认大端,但我自己的电脑默认小端,所以要用DataView采用大端的处理方式才能获取一致的数据。至此过了一周,填上了这个坑。。。

猜你喜欢

转载自blog.csdn.net/LightUpHeaven/article/details/81032413
今日推荐