何爲可變長度的數值(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);
}