網遊通訊傳輸可變長度的數值和數組

何爲可變長度的數值(int,long等)

其實現在大部分網絡遊戲(端遊、頁遊、手遊都一樣),在客戶端和服務端通訊,關於數值都會採用可變長度的方式來傳輸,從簡而減小通訊量。
一般情況,客戶端和服務端進行網絡的socket通訊,都是採用二進制數值來進行的(也有采用字符串)。
可變長度的int是指根據實際的數值在網絡傳輸中動態地改變長度。比如int在傳輸中可以變爲byte,short,從而減少int的長度。文本會就Java服務端,H5和AS3客戶端之間的通訊來進行講解。

固定長度數值的網絡發送

表示數組的長度一般都是固定,比如short是2個字節,int4個字節,long8個字節。那麼客戶端和服務端通訊的時候,傳輸short,int等數據過程的時候,如果不實現一些算法,則是直接傳輸,那麼就是數值類型有多少個字節,就往數據流(ByteArray)寫多少個字節。
但是實際應用中,雖然定義了一個數值類型,但是他表示的值經常在byte,short,int等不同範圍之間切換。比如定義用戶的金錢

//java
int moeny = 100  //用戶的金錢,int 4個bytes
int playerId = 100000001;
//AS3
var moeny:int  = 100;
var playerId:int =  100000001;

js只有number,如果不進行字節長度控制的話,每次都得發8字節過去給js客戶端

//js 只有number,8個字節
var money = 100;
var playerId = 100000001;

很顯然,如果只發送一個10000或者127以內的數值,直接寫4個字節的int,是非常浪費流量。
比如發送moeny這個字段就浪費了,playerId比較大那就是正常採用int了。js的話損失更大了,number是8個字節來進行發送。
所以一些好的通訊協議庫會採用根據數值的實際值大小來動態發送byte,short,int這些類型。
然後再收到的那一端再把byte,short轉換成int或者long,number類型。
比如Google protobuf就可以把通訊的內容壓縮得非常小。
當然具體網絡傳輸協議採用什麼樣的方式,是個比較廣的問題,一般是要根據項目的實際情況來處理,不在本章的討論範圍。
固定長度的發送和接受代碼,比如發送playerId和moeny到服務端(演示爲僞代碼,後面會給出全部代碼)

//AS3
var bytes:ByteArray = new ByteArray();
bytes.writeInt(playerId);
bytes.writeInt(moeny);
//java 接受數據
ByteBuf buf = frame.content();
int money = buf.readInt();
int playerId = buf.readInt();

客戶端和服務端收發保持一直,這樣就可以進行通訊了。
實際項目中,也會對一些數值定義類型做優化,不會全部統一int或者number。
比如表示類型,雖然顯示的時候是用int來表示,但是實際寫給服務器的時候,是採用採用byte。

//AS3
var type:int = 5;
var bytes:ByteArray = new ByteArray();
bytes.writeByte(type);
//java 接受數據
ByteBuf buf = frame.content();
int type = buf.readByte();

這樣是屬於主動地節省字節,因爲已經明確知道type不會超過127,所以寫byte類型是安全的。

發送可變長度的數值的通訊機制

有些數據的值變化比較大,可能是0到上百萬之間的變化,這種情況,我們只能使用int來表。比如money(遊戲中的元寶)。剛開始玩家可能只有幾十個元寶或者0,久一點可能1,2萬了。土豪可能直接來個10w8w的。
假如我們還是發送定長的int,對於沒有元寶或者低於30000元寶的玩家來說,就浪費通訊字節了。
所以原理是這樣的,每次發送的時候,判斷一下數值的大小,默認是寫byte,如果大於或者等於某個規定值,則是表示類型。比如發送一個int到服務器去,下面的演示代碼:
//先判斷數據的長度

    /**
    * 寫可變的整形數據類型 
    * @param bytes 二進制數組
    * @param value
    */      
    public static function writeVaryInt(bytes:ByteArray,value:int):void
    {
        //用-128、-127,-126來做標識符,分別表示short int 和long類型
        if(value <= 127 && value > -126)
        {
            //最小範圍內,直接寫byte
            bytes.writeByte(value);
            return ;
        }
        if(value <= 32767 && -value >= -32768)
        {
            //short -128
            bytes.writeByte(-128);
            bytes.writeShort(value);
            return ;
        }
        if(value <= 2147483647 && -value >= -2147483648)
        {
            //int -127
            bytes.writeByte(-127);
            bytes.writeInt(value);
            return ;
        }
        //剩下的都是long了
        bytes.writeByte(-126);
        bytes.writeDouble(value);
}

Java對應的讀取內容是這樣的:

/**
 * 讀取可變長度的整形數據 
 * @param bytes 二進制數據
 * @return 相應的整形
 */     
public static long readVaryInt( ByteBuf bytes)
{
    byte type = bytes.readByte();
    //byte
    if(type <= 127 && type > -126)
        return type;
    //short
    if(type == -128)
        return bytes.readShort();
    //int
    if(type == -127)
        return bytes.readInt();
    //double(long)
    return (long)bytes.readDouble();
}

其實還可以進一步去擴展,比如還可以寫單精度和雙精度,以及混合之類等等。

數組長度的傳輸方式

可以很容易地根據這個原理,來寫數組長度,而且數組長度比數值更簡單。

/**
 * 動態寫數組的長度
 * @param bytes
 * @param ary
 */
public static function writeArySize(bytes:ByteArray,ary:Array):void
{
    //數組的int長度比較清晰,不會有負的,所以採用-1來表示
    var len:int = ary.length;
    if(len <= 127 )
    {
        bytes.writeByte(len);
        return ;
    }
    if(len <= 32767)
    {
        //寫個標識符
        bytes.writeByte(-1);
        //寫個short的長度
        bytes.writeShort(length);
        return ;
    }
    //剩下都寫int了,long不用想,太可怕
    bytes.writeByte(-2);
    //寫個short的長度
    bytes.writeInt(length);
}

讀取就不貼了,只要按照寫的規則讀出來就行了。

附錄:Html5版本的可變長度數值讀取方法

這裏採用的是白鷺引擎寫的,語言是TypeScript。跟AS3的幾乎一樣的邏輯和寫法。

/**
 * 寫可變的整形數據類型
 * @param bytes 二進制數組
 * @param value
 */
static writeVaryInt(bytes:egret.ByteArray,value:number):void
{
    //用-128、-127,-126來做標識符,分別表示short int 和long
    //寫byte
    if(value <= 127 && value > -126)
    {
        bytes.writeByte(value);
        return ;
    }
    if(value <= 32767 && -value >= -32768)
    {
        //short -128
        bytes.writeByte(-128);
        bytes.writeShort(value);
        return ;
    }
    if(value <= 2147483647 && -value >= -2147483648)
    {
        //int -127
        bytes.writeByte(-127);
        bytes.writeInt(value);
        return ;
    }
    //剩下的都是long了
    bytes.writeByte(-126);
    bytes.writeDouble(value);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章