在最近參與開發的adobe air項目中,先後端的通訊協議從XML、JSON再到protobuf,最後選擇protobuf緣由,主要是先後端維護protobuf協議就好了,同時還能夠利用IDE作一些編譯檢查。目前我能找到的protobuf as3開源庫,都存在一些問題:不支持嵌套類,生成代碼沒法編譯等等。因而花了一點時間,參考google protobuf相關說明,編寫protobuf-as3以及protoc-as3,用於支持運行時及代碼生成,https://github.com/zhongfq/protobuf-as3。前端
protobuf-as3庫只支持proto3格式,proto3列出的數據類型基本都支持。git
如下proto文件將生成5個as3類:Token.as、Token$Type.as、TokenBindingResponseCode.as、TokenBindingRequest.as、TokenBindingResponse.as。咱們以‘$’做爲分隔符以實現protobuf類的嵌套。github
syntax = "proto3"; package user.token; message Token { enum Type { NONE = 0; QQ = 1; WEIBO = 2; WECHAT = 3; } Type type = 1; string value = 2; } enum TokenBindingResponseCode { ERROR = 0; OK = 200; EXIST = 400; } message TokenBindingRequest { Token token = 1; } message TokenBindingResponse { TokenBindingResponseCode responseCode = 1; Token token = 2; }
var token:Token = new Token(); token.type = Token$Type.QQ; token.value = "xxxxxx"; var req:TokenBindingRequest = new TokenBindingRequest(); req.token = token;
protobuf數據不包含任何的元數據,因此對於一段數據而言,你是不可以區別這段數據是屬於哪一條協議的,所以對於先後端的交互,我引入12字節的頭部(length、session、type)。後端
package network { import flash.utils.ByteArray; internal class Packet { private var _session:int; private var _type:int; private var _data:ByteArray; public function Packet(session:int, type:int, data:ByteArray) { _session = session; _type = type; _data = data; _data.position = 0; } public function get session():int { return _session; } public function get type():int { return _type; } public function get data():ByteArray { return _data; } } }
package network { import flash.net.Socket; import flash.utils.ByteArray; import flash.utils.Endian; internal class PacketBuffer { //length + session + type private static const HEADER_LENGTH:int = 12; private var _length:int = 0; private var _position:int = 0; private var _packet:Packet = null; public function PacketBuffer() { } public function pack(socket:Socket):Packet { // 消息長度至少爲12, length + session + type if (socket.bytesAvailable <= 0 || (_length == 0 && socket.bytesAvailable < HEADER_LENGTH)) { return null; } socket.endian = Endian.BIG_ENDIAN; if (_length == 0) { _length = socket.readInt(); // valid packet: length + session + type >= 12 if (_length < HEADER_LENGTH) { socket.close(); return null; } var session:int = socket.readInt(); var type:int = socket.readInt(); _position = 0; _packet = new Packet(session, type, new ByteArray()); } if (socket.bytesAvailable > 0 || _length == HEADER_LENGTH) { var len:int = Math.min(_length - HEADER_LENGTH - _position, socket.bytesAvailable); socket.readBytes(_packet.data, _position, len); _position += len; if (_position == _length - HEADER_LENGTH) { var ret:Packet = _packet; _packet = null; _position = 0; _length = 0; return ret; } } return null; } } }
syntax = "proto3"; enum CommandCode { // gateway GATEWAY = 0x0000; // login SIGN_IN = 0x0100; CHECK_ACCOUNT = 0x0101; SIGN_UP = 0x0102; QUICK_ENROLL = 0x0103; // user USER_MODIFIER = 0x0200; TOKEN_BINDING = 0x0201; USER_STAT = 0x0202; USER_AVATAR = 0x0203; }
CommandCode做爲數據包的類型標識,用於上面所說頭部中的類型段。服務器
對於發送一個數據包,方式以下:session
private function sendPacket(packet:Packet):void { // length + session + type _socket.endian = Endian.BIG_ENDIAN; _socket.writeInt(packet.data.length + 12); _socket.writeInt(packet.session); _socket.writeInt(packet.type); _socket.writeBytes(packet.data); _socket.flush(); }
對於接收數據包,方式以下:socket
private function socketDataHandler(event:ProgressEvent):void { var packet:Packet; while ((packet = _packetBuffer.pack(_socket)) != null) { dispatchPacket(packet); } }
session變量由服務器維護,服務器每發送一條用戶數據,session就+1,前端斷線重連時,發送session與服務做對比,若是同樣,就是執行斷線重連,若是不同就強制走登陸流程。google