張大禹
(中國人民解放軍91550 部隊 遼寧省大連市 116000)
在數據傳輸的過程中,數據本身的結構變化對于用戶來說是透明的,但是數據的接收端和發(fā)送端,在數據結構和存儲策略方面不可能永遠一致,因此就出現了稱之為“字節(jié)一致”的原則,用于實現數據接收端對于所接收到數據處理,尤其是當涉及傳輸位域結構相關問題的時候,這一原則的價值尤其突出,能夠幫助將將傳輸過程中的數據碎片重新組合在一起。
數據無論是在日常生活中還是在計算機傳輸和存儲領域中,都有一定的規(guī)則可以遵循,這些規(guī)則的存在讓讀者或者信息的接收端能夠合理對內容進行理解。
數據存儲涉及到字節(jié)序(byte-order)與比特序(bit-order)兩個概念,多種情況下字節(jié)會被作為數據存儲的基本單元對待,但是字節(jié)序并不是存儲順序,這二者之間還存在比特的編址問題。對于不同的存儲系統(tǒng)來說,其存儲順序也有所不同,這樣的系統(tǒng)之間想要實現有效的數據交換,如果涉及到按位定義的位域結構(bit-field)時,就必須將比特序納入考慮的范圍。這里需要進一步明確比特序的常規(guī)形態(tài),即如果將數的高位存儲置于高地址之上,則 稱之為小端(little-endian),反之則稱之為大端(big-endian)[1]。
在實際對于數進行書寫的時候,從左到右位次從高到低,無論系統(tǒng)是小端還是大端,這個規(guī)則都不應當有所改變,這是將二進制數的每一位與物理存儲的比特位進行對應的時候必須堅持的原則。但是需要依據大小端的定義對每個字節(jié)和比特的地址偏移狀況進行標記。除此以外,圖形表示法也是常見的方法。此種表達方式對每個數據成員進行記錄的時候,遵循在水平方向從左到右表示地址從低到高,垂直方向的高度則表示從最高位到對低位的變化。此種表達方式可以對交換數據過程中的數據成員分布以及高低位變化一目了然,這對于弄清楚數據傳輸過程中的各種細節(jié)十分重要,也有助于我們正確復原數據[2]。這種用來表示字符串的圖多呈現出鋸齒形狀,以字符串“SUN”為例,其鋸齒圖參見圖1。
圖1: 從小端傳輸到大端的字符串鋸齒圖
首先來對字節(jié)一致原則進行闡述。
在數據傳輸過程中,數據的發(fā)送端和接收端,具體來說就是兩側的內存,在這兩側內存中間,還有諸多的通信環(huán)節(jié),諸如通信鏈路和各類接口等等。但是不管經過多少環(huán)節(jié),都必須遵循“字節(jié)一致”的原則,這個原則讓大端和小端不同系統(tǒng)之間,在經歷了數據的傳輸之后,保持著字節(jié)的順序不變,而只是對每個字節(jié)內的比特序進行反轉,字節(jié)的值不會有所改變。具體來說,就是在數據傳輸的過程中,不同存儲順序的系統(tǒng)之間交換數據時,堅持字節(jié)順序不變,以及字節(jié)的值不變,但是對應的,字節(jié)內的比特序會發(fā)生反轉改變[3]。
在此基礎之上進一步對數據傳輸過程進行審視。在TCP/IP 協(xié)議框架之下,普遍選用大端序展開工作,這種統(tǒng)一的標準使得所有在TCP/IP 協(xié)議框架之下傳輸數據的存儲端都可以實現對于協(xié)議以及傳輸結果的正確解讀。因為大端序廣泛應用在TCP/IP 協(xié)議基礎的網絡環(huán)境中,因此也會被人稱為網絡序,但是這里存在一個誤解,即TCP/IP 協(xié)議采用的網絡序只會作用于協(xié)議頭部中參數的存儲順序,而不會作用于整個傳輸的數據包。例如在struct sockaddr_in 結構之中,頭部的IP 地址和端口號都會采用大端序進行編輯,但是對于除去頭部以外的部分,也就是用戶需要傳輸的數據部分而言,TCP/IP協(xié)議并不會干涉用戶定義的數據結構,也不會過問遵從了大端還是小端規(guī)則,只會依據字節(jié)一致的原則將數據作為字節(jié)流進行傳輸,保證每個字節(jié)的順序和內容保持一定。對于負責數據傳輸的函數進行考察可以更好地對此種情況進行了解。在函數sendto()以及recvfrom()中,其收發(fā)緩沖區(qū)的類型被定義為const char*或char*,也就是說,數據的接收端會對字節(jié)流進接收以及解讀,依據發(fā)送端的數據結構來將傳輸接收到的數據結構進行映射,從而實現對于數據成員的還原。
在傳輸的過程中,如果數據的收發(fā)雙方采用了同種存儲順序邏輯,則數據接收端可以直接對數據結構進行定義和還原,但是對于存儲策略也有所不同的收發(fā)雙方而言,數據接收端就必須利用字節(jié)一致的原則對所接收到的數據進行考察,將這些數據重新映射到新的數據結構之中。
在這個數據傳輸的過程之中,還有一個重要的問題需要予以關注,就是字節(jié)對齊。對于數據傳輸參與的收發(fā)兩端而言,不同的字節(jié)對齊方式必然會造成傳輸數據群體中的每一個元素,其排列方式和結構長度的變化。即便是數據收發(fā)兩側采用了同樣的存儲序策略,如果無法落實字節(jié)對齊問題,同樣也會造成數據接收端數據內容接收的失敗。對于此種問題,C/C++編譯器提供了預處理指令#pragma pack(n),這一命令可以讓數據的接收端與數據的發(fā)送端保持同樣的字節(jié)對齊方式。然而雖然有這樣的指令用于支持,但如果數據傳輸雙方的存儲順序存在差異,也仍然需要面對復雜的處理過程。如果有數據元素超越了字節(jié)邊界,則需要在數據的接收端對數據結構進行重新定義,這個時候字節(jié)對齊問題的意義就會顯得尤為突出。編譯器為了保持字節(jié)對齊,還有可能需要對結構體進行填充。這個時候預處理指令#pragma pack(1)可以用于實現結構成員之間沒有填充或者基于字節(jié)定義位域結構等情況的填充,實現方法即面對結構進行字節(jié)的逐一填充[4]。
上一節(jié)中已經提到,如果在數據傳輸過程之中,數據收發(fā)的兩端采用了不同的存儲順序,則二者之間的數據結構映射就會面對更為復雜的情況,處理方案也會更加復雜。在這一部分中對此類情況展開更為深入的闡述。
首先,對于以字節(jié)作為基本單位的數據來說,都可以依據字節(jié)一致的規(guī)則進行傳輸,字節(jié)的順序以及具體的值都不會發(fā)生改變,數據的接收端只需要按照發(fā)送端的數據結構定義來對數據進行處理即可。這樣的情況下,雖然字節(jié)的高低位會發(fā)生反轉,但是對接收端來說并沒有什么影響,也不需要額外進行控制。此類狀態(tài)之下的傳輸可以參見圖1。
其次,對于多字節(jié)數據而言,當數據的收發(fā)兩端采用了不同額字節(jié)序的時候,同樣,在字節(jié)一致原則的控制之下,接收端獲取到的字節(jié)順序和值并不會因為傳輸過程而有所變化,但字節(jié)權重值會出現反轉。小端系統(tǒng)中的低地址字節(jié)在大端系統(tǒng)中就會成為高位地址字節(jié),這種轉變需要在數據傳輸中給予關注。此種情況可以參見圖2。
圖2: 多字節(jié)數據傳輸鋸齒圖
圖2(a)為信息發(fā)送端,假設為小端系統(tǒng),傳輸的數據如圖,為一個四個字節(jié)的整數,在進行傳輸之后,在信息接收端形成如圖2(b)的形態(tài),其中數據接收端為大端系統(tǒng)。因此就涉及到需要在數據接收端對接收到的數據結果進行調整,具體來說,就是要將圖2(b)調整成為圖2(c)狀態(tài)。具體而言調整規(guī)則比較簡單,即將多字節(jié)數據的最高地址上的字節(jié)內容與最低地址上的字節(jié)內容進行交換,次高地址上的字節(jié)內容與次低地址上的字節(jié)內容進行交換,以此類推,完成整個數據的翻轉。
最后,在數據傳輸領域之中,有一個特殊的領域叫做“位域”。從概念上看,位域本質上也是一種數據結構,其可以將數據以“位”的形式進行緊湊存儲,并且允許工作人員對這一類結構中的位進行操作??傮w來說,位域的核心優(yōu)勢在于對存儲空間的節(jié)約,這種節(jié)約尤其是在程序需要大容量的數據單元的時候,能夠突出其優(yōu)勢。
對于位域的傳輸而言,需要進一步分為兩種情況進行考察。
(1)即單字節(jié)內的位域結構。在對此種位域進行傳輸的時候需要注意,無論何種系統(tǒng),在進行結構定義的時候,編譯器都會按照結構成員來進行次序的確定,從內存的低地址開始進行存儲資源的分配。假設在大端系統(tǒng)中來對某位域結構進行定義如下:
改數據傳輸到小端系統(tǒng)之后,高低位出現了翻轉,因此對應的結構會轉變成如下:
對于上述的這種情況,用鋸齒圖進行表示,可以參見圖3。
圖3: 數據傳輸發(fā)送端以及接收端分別問大端系統(tǒng)和小端系統(tǒng)情況下的鏡像關系
從圖3 中可以看到,當傳輸內容到達接收端之后,原來的高低位發(fā)生了變化,低位換到了高位,原來的高位換到了低位,但是其數據值并未發(fā)生改變。
(2)則是跨字節(jié)的位域結構。此種情況發(fā)生在位域結構超出了字節(jié)邊界,對應的情況也會隨之變得復雜。這主要是考慮到高位和低位反轉之后,跨字節(jié)的位域會出現地址不連續(xù)的問題,即整個存儲內容會出現在兩個或者更多地址空間中。這種情況通常稱之為空間斷裂。例如,一個數據發(fā)送端如果采用大端系統(tǒng)規(guī)則工作,其上的位域結構為:
在存儲的時候,上面的結構在內存中的映像參見圖4(a),其中灰色部分是跨字節(jié)邊界的部分。這樣的數據傳輸到小端系統(tǒng)之后,對應的在內存中的映射見圖4(b)。
圖4: 跨字節(jié)位域數據從大端系統(tǒng)傳輸到小端系統(tǒng)中的對比
無論是大端還是小端系統(tǒng),都是從存儲環(huán)境的低位開始展開存儲資源的分配,因此想要實現對于發(fā)送端結構的正確描述,就需要數據的接收端重新展開對于結構成員的順序定義。對于上述大端系統(tǒng)發(fā)送的數據,小端系統(tǒng)應當將數據定義成如下結構:
在此種情況中,變量s3.v2 跨越了字節(jié)邊界,對應在到達信息接收端的時候,存儲空間斷為兩個部分。對于這種情況,可以考慮用移位拼接的算法進行處理:
數據傳輸過程看似透明,但是其中的細節(jié)關系十分微妙,尤其是高低位的翻轉以及位域的地址斷裂問題。在面對此類情況的時候,需要牢牢把握字節(jié)一致原則,結合內存映射和 鋸齒圖進行理解,實現數據的有效還原。