標籤:架構 執行 import 結果 dia ack 無符號 服務 位元組對齊
原文:http://lihaoquan.me/2016/11/5/golang-byteorder.html
這個人的部落格寫的不錯,品質也比較高。 我應該也要有這種精神,這種態度。深入到電腦的世界中去。也是一種快樂。
使用Go開發一個簡單反向 Proxy服務 這篇文章也要研究一下。很好
---------------------------------------------------------------------------------------------go語言的位元組序
go
最近在看nsq的源碼時候,發現它處理message的時候,都會採用位元組序進行資料包的處理,於是我覺得有必要深入瞭解下TCP協議中 位元組序的知識
位元組序(Byte Order)
我們一般把位元組(byte)看作是資料的最小單位。當然,其實一個位元組中還包含8個bit (bit = binary digit)。 在一個32位的CPU中“字長”為32個bit,也就是4個byte。在這樣的CPU中,總是以4位元組對齊的方式來讀取或寫入記憶體, 那麼同樣這4個位元組的資料是以什麼順序儲存在記憶體中的呢?我們下面詳細探討一下。
位元組序包括:大端序和小端序,為什麼要這麼麻煩還要分門別類呢?舉個例子,255用二進位表達就是1111 1111,再加1就是1 0000 0000,多了一個1出來,顯然我們需要再用額外的一個位元組來存放這個1,但是這個1要存放在第一個位元組還是第二個位元組呢?這時候因為人們選擇的不同,就出現了大端序和小端序的差異。
而所謂大位元組序(big endian),便是指其“最高有效位(most significant byte)”落在低地址上的儲存方式。例如像地址a寫入0x0A0B0C0D之後,在記憶體中的資料便是:
而對於小位元組序(little endian)來說就正好相反了,它把“最低有效位(least significant byte)”放在低地址上。例如:
對於我們常用的CPU架構,如Intel,AMD的CPU使用的都是小位元組序,而例如Mac OS以前所使用的Power PC使用的便是大位元組序(不過現在Mac OS也使用Intel的CPU了)。
Go 處理位元組序
Go中處理大小端序的代碼位於 encoding/binary
,包中的全域變數BigEndian用於操作大端序資料,LittleEndian用於操作小端序資料,這兩個變數所對應的資料類型都實行了ByteOrder介面:
type ByteOrder interface { Uint16([]byte) uint16 Uint32([]byte) uint32 Uint64([]byte) uint64 PutUint16([]byte, uint16) PutUint32([]byte, uint32) PutUint64([]byte, uint64) String() string}
其中,前三個方法用於讀取資料,後三個方法用於寫入資料。
大家可能會注意到,上面的方法操作的都是無符號整型,如果我們要操作有符號整型的時候怎麼辦呢?很簡單,強制轉換就可以了,比如這樣:
func PutInt32(b []byte, v int32) { binary.BigEndian.PutUint32(b, uint32(v))}
為了深入瞭解它們,我們先寫一個程式觀察下go處理大端序和小端序的方式:
package mainimport ( "encoding/binary" "fmt" "unsafe")const INT_SIZE int = int(unsafe.Sizeof(0))//判斷我們系統中的位元組序類型func systemEdian() { var i int = 0x1 bs := (*[INT_SIZE]byte)(unsafe.Pointer(&i)) if bs[0] == 0 { fmt.Println("system edian is little endian") } else { fmt.Println("system edian is big endian") }}func testBigEndian() { // 0000 0000 0000 0000 0000 0001 1111 1111 var testInt int32 = 256 fmt.Printf("%d use big endian: \n", testInt) var testBytes []byte = make([]byte, 4) binary.BigEndian.PutUint32(testBytes, uint32(testInt)) fmt.Println("int32 to bytes:", testBytes) convInt := binary.BigEndian.Uint32(testBytes) fmt.Printf("bytes to int32: %d\n\n", convInt)}func testLittleEndian() { // 0000 0000 0000 0000 0000 0001 1111 1111 var testInt int32 = 256 fmt.Printf("%d use little endian: \n", testInt) var testBytes []byte = make([]byte, 4) binary.LittleEndian.PutUint32(testBytes, uint32(testInt)) fmt.Println("int32 to bytes:", testBytes) convInt := binary.LittleEndian.Uint32(testBytes) fmt.Printf("bytes to int32: %d\n\n", convInt)}func main() { systemEdian() fmt.Println("") testBigEndian() testLittleEndian()}
執行的結果:
system edian is big endian256 use big endian:int32 to bytes: [0 0 1 0]bytes to int32: 256256 use little endian:int32 to bytes: [0 1 0 0]bytes to int32: 256
總結
為了程式的相容,我們在開發跨伺服器的TCP服務時,每次發送和接受資料都要進行轉換,這樣做的目的是保證代碼在任何電腦上執行時都能達到預期的效果。
【轉】go語言的位元組序