Protobuf 全稱Protocol Buffers 是一種輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲格式,可以用于結(jié)構(gòu)化數(shù)據(jù)串行化,很適合做數(shù)據(jù)存儲或 RPC 數(shù)據(jù)交換格式。它可用于通訊協(xié)議、數(shù)據(jù)存儲等領(lǐng)域的語言無關(guān)、平臺無關(guān)、可擴(kuò)展的序列化結(jié)構(gòu)數(shù)據(jù)格式。目前提供了 C++、Java、Python、C#等多種語言的 API。Protobuf是google開源的序列化和反序列化工具,主要是用在網(wǎng)絡(luò)游戲的消息結(jié)構(gòu)體定義上。它相對于XML文件和Json文件性能更好,效率更高,在在網(wǎng)站 http://code.google.com/p/protobuf/downloads/list上可以下載 Protobuf 的源代碼,它的文件格式是以.proto為擴(kuò)展名的文件。為了便于大家更好的理解其使用原理,下面就通過案例的方式給大家講解。
Protobuf消息結(jié)構(gòu)體定義
在使用Protobuf定義消息結(jié)構(gòu)體時(shí)首先要搞明白其語法結(jié)構(gòu),Protobuf定義的消息由至少一個(gè)字段組合而成,類似于C語言中的結(jié)構(gòu)。每個(gè)字段都有一定的格式。其限定修飾required、optional、repeated三個(gè)修飾符。
Required修飾符表示是一個(gè)必須字段,必須相對于發(fā)送方,在發(fā)送消息之前必須設(shè)置該字段的值,對于接收方,必須能夠識別該字段的意思。發(fā)送之前沒有設(shè)置required字段或者無法識別required字段都會引發(fā)編解碼異常,導(dǎo)致消息被丟棄。
Optional修飾符表示是一個(gè)可選字段,可選對于發(fā)送方,在發(fā)送消息時(shí),可以有選擇性的設(shè)置或者不設(shè)置該字段的值。對于接收方,如果能夠識別可選字段就進(jìn)行相應(yīng)的處理,如果無法識別,則忽略該字段,消息中的其它字段正常處理。—因?yàn)閛ptional字段的特性,很多接口在升級版本中都把后來添加的字段都統(tǒng)一的設(shè)置為optional字段,這樣老的版本無需升級程序也可以正常的與新的軟件進(jìn)行通信,只不過新的字段無法識別而已,因?yàn)椴⒉皇敲總€(gè)節(jié)點(diǎn)都需要新的功能,因此可以做到按需升級和平滑過渡。
Repeated:表示該字段可以包含0~N個(gè)元素。其特性和optional一樣,但是每一次可以包含多個(gè)值,可以看作是在傳遞一個(gè)數(shù)組的值或者List列表數(shù)值。開發(fā)網(wǎng)絡(luò)游戲時(shí),經(jīng)常會定義消息結(jié)構(gòu)體,這些消息結(jié)構(gòu)體會在客戶端和服務(wù)器中都會用到,所以只需要定義一套就可以了,現(xiàn)在移動端大部分用戶都在使用Unity引擎開發(fā),所以這些結(jié)構(gòu)體需要轉(zhuǎn)成C#語言。下面開始從結(jié)構(gòu)體定義開始講起。
編寫Protobuf結(jié)構(gòu)體
網(wǎng)絡(luò)消息的定義通常會使用json文件或者二進(jìn)制文件或者自定義結(jié)構(gòu)體,現(xiàn)在使用protobuf定義消息結(jié)構(gòu)體的公司越來越多,逐漸成為消息結(jié)構(gòu)體定義的主流,這也要感謝google提供了一個(gè)開源的序列化和反序列化工具。本節(jié)以實(shí)際項(xiàng)目開發(fā)的案例給大家介紹一下網(wǎng)絡(luò)消息結(jié)構(gòu)體的定義,任何大型網(wǎng)絡(luò)游戲都有角色的定義。首先從角色的定義說起,角色消息包括很多的屬性。定義結(jié)構(gòu)體如下所示,其中message是結(jié)構(gòu)體的修飾符,是必須的。結(jié)構(gòu)體定義如下所示:
//角色信息結(jié)構(gòu)
message msgcharinfo{ optional uint32 uaid = 1; //用戶ID optional uint32 charid = 2; //角色I(xiàn)D optional uint32 kind = 3; //角色種類 optional string name = 4; //角色名字 optional string head = 5; //頭像ID optional uint32 level = 6; //角色等級 optional uint32 exp = 7; //角色經(jīng)驗(yàn) optional uint32 phypower = 8; //物理攻擊 optional uint32 leadership = 9; //領(lǐng)導(dǎo)標(biāo)記 optional uint32 friendnum = 10; //朋友數(shù)量 optional uint32 gamecoin = 11; //游戲貨幣 optional uint32 diamond = 12; //鉆石數(shù)量};
以上是網(wǎng)絡(luò)游戲中完整的角色定義,包括用戶id,游戲中角色id等信息。它存放的文件擴(kuò)展名為.proto。結(jié)構(gòu)體中的各個(gè)項(xiàng)的修飾都是optional,也就是可選項(xiàng),可以不用賦值。protobuf自身定義的文件是文本文件。如果將該文件直接放到Unity工程中,Unity是不會識別的。這就需要將其轉(zhuǎn)成Unity可識別的腳步C#文件。再舉一個(gè)例子,還是結(jié)構(gòu)體定義是枚舉定義代碼如下所示:
//初始化角色獎(jiǎng)勵(lì)信息
enum enumGetCharRewardResult{ Success = 0; //成功獲取角色 SystemError = 1; //系統(tǒng)錯(cuò)誤 NewChar = 2; //創(chuàng)建新角色獎(jiǎng)勵(lì)信息};
該結(jié)構(gòu)體是以enum修飾的枚舉類型,里面有三項(xiàng),枚舉定義跟C++或者Java定義的類似,枚舉定義的內(nèi)部成員不需要任何符號修飾。假設(shè)以上內(nèi)容是在文件common.proto文件定義的,下面我們再定義一個(gè)proto文件,其內(nèi)容如下所示:
package clientmsg;import "common.proto";message C2SNameRepetition{ optional msgcharinfo charinfo = 1; //創(chuàng)建角色 optional uint32 mapid = 2; optional uint32 cityid = 3;};
給大家解釋一下代碼,第一行package clientmsg;表示的是模塊的封裝,其含義類似C++或者C#的namespace命名空間,第二行import "common.proto";表示的是引用該文件,目的是需要用到該文件已定義的結(jié)構(gòu)體,例如message C2SNameRepetition定義的內(nèi)容中的語句optional msgcharinfo charinfo = 1;它引用的是common.proto文件中已定義的msgcharinfo的結(jié)構(gòu)體,protobuf支持這種引用關(guān)系,從中可以看出Protobuf語言也是比較靈活的,文件與文件之間是可以互相引用的,接下來開始介紹轉(zhuǎn)換工具的制作了。
Protobuf轉(zhuǎn)換工具制作
定義好了proto文件后,如果直接放到unity中,它只能被作為文本文件,這不是開發(fā)者想要的,因?yàn)橐诔绦蛑惺褂枚x好的結(jié)構(gòu)體,需要一個(gè)能將其轉(zhuǎn)換成C#腳本文件的工具。下面開始告訴大家制作該工具需要做哪些工作?制作工具需要的庫文件可以在網(wǎng)上下載到,就是已編譯好的庫工程,主要內(nèi)容如下圖所示:
接下來需要寫一個(gè)批處理文件執(zhí)行proto批量轉(zhuǎn)換操作,假設(shè)上述庫文件是在文件路徑:3Party\protobuf-net\net下面。制作的工具文件的擴(kuò)展名為.bat。批處理文件完整內(nèi)容如下所示:
@echo offset tool=..\3Party\protobuf-net\netrem ===============================================rem Supportset proto=common.proto%tool%\protogen.exe -i:%proto% -o:%proto%.cs -qset proto=login.proto%tool%\protogen.exe -i:%proto% -o:%proto%.cs -qset proto=begingame.proto%tool%\protogen.exe -i:%proto% -o:%proto%.cs -qpause
其中語句set tool=..\3Party\protobuf-net\net表示的是庫文件所在的目錄,set proto=common.proto表示的是要轉(zhuǎn)換的proto文件名字,%tool%\protogen.exe -i:%proto% -o:%proto%.cs -q表示的是調(diào)用上述目錄下的庫,將common.proto轉(zhuǎn)化成common.proto.cs文件,以此類推,因?yàn)槲覀兌x的commo.cs是公用的文件,下面的文件都會引用到該文件的內(nèi)容,同時(shí)把proto文件拷貝到與擴(kuò)展名bat相同的文件夾下面。其執(zhí)行的效果如下圖所示:
make-protobuf.bat就是執(zhí)行的批處理程序,執(zhí)行的結(jié)果就是生成對應(yīng)的cs文件,然后將cs文件拖放到Unity的目錄下面,生成的cs文件內(nèi)容如下所示,以login.proto.cs文件為例,文件內(nèi)容如下圖4-3所示:
限于篇幅所限,只截取一部分內(nèi)容,第一行表示的是引用common,再下面是命名空間以及類聲明,眼尖的讀者可能注意到了一個(gè)細(xì)節(jié)就是類前面的修飾符partial,給大家介紹一下,它屬于一個(gè)局部類型,局部類型允許我們將一個(gè)類、結(jié)構(gòu)或接口分成幾個(gè)部分,分別實(shí)現(xiàn)在幾個(gè)不同的.cs文件中。給大家普及一下partial的基礎(chǔ)知識,一是類型特別大,不宜放在一個(gè)文件中實(shí)現(xiàn);二是一個(gè)類型中的一部分代碼為自動化工具生成的代碼,不宜與我們自己編寫的代碼混合在一起;三是需要多人合作編寫一個(gè)類。這幾種情況適用于partial修飾。接下來介紹一下如何在Unity中使用。
Protobuf文件在Unity中運(yùn)用
在Unity中使用定義的Protobuf文件,首先需要把Protobuf-net的源文件放到Unity目錄下,源文件的下載地址是:
https://github.com/mgravell/protobuf-net/tree/master/protobuf-net,使用源文件的目的是可以實(shí)現(xiàn)Protobuf在android和ios平臺同時(shí)使用,代碼可以直接在Google提供的官網(wǎng)上直接下載,拖放到Unity中的效果如下圖4-4所示
這些前期工作完成后就可以直接調(diào)用Protobuf源代碼中的庫函數(shù)進(jìn)行序列化和反序列化。下面將生成的cs腳本文件拖放到Unity中,效果如下圖所示:
接下來介紹一下如何在Unity中使用,在使用定義好的結(jié)構(gòu)體時(shí),需要在文件中加入引用頭文件的using clientmsg,然后在函數(shù)中首先new一個(gè)對象如下所示:
clientmsg.c2s_login msg = new clientmsg.c2s_login();
然后再對其結(jié)構(gòu)體填充數(shù)值,如下語句所示:
msg.name = Global.userInputName; msg.pwd = Global.password; messageContentLen += msg.name.Length; messageContentLen += msg.pwd.Length;
最后可以將其結(jié)構(gòu)體發(fā)送到服務(wù)器:SendProtoBufMsg(msg, awnet);這樣整個(gè)Protobuf文件的使用就結(jié)束了,希望對大家有所幫助,具體詳情查看書籍《Unity3D實(shí)戰(zhàn)核心技術(shù)詳解》一書。
筆者簡介:姜雪偉個(gè)人網(wǎng)頁