天天看點

網絡位元組順序、大端法、小端法

      在國内的4種短信協定的協定頭部分,都定義了4個位元組長度的message length字段,字段的資料類型為無符号整形(也就是說,這個字段的範圍是0-2^16-1);而在java語言中,沒有無符号整形這種資料類型(如果用int類型來表示,由于java中int型是有符号數,則會發送溢出),我設想将message length存入long類型中,将數字的大小控制在0-2^16-1範圍之内,當超過此範圍歸零重新開始。

在網絡傳輸時,将long類型先轉化為byte數組,步驟如下:

long l;

byte[] b;

b[0]=(byte)(l>>>24);

b[1]]=(byte)(l>>>16);

b[2]]=(byte)(l>>>8);

b[3]]=(byte)(l);

此時,b[]中就按照網絡位元組順序(大端法,即l的高位資料存放在byte[]的低位位址,因為位址是      
從低向高發展的)存放着4個bytes的資料      
使用OutputStream的public void write(byte[] b,int off,int len)方法來向Socket寫位元組流      
,寫byte[0]至byte[3]的位元組。
      

java.io

Class OutputStream

write

public abstract void write(int b)
                    throws IOException      
Writes the specified byte to this output stream. The general contract for

write

is that one byte is written to the output stream. The byte to be written is the eight low-order bits of the argument

b

. The 24 high-order bits of

b

are ignored.

Subclasses of

OutputStream

must provide an implementation for this method.
Parameters:

b

- the

byte

.
Throws:

IOException

- if an I/O error occurs. In particular, an

IOException

may be thrown if the output stream has been closed.

write

public void write(byte[] b,
                  int off,
                  int len)
           throws IOException      
Writes

len

bytes from the specified byte array starting at offset

off

to this output stream. The general contract for

write(b, off, len)

is that some of the bytes in the array

b

are written to the output stream in order; element

b[off]

is the first byte written and

b[off+len-1]

is the last byte written by this operation.

The

write

method of

OutputStream

calls the write method of one argument on each of the bytes to be written out. Subclasses are encouraged to override this method and provide a more efficient implementation.

If

b

is

null

, a

NullPointerException

is thrown.

If

off

is negative, or

len

is negative, or

off+len

is greater than the length of the array

b

, then an IndexOutOfBoundsException is thrown.
Parameters:

b

- the data.

off

- the start offset in the data.

len

- the number of bytes to write.
Throws:

IOException

- if an I/O error occurs. In particular, an

IOException

is thrown if the output stream is closed.

------關于網絡、主機位元組順序的文章

http://www-128.ibm.com/developerworks/cn/java/l-datanet/index.html

主機和網絡位元組序的轉換

最近使用C#進行網絡開發,需要處理ISO8583封包,由于其中有些域是數值型的,于是在傳輸的時候涉及到了位元組序的轉換。位元組順序是指占記憶體多于一個位元組類型的資料在記憶體中的存放順序,通常有兩種位元組順序,根據他們所處的位置我們分别稱為主機節序和網絡位元組序。

通常我們認為網絡位元組序為标準順序,封包的時候,将主機位元組序轉換為網絡位元組序,拆包的時候要将網絡位元組序轉換為主機位元組序。原以為還要自己寫函數,其實網絡庫已經提供了。

主機到網絡:short/int/long IPAddress.HostToNetworkOrder(short/int/long)

網絡到主機:short/int/long IPAddress.NetworkToHostOrder(short/int/long)

主機位元組序指低位元組資料存放在記憶體低位址處,高位元組資料存放在記憶體高位址處,如:

int x=1;    //此時x為主機位元組序:[1][0][0][0] 低位到高位

int y=65536 //此時y為主機位元組序:[0][0][1][0] 低位到高位

我們通過主機到網絡位元組序的轉換函數分别對x和y進行轉換得到他們對應的網絡位元組序值,網絡節序是高位元組資料存放在低位址處,低位元組資料存放在高位址處,如:

int m=IPAddress.HostToNetworkOrder(x);

//此時m為主機位元組序:[0][0][0][1] 高位到低位

int n=IPAddress.HostToNetworkOrder(y);

//此時n為主機位元組序:[0][1][0][0] 高位到低位

經過轉換以後,我們就可以通過

byte[]btValue=BitConverter.GetBytes(m);

得到一個長度為4的byte數組,然後将這個數組設定到封包的相應位置發送出去即可。

同樣,收到封包後,可以将封包按域拆分,得到btValue,使用

int m=BitConverter.ToInt32(btValue,0);//從btValue的第0位開始轉換

得到該域的值,此時還不能直接使用,應該再用網絡到主機位元組序的轉換函數進行轉換:

int x=IPAddress.NetworkToHostOrder(m);

這時得到的x才是封包中的實際值。

---------------------------------------------------

也談位元組序問題

http://bigwhite.blogbus.com/logs/2005/09/

一次Sun SPARC到Intel X86的平台移植讓我們的程式遭遇了“位元組序問題”,既然遇到了也就不妨深入的學習一下。

一、位元組序定義

位元組序,顧名思義位元組的順序,再多說兩句就是大于一個位元組類型的資料在記憶體中的存放順序(一個位元組的資料當然就無需談順序的問題了)。

其實大部分人在實際的開發中都很少會直接和位元組序打交道。唯有在跨平台以及網絡程式中位元組序才是一個應該被考慮的問題。

在所有的介紹位元組序的文章中都會提到位元組序分為兩類:Big-Endian和Little-Endian。引用标準的Big-Endian和Little-Endian的定義如下:

a) Little-Endian就是低位位元組排放在記憶體的低位址端,高位位元組排放在記憶體的高位址端。

b) Big-Endian就是高位位元組排放在記憶體的低位址端,低位位元組排放在記憶體的高位址端。

c) 網絡位元組序:TCP/IP各層協定将位元組序定義為Big-Endian,是以TCP/IP協定中使用的位元組序通常稱之為網絡位元組序。

其實我在第一次看到這個定義時就很糊塗,看了幾個例子後也很是朦胧。什麼高/低位址端?又什麼高低位?翻閱了一些資料後略有心得。

二、高/低位址與高低位元組

首先我們要知道我們C程式映像中記憶體的空間布局情況:在《C專家程式設計》中或者《Unix環境進階程式設計》中有關于記憶體空間布局情況的說明,大緻如下圖:

----------------------- 最高記憶體位址 0xffffffff

 | 棧底

 .

 .              棧

 .

  棧頂

-----------------------

 |

 |

/|/

NULL (空洞) 

/|/

 |

 |

-----------------------

                堆

-----------------------

未初始化的資料

----------------(統稱資料段)

初始化的資料

-----------------------

正文段(代碼段)

----------------------- 最低記憶體位址 0x00000000

以上圖為例如果我們在棧上配置設定一個unsigned char buf[4],那麼這個數組變量在棧上是如何布局的呢[注1]?看下圖:

棧底 (高位址)

----------

buf[3]

buf[2]

buf[1]

buf[0]

----------

棧頂 (低位址)

現在我們弄清了高低位址,接着我來弄清高/低位元組,如果我們有一個32位無符号整型0x12345678(呵呵,恰好是把上面的那4個位元組buf看成一個整型),那麼高位是什麼,低位又是什麼呢?其實很簡單。在十進制中我們都說靠左邊的是高位,靠右邊的是低位,在其他進制也是如此。就拿0x12345678來說,從高位到低位的位元組依次是0x12、0x34、0x56和0x78。

高低位址和高低位元組都弄清了。我們再來回顧一下Big-Endian和Little-Endian的定義,并用圖示說明兩種位元組序:

以unsigned int value = 0x12345678為例,分别看看在兩種位元組序下其存儲情況,我們可以用unsigned char buf[4]來表示value:

Big-Endian: 低位址存放高位,如下圖:

棧底 (高位址)

---------------

buf[3] (0x78) -- 低位

buf[2] (0x56)

buf[1] (0x34)

buf[0] (0x12) -- 高位

---------------

棧頂 (低位址)

Little-Endian: 低位址存放低位,如下圖:

棧底 (高位址)

---------------

buf[3] (0x12) -- 高位

buf[2] (0x34)

buf[1] (0x56)

buf[0] (0x78) -- 低位

---------------

棧頂 (低位址)

在現有的平台上Intel的X86采用的是Little-Endian,而像Sun的SPARC采用的就是Big-Endian。

三、例子

測試平台: Sun SPARC Solaris 9和Intel X86 Solaris 9

我們的例子是這樣的:在使用不同位元組序的平台上使用相同的程式讀取同一個二進制檔案的内容。

生成二進制檔案的程式如下:

int main() {

        FILE    *fp = NULL;

        int     value = 0x12345678;

        int     rv = 0;

        fp = fopen("temp.dat", "wb");

        if (fp == NULL) {

                printf("fopen error/n");

                return -1;

        }

        rv = fwrite(&value, sizeof(value), 1, fp);

        if (rv != 1) {

                printf("fwrite error/n");

                return -1;

        }

        fclose(fp);

        return 0;

}

讀取二進制檔案的程式如下:

int main() {

        int             value   = 0;

        FILE         *fp     = NULL;

        int             rv      = 0;

        unsigned        char buf[4];

        fp = fopen("temp.dat", "rb");

        if (fp == NULL) {

                printf("fopen error/n");

                return -1;

        }

        rv = fread(buf, sizeof(unsigned char), 4, fp);

        if (rv != 4) {

                printf("fread error/n");

                return -1;

        }

        memcpy(&value, buf, 4); // or value = *((int*)buf);

        printf("the value is %x/n", value);

        fclose(fp);

        return 0;

}

測試過程:

(1) 在SPARC平台下生成temp.dat檔案

在SPARC平台下讀取temp.dat檔案的結果:

the value is 12345678

在X86平台下讀取temp.dat檔案的結果:

the value is 78563412

(1) 在X86平台下生成temp.dat檔案

在SPARC平台下讀取temp.dat檔案的結果:

the value is 78563412

在X86平台下讀取temp.dat檔案的結果:

the value is 12345678

[注1]

buf[4]在棧的布局我也是通過例子程式得到的:

int main() {

        unsigned char buf[4];

        printf("the buf[0] addr is %x/n", buf);

        printf("the buf[1] addr is %x/n", &buf[1]);

        return 0;

}

output:

SPARC平台:

the buf[0] addr is ffbff788

the buf[1] addr is ffbff789

X86平台:

the buf[0] addr is 8047ae4

the buf[1] addr is 8047ae5

兩個平台都是buf[x]所在位址高于buf[y] (x > y)。

-----------------------------------------------------------------

繼續閱讀