作为一个有逼格的实时动态网站,websocket是必不可少的;对于数据量不大的场景,使用json传递数据便绰绰有余了。然后当你想偷懒,直接这么写的时候,老板就会来敲打你的狗头:“现在数据量变多了,网站怎么不动了啊?”
于是,必须使用protobuf了。
protobuf的简介就不多说了,百度都有。
先是本篇中用到的proto定义,具体意义便不用细究了,只是举个例子而已。
syntax = "proto3";
package dsproto;
message EluItem {
uint32 timestamp = 1;
string label = 2;
string name = 3;
float price = 4;
float vol = 5;
float bsv = 6;
float ssv = 7;
float highLimit = 8;
float lowLimit = 9;
}
enum MsgType {
SINGLE_ELU = 0;
ALL_ELU = 1;
}
message Msg{
MsgType type = 1;
oneof content {
SingleEluWrapper singleEluWrapper = 2;
AllEluWrapper allEluWrapper = 3;
}
}
message SingleEluWrapper {
EluItem eluItem = 1;
}
message AllEluWrapper {
repeated EluItem eluItems = 1;
}
后台写法没什么多说的。这里以golang为例,用到的protobuf库在此。
首先便是把业务bean转换为protobuf生成的bean,如下:
func (eluItem *EluItem) asProto() *dsproto.EluItem {
return &dsproto.EluItem{
Timestamp: eluItem.Timestamp,
Label: eluItem.Label,
Name: eluItem.Name,
Price: eluItem.Price,
Vol: eluItem.Vol,
Bsv: eluItem.BSV,
Ssv: eluItem.SSV,
HighLimit: eluItem.HighLimit,
LowLimit: eluItem.LowLimit,
}
}
然后提供打包成最终需要序列化的消息包的方法:
func packEluItem(eluItem *EluItem) *dsproto.Msg{
msg := &dsproto.Msg{
Type: dsproto.MsgType_SINGLE_ELU,
Content: &dsproto.Msg_SingleEluWrapper{
SingleEluWrapper: &dsproto.SingleEluWrapper{
EluItem: eluItem.asProto(),
},
},
}
return msg
}
这个dsproto.Msg就是我们需要往客户端发送的数据了。例如:
func sendMsg(session *wssession.Session, msg *dsproto.Msg) {
if data, err := proto.Marshal(msg); err == nil {
session.Send(data)
}
}
此时的data已经是序列化好的二进制数据,最后一步便是通过websocket发送出去:
s.socket.WriteMessage(websocket.BinaryMessage, data)
此处一定要是BinaryMessage,而不是TextMessage
前端用到了protobuf.js这个库。请仔细阅读下面的文档, 并将其安装到当前前端工程。
这里我选择将proto编译成json文件,用到了pbjs这个工具。
所以先npm install pbjs --global安装之。
然后使用pbjs -t json src/proto/dsproto.proto -o src/proto/dsproto.json将其编译为json文件,这里为了方便,可以将此命令写入package.json:
"scripts": {
"dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",
"start": "npm run dev",
"lint": "eslint --ext .js,.vue src",
"build": "node build/build.js",
"proto": "pbjs -t json src/proto/dsproto.proto -o src/proto/dsproto.json"
},
这样每次proto文件变动后,只需要npm run proto即可同步更新。
接下来开始正式撸码:
一个全局helper类是必不可少的,例如:
import dsprotoJson from '@/proto/dsproto.json'
import protobuf from 'protobufjs'
class ProtoHelper {
constructor () {
let root = protobuf.Root.fromJSON(dsprotoJson)
this.Msg = root.lookup('dsproto.Msg')
this.MsgType = root.lookup('dsproto.MsgType')
}
}
export default new ProtoHelper()
将需要用到的类型缓存在protoHelper中。
于是,可以在websocket的onmessage方法中愉快的使用protobuf了:
onmessage (e) {
console.log(protoHelper.Msg.decode(new Uint8Array(e.data)))
}
protoHelper.Msg.decode(new Uint8Array(e.data))得到的便是Msg的实体了,可以发给上层业务逻辑进行处理。
当然,到此还并没有完。最为核心的一个配置,便是将你的websocket做如下设置:
ws.binaryType = 'arraybuffer'