在as3中使用protobuf

  在最近參與開發的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

相關文章
相關標籤/搜索