http://yuanshuilee.blog.163.com/blog/static/21769727520142273283412/ 2014-03-27 網(wǎng)絡(luò)通信通常分為基于數(shù)據(jù)結(jié)構(gòu)的和基于流的。HTTP協(xié)議就是后者的一個(gè)例子。 有時(shí)為了提高程序的處理速度和數(shù)據(jù)處理的方便,會(huì)使用基于數(shù)據(jù)結(jié)構(gòu)的通信(不需要對(duì)流進(jìn)行解析)。但是,當(dāng)需要在多平臺(tái)間進(jìn)行通信時(shí),基于數(shù)據(jù)結(jié)構(gòu)的通信,往往要十分注意以下幾個(gè)方面:[1] 字節(jié)序[2] 變量長(zhǎng)度[3] 內(nèi)存對(duì)齊 在常見(jiàn)的系統(tǒng)架構(gòu)中(Linux X86,Windows),非單字節(jié)長(zhǎng)度的變量類型,都是低字節(jié)在前,而在某些特定系統(tǒng)中,如Soalris Sparc平臺(tái),高字節(jié)在前。如果在發(fā)送數(shù)據(jù)前不進(jìn)行處理,那么由Linux X86發(fā)向Soalris Sparc平臺(tái)的數(shù)據(jù)值,勢(shì)必會(huì)有極大的偏差,進(jìn)而程序運(yùn)行過(guò)程中無(wú)法出現(xiàn)預(yù)計(jì)的正常結(jié)果,更嚴(yán)重時(shí),會(huì)導(dǎo)致段錯(cuò)誤。 對(duì)于此種情況,我們往往使用同一的字節(jié)序。在系統(tǒng)中,有ntohXXX(), htonXXX()等函數(shù),負(fù)責(zé)將數(shù)據(jù)在網(wǎng)絡(luò)字節(jié)序和本地字節(jié)序之間轉(zhuǎn)換。雖然每種系統(tǒng)的本地字節(jié)序不同,但是對(duì)于所有系統(tǒng)來(lái)說(shuō),網(wǎng)絡(luò)字節(jié)序是固定的-----高字節(jié)在前。所以,可以以網(wǎng)絡(luò)字節(jié)序?yàn)橥ㄐ诺臉?biāo)準(zhǔn),發(fā)送前,數(shù)據(jù)都轉(zhuǎn)換為網(wǎng)絡(luò)字節(jié)序。 轉(zhuǎn)換的過(guò)程,也建議使用ntohXXX(), htonXXX()等標(biāo)準(zhǔn)函數(shù),這樣代碼可以輕松地在各平臺(tái)間進(jìn)行移植(像通信這種很少依賴系統(tǒng)API的代碼,做成通用版本是不錯(cuò)的選擇)。
變量的長(zhǎng)度,在不同的系統(tǒng)之間會(huì)有差別,如同是Linux2.6.18的平臺(tái),在64位系統(tǒng)中,指針的長(zhǎng)度為8個(gè)字節(jié),而在32位系統(tǒng)中,指針又是4個(gè)字節(jié)的長(zhǎng)度---此處只是舉個(gè)例子,很少有人會(huì)將指針作為數(shù)據(jù)發(fā)送出去。下面是我整理的在64位Linux系統(tǒng)和32位Linux系統(tǒng)中,幾種常見(jiàn)C語(yǔ)言變量的長(zhǎng)度: short int long long long ptr time_t32位 2 4 4 8 4 464位 2 4 8 8 8 8 在定義通信用的結(jié)構(gòu)體時(shí),應(yīng)該考慮使用定長(zhǎng)的數(shù)據(jù)類型,如uint32_t,4字節(jié)的固定長(zhǎng)度,并且這屬于標(biāo)準(zhǔn)C庫(kù)(C99),在各系統(tǒng)中都可使用。
內(nèi)存對(duì)齊的問(wèn)題,也與系統(tǒng)是64位還是32位有關(guān)。如果你手頭有32位和64位系統(tǒng),不妨寫(xiě)個(gè)簡(jiǎn)單的程序測(cè)試一下,你就會(huì)看到同一個(gè)結(jié)構(gòu)體,即便使用了定長(zhǎng)的數(shù)據(jù)類型,在不同系統(tǒng)中的大小是不同的。對(duì)齊往往是以4字節(jié)或8字節(jié)為準(zhǔn)的,只要你寫(xiě)的測(cè)試程序,變量所占空間沒(méi)有對(duì)齊到4或8的倍數(shù)即可,舉個(gè)簡(jiǎn)單的測(cè)試用的結(jié)構(gòu)體的例子吧:struct student{ char name[7]; uint32_t id; char subject[5];}; 在每個(gè)系統(tǒng)上看下這個(gè)結(jié)構(gòu)體的長(zhǎng)度吧。 內(nèi)存對(duì)齊,往往是由編譯器來(lái)做的,如果你使用的是gcc,可以在定義變量時(shí),添加__attribute__,來(lái)決定是否使用內(nèi)存對(duì)齊,或是內(nèi)存對(duì)齊到幾個(gè)字節(jié),以上面的結(jié)構(gòu)體為例: 1)到4字節(jié),同樣可指定對(duì)齊到8字節(jié)。struct student{ char name[7]; uint32_t id; char subject[5];} __attribute__ ((aligned(4)));
2)不對(duì)齊,結(jié)構(gòu)體的長(zhǎng)度,就是各個(gè)變量長(zhǎng)度的和struct student{ char name[7]; uint32_t id; char subject[5];} __attribute__ ((packed));
雖然,網(wǎng)絡(luò)編程里面的數(shù)據(jù)傳送推薦用序列化,但我不用,還是選擇結(jié)構(gòu)體(返璞歸真),有以下幾點(diǎn)理由:1.跨平臺(tái)問(wèn)題:序列化確實(shí)可以很好的跨語(yǔ)言平臺(tái),可大多數(shù)網(wǎng)絡(luò)游戲不需要跨語(yǔ)言平臺(tái)
2.別以為有了序列化就不需要結(jié)構(gòu)體表面上序列化代碼量小,按順序讀和寫(xiě)char int short LPCSTR ... 就好,邏輯對(duì)象寫(xiě)不寫(xiě)都無(wú)所謂,那就是大錯(cuò)而特錯(cuò)了待序列化的對(duì)象發(fā)送前的結(jié)構(gòu)還是不可省略的,序列化的過(guò)程就是 object->(按一定順序拆分)write->bytes->(按拆分順序組裝)read->object的過(guò)程其實(shí)object還是不能省略,很多人寫(xiě)網(wǎng)絡(luò)程序不注重邏輯對(duì)象結(jié)構(gòu),收到的一堆bytes按一定順序讀和寫(xiě)就完事了,這樣雖然靈活,但缺乏結(jié)構(gòu),容易造成混亂,調(diào)試起來(lái)是災(zāi)難所以結(jié)構(gòu)體(或類)還是省略不了的,所以:別以為有了序列化,就不需要結(jié)構(gòu)體了。
3.結(jié)構(gòu)體存在內(nèi)存對(duì)齊和CPU不兼容的問(wèn)題,可以避免的確結(jié)構(gòu)體是有內(nèi)存對(duì)齊的問(wèn)題,存在兼容性問(wèn)題,我可以選擇pack(1)把內(nèi)存對(duì)齊給關(guān)閉掉,避免兼容性問(wèn)題,既然選擇了iocp就不打算跨平臺(tái)了,可以避免結(jié)構(gòu)體平臺(tái)兼容的問(wèn)題
4.結(jié)構(gòu)體調(diào)試起來(lái)方便很多,減少內(nèi)存拷貝,效率高不用序列化可write和read的過(guò)程就不需要過(guò)多考慮(少寫(xiě)太多代碼了),read write 就好像現(xiàn)代社會(huì)每個(gè)人每天都要穿衣服和脫衣服一樣,原始社會(huì)需要嗎?其實(shí)人類進(jìn)化到原始裸奔狀態(tài)才是最爽快的:)但還是要說(shuō)句公道話:有人說(shuō)序列化編碼解碼read write 需要耗費(fèi)資源, 誠(chéng)然這個(gè)過(guò)程基本等于賦值和內(nèi)存拷貝,那點(diǎn)效率損失主要還在內(nèi)存拷貝上,這點(diǎn)效率損失很小,不能作為序列化的缺點(diǎn),當(dāng)然如果涉及到數(shù)據(jù)加密那將是另外一個(gè)話題
5.結(jié)構(gòu)體貌似呆板,發(fā)送數(shù)據(jù)限制多,發(fā)送變長(zhǎng)數(shù)據(jù)就不方便,數(shù)據(jù)組織起來(lái)也不靈活我想這是很多人拋棄結(jié)構(gòu)體,選擇用序列化方式發(fā)送和接受數(shù)據(jù)的一個(gè)很重要的原因但:其實(shí)對(duì)于變長(zhǎng)結(jié)構(gòu)(子結(jié)構(gòu)也是變長(zhǎng))的問(wèn)題,用結(jié)構(gòu)體來(lái)實(shí)現(xiàn)的確很麻煩,但并不代表不能實(shí)現(xiàn)我已經(jīng)實(shí)現(xiàn)了,而且讀和寫(xiě)變長(zhǎng)子結(jié)構(gòu)體嵌套任意多層都不成問(wèn)題,可以存儲(chǔ)復(fù)雜變長(zhǎng)的數(shù)據(jù)結(jié)構(gòu),數(shù)據(jù)就如同能自動(dòng)序列化一樣方便,這個(gè)應(yīng)該是技術(shù)難點(diǎn),但細(xì)心去做是可以實(shí)現(xiàn)的
6.關(guān)于結(jié)構(gòu)體指針游戲里面要發(fā)送的數(shù)據(jù)內(nèi)存事先分配好的,不存在指針,深度復(fù)制更不用考慮,所以內(nèi)存拷貝不會(huì)出錯(cuò)如果用到指針即使用序列化來(lái)實(shí)現(xiàn)也會(huì)面臨同樣的問(wèn)題也占不了多少便宜,由于C++這們語(yǔ)言的特點(diǎn),不象java那樣有個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn),對(duì)于序列化本身沒(méi)有一個(gè)統(tǒng)一的標(biāo)準(zhǔn),所以可想而知,有人說(shuō):boost有它的序列化的實(shí)現(xiàn)其實(shí)那個(gè)實(shí)現(xiàn)不見(jiàn)得就合適你自己,如果真要做序列化,編碼和解碼的仿照那個(gè)過(guò)程自己寫(xiě)才最為牢靠,哪些指針對(duì)應(yīng)的內(nèi)存需要序列化那些不需要序列化,是個(gè)邏輯結(jié)構(gòu),需要自己說(shuō)了算才好(好像扯遠(yuǎn)了點(diǎn))說(shuō)回游戲數(shù)據(jù),既然不用需要他用到指針,結(jié)構(gòu)體用來(lái)發(fā)送數(shù)據(jù)也沒(méi)問(wèn)題的
7 平臺(tái)擴(kuò)充問(wèn)題退一萬(wàn)步的說(shuō):換了語(yǔ)言就基本上換了客戶端,客戶端的數(shù)據(jù)組織形式都要重寫(xiě)實(shí)在不行還可以考慮用xml json 編碼等等一些跨平臺(tái)的解決方案,現(xiàn)在所寫(xiě)的結(jié)構(gòu)體是可以用來(lái)做數(shù)據(jù)接收的,只是發(fā)送的不再是結(jié)構(gòu)體而已
8.綜上所述
如果需要跨語(yǔ)言平臺(tái),不用序列化(二進(jìn)制流或xml, json文本等等)根本無(wú)法實(shí)現(xiàn)
序列化的優(yōu)點(diǎn)還是非常多的.如果主要是跨平臺(tái)和語(yǔ)言自定義讀寫(xiě)規(guī)則,根據(jù)需要讀寫(xiě)對(duì)象的某一部分?jǐn)?shù)據(jù),
空間浪費(fèi)少,不存在內(nèi)存對(duì)齊問(wèn)題等諸多優(yōu)點(diǎn),缺點(diǎn)就是拐彎抹角,代碼量大,調(diào)試不方便
【參考資料】http://www.cnblogs.com/chenyadong/archive/2011/09/02/2163654.htmlhttp://blog.163.com/liuweiyoung@126/blog/static/1731310452011823113123261/
|