不同的CPU有不同的字節序類型,這些字節序是指 整數 在內存中保存的順序,這個叫做 主機序。
最常見的有兩種:
1.Little endian:將低序字節存儲在起始地址
2.Big endian:將高序字節存儲在起始地址
LE little-endian(小端)
- 最符合人的思維的字節序;
- 地址低位存儲值的低位;
- 地址高位存儲值的高位;
- 怎么講是最符合人的思維的字節序,是因為從人的第一觀感來說;
- 低位值小,就應該放在內存地址小的地方,也即內存地址低位;
- 反之,高位值就應該放在內存地址大的地方,也即內存地址高位;
BE big-endian(大端)
- 最直觀的字節序;
- 地址低位存儲值的高位;
- 地址高位存儲值的低位;
- 為什么說直觀,不要考慮對應關系;
- 只需要把內存地址從左到右按照由低到高的順序寫出;
- 把值按照通常的高位到低位的順序寫出;
- 兩者對照,一個字節一個字節的填充進去;
例子:在內存中雙字 0x01020304(DWORD) 的存儲方式
內存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04
例子:如果我們將0x1234abcd寫入到以0x0000開始的內存中,則結果為
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x23 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12
x86系列CPU都是little-endian的字節序。
網絡字節順序是TCP/IP中規定好的一種數據表示格式,它與具體的CPU類型、操作系統等無關,從而可以保證數據在不同主機之間傳輸時能夠被正確解釋。網絡字節順序采用big endian排序方式。
為了進行轉換 bsd socket提供了轉換的函數 有下面四個
htons 把unsigned short類型從主機序轉換到網絡序
htonl 把unsigned long類型從主機序轉換到網絡序
ntohs 把unsigned short類型從網絡序轉換到主機序
ntohl 把unsigned long類型從網絡序轉換到主機序
在使用little endian的系統中,這些函數會把字節序進行轉換;
在使用big endian類型的系統中,這些函數會定義成空宏;
同樣,在網絡程序開發時 或是跨平台開發時,也應該注意保證只用一種字節序,不然兩方的解釋不一樣就會產生bug。
注:
1、網絡與主機字節轉換函數:htons()、ntohs()、htonl()、ntohl() (注意:s 就是short l是long h是host n是network)
2、不同的CPU上運行不同的操作系統,字節序也是不同的,參見下表。
處理器 操作系統 字節排序
Alpha 全部 Little endian
HP-PA NT Little endian
HP-PA UNIX Big endian
Intelx86 全部 Little endian <-----x86系統是小端字節序系統
Motorola680x() 全部 Big endian
MIPS NT Little endian
MIPS UNIX Big endian
PowerPC NT Little endian
PowerPC 非NT Big endian <-----PPC系統是大端字節序系統
RS/6000 UNIX Big endian
SPARC UNIX Big endian
IXP1200 ARM核心 全部 Little endian
摘自:http://baike.baidu.com/view/2194385.htm
IP地址的三種表示格式及在開發中的應用
使用TCP/IP協議進行網絡應用開發的朋友首先要面對的就是對IP地址信息的處理。IP地址其實有三種不同的表示格式。IP地址是IP網絡中數據傳輸的依據,它標識了IP網絡中的一個連接,一台主機可以有多個IP地址,IP分組中的IP地址在網絡傳輸中將保持不變。下面具體介紹IP地址的三種不同表示格式。
一、點分10進制表示格式
這是我們最常見的表示格式,比如某機的IP地址可能為“202.101.105.66”。事實上,對於Ipv4(IP版本)來說,IP地址是由一個32位的二進制數所構成,但這樣一串數字序列無疑是十分冗長並且難以閱讀和記憶的。為了方便人們的記憶和使用,就將這串數字序列分成4組,每組8位,並改為用10進制數進行表示,最后用小原點隔開,於是就演變成了“點分10進制表示格式”。
來看看剛才那個IP地址的具體轉化過程:
IP地址:11001010011001010110100101000010
分成4組后:11001010 01100101 01101001 01000010
十進制表示:202 101 105 66
點分表示:202.101.105.66
二、網絡字節順序格式(NBO,Network Byte Order)
下面我們來談談網絡字節順序格式,它和我們后面將要介紹的主機字節順序格式一樣,都只在進行網絡開發中才會遇到。因此,在下面的介紹中,我假設讀者對Socket編程知識有一定的基礎。
在網絡傳輸中,TCP/IP協議在保存IP地址這個32位二進制數時,協議規定采用在低位存儲地址中包含數據的高位字節的存儲順序,這種順序格式就被稱為網絡字節順序格式。在實際網絡傳輸時,數據按照每32位二進制數為一組進行傳輸,由於存儲順序的影響,實際的字節傳輸順序是由高位字節到低位字節的傳輸順序。
為了使通信的雙方都能夠理解數據分組所攜帶的源地址、目的地址以及分組的長度等二進制信息,無論是主機還是路由器,在發送每一個分組以前,都必須將二進制信息轉換為TCP/IP標准的網絡字節順序格式。網絡字節順序格式的地址不受主機、路由器類型的影響,它的表示是唯一的。
在Socket編程開發中,通過函數inet_addr和inet_ntoa可以實現點分字符串與網絡字節順序格式IP地址之間的轉換。
inet_addr函數原型如下:
1
|
unsigned
long
inet_addr(
const
char
FAR * cp)
|
函數中的參數cp指向網絡中標准的點分地址字符串,其中每個以點分開的數字不可以大於255,這些數字可以是十進制、八進制、十六進制或者混合使用。
如“10.23.2.3”、“012.003.002.024”、“0xa.0x3.0x14.0x2”、“10.003.2.0x12”。
下面舉一個函數小例子,該函數可以用來測試一目標主機的某端口是否開放,這是端口掃描技術的基礎。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
bool
ScanPort(
char
* m_IP,u_short m_port) {
struct
sockaddr_in m_SqlAddress;
//server's address.
SOCKET m_socket;
int
ret;
memset
((
char
*)&m_SqlAddress,0,
sizeof
(m_SqlAddress));
m_SqlAddress.sin_port = htons(m_port);
m_SqlAddress.sin_addr.s_addr = inet_addr(m_IP);
m_SqlAddress.sin_family = AF_INET;
m_socket = socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
// Create TCP Connect.
if
(m_socket < 0) {
return
FALSE;
}
ret = connect(m_socket,(
struct
sockaddr *)&m_SqlAddress,
sizeof
(m_SqlAddress));
return
(ret);
}
|
三、主機字節順序格式(HBO,Host Byte Order)
主機字節順序格式顧名思義,其IP地址的格式是和具體主機或者路由器相關的。對於不同的主機,在進行IP地址的存儲時有不同的格式,比如對於Motorola 68k系列主機,其HBO與NBO是相同的。而對於Intel x86系列,HBO與NBO則正好相反。
在Socket編程中,有四個函數來完成主機字節順序格式和網絡字節順序格式之間的轉換,它們是:htonl、htons、ntohl、和ntohs。htons和ntohs完成16位無符號數的相互轉換,htonl和ntohl完成32位無符號數的相互轉換。
在實際應用中我們常見到將端口號轉換的例子(如上例)。這是因為,如果用戶輸入一個數字,而且將指定使用這一數字作為端口號,應用程序則必須在使用它建立地址以前,把它從主機字節順序轉換成網絡字節順序(使用htons()函數),以遵守TCP/IP協議規定的存儲標准。相應地,如果應用程序希望顯示包含於某一地址中的端口號(例如從getpeername()函數中返回的),這一端口號就必須在被顯示前從網絡順序轉換到主機順序(使用ntohs()函數)。
那么,對於IP地址,主機字節順序格式的轉換又有哪些應用呢?
應用一:如果想知道從202.156.2.23到202.156.9.65這兩個IP之間到底有多少個主機地址怎么辦?這時就可以將兩個IP地址轉換為主機字節順序的格式然后相減來得到,具體的實現如下:
1
2
3
4
5
6
7
8
9
|
int
GetIPCount(
char
* ip1,
char
* ip2) {
long
pp;;
long
ss;;
pp = ntohl(inet_addr(ip1));;
ss = ntohl(inet_addr(ip2));;
return
(ss - pp + 1);;
}
|
應用二:如果對一個網段進行掃描,比如,當前正在掃描202.156.23.255,怎么讓程序知道下一個應掃的IP是202.156.24.0?這時可以將當前IP轉換成主機字節順序格式並加1后,在轉換回網絡格式即可,具體實現如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
char
*GetNextIp(
char
*m_curip) {
struct
sockaddr_in in;;
long
pp;;
char
*re;;
pp = ntohl(inet_addr(m_curip));;
pp = pp + 1;;
in.sin_addr.s_addr = htonl(pp);;
re = inet_ntoa(in.sin_addr);;
return
(re);;
}
|
總結
本文介紹了IP地址的三種不同表示格式,包括各種格式產生的原因、具體含義以及在Socket編程開發中的一些應用。在實際應用中,必須遵循應用時所應采用的格式標准,同時還應靈活運用格式間的相互轉換以及計算技巧。通過對本文的閱讀,希望可以給讀者在以后的學習和工作開發帶來啟發。
摘自:http://bbs.csdn.net/topics/60375114
網絡字節序與主機字節序的轉換
在對IP地址結構體SOCKADDR_IN賦值的時候,經常會用到下列的函數 htonl(), htons(), inet_addr(),與之相對應的函數是 ntohl(), ntohs(), inet_ntoa()。查看這些函數的解析,會發現這些函數其實是與主機字節序和網絡字節序之間轉換有關。就是什么網絡字節序,什么是主機字節序呢?下面我寫出他們之間的轉換:
用IP地址127.0.0.1為例:
第一步 127 . 0 . 0 . 1 把IP地址每一部分轉換為8位的二進制數。
第二步 01111111 00000000 00000000 00000001 = 2130706433 (主機字節序)
然后把上面的四部分二進制數從右往左按部分重新排列,那就變為:
第三步 00000001 00000000 00000000 01111111 = 16777343 (網絡字節序)
然后解析上面提到的函數作用就簡單多了,看以下代碼:
1
2
3
4
|
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(2130706433);
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000);
|
先是定義了一個IP地址結構體addrSrv,然后初始化它的IP時addrSrv.sin_addr.S_un.S_addr必須是賦值IP地址的網絡字節序,htonl函數的作用是把一個主機字節序轉換為網絡字節序,也就是上面轉換過程中第二步轉換為第三步的作用,127.0.0.1的主機字節序是2130706433,把主機字節序2130706433轉換為網絡字節序就是htonl(2130706433)=16777343,所以如果你知道網絡字節序是16777343的話,addrSrv.sin_addr.S_un.S_addr=htonl(2130706433);與addrSrv.sin_addr.S_un.S_addr=16777343;是完全一樣的。
1
2
3
|
addrSrv.sin_addr.S_un.S_addr = htonl(2130706433);
// 這句還可以寫為:
addrSrv.sin_addr.S_un.S_addr = inet_addr(
"127.0.0.1"
);
// 結果是完全一樣的。
|
可見inet_addr函數的轉換作用就是上面的第一步到第三步的轉換。
下面再看端口的主機字節序與網絡字節序的轉換。以6000端口為例。
第一步 00010111 01110000 = 6000 (主機字節序)
端口號其實就已經是主機字節序了,首先要把端口號寫為16位的二進制數,分前8位和后8位。
第二步 01110000 00010111 = 28695 (網絡字節序)
然后把主機字節序的前八位與后八位調換位置組成新的16位二進制數,這新的16位二進制數就是網絡字節序的二進制表示了。
因此,如果你知道6000端口的網絡字節序是28695的話。 addrSrv.sin_port=htons(6000); 可以直接寫為 addrSrv.sin_port=28695; 結果是一樣的,htons()的作用就是把端口號主機字節序轉換為網絡字節序。
與 htonl(), htons(), inet_addr() 與之相對應的函數是 ntohl(), ntohs(), inet_ntoa(),不難看出,ntohl(), ntohs(), inet_ntoa() 這三個函數其實就是執行與他們相對應函數的相反轉換,在這里就不詳細解析了。