1. 程式人生 > >IP 地址的三種表示形式

IP 地址的三種表示形式

 使用TCP/IP協議進行網路應用開發的朋友首先要面對的就是對IP地址資訊的處理。IP地址其實有三種不同的表示格式:

       1)Ascii(網路點分字串)-

       2) 網路地址(32位無符號整形,網路位元組序,大頭)

       3)主機地址 (主機位元組序)  

       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函式原型如下:   
  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”。   

 我們在前面的socket程式設計提到client端的程式碼,連線本地埠:

/* File Name: client.c */  

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096

int main(int argc, char** argv)
{
    int    sockfd, n,rec_len;
    char    recvline[4096], sendline[4096];
    char    buf[MAXLINE];
    struct sockaddr_in    servaddr;

    if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
		printf("create socket error: %s(errno: %d)\n", strerror(errno),errno);
		exit(0);
    }

    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(8000);
	//可以使用:inet_pton(AF_INET,  "127.0.0.1", servaddr.sin_addr);
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");//將字串形式的IP地址轉換為按網路位元組順序的整形值
    connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) ;
    printf("send msg to server: \n");
    fgets(sendline, 4096, stdin);
    send(sockfd, sendline, strlen(sendline));
    irec_len = recv(sockfd, buf, MAXLINE,0);
     
    buf[rec_len]  = '\0';
    printf("Received : %s ",buf);
    close(sockfd);
}

三、主機位元組順序格式(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地址轉換為主機位元組順序的格式然後相減來得到,具體的實現如下:   
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後,在轉換回網路格式
即可,具體實現如下:   
  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程式設計開發中的一些應用。在實際應用中,必須遵循應用時所應採用的格式標準,同時還應靈活運用格式間的相互轉換以及計算技巧。

位元組序相關知識

 1)位元組序

          位元組序又稱端序,尾序,英文:Endianness。在電腦科學領域中,位元組序是指存放多位元組資料的位元組(byte)的順序,典型的情況是整數在記憶體中的存放方式和網路傳輸傳輸順序。Endianness有時候也可以用指位序(bit)。

        一般而言,位元組序指示了一個UCS-2字元的哪個位元組儲存在低地址。如果LSByte 在MSByte的前面,即LSB為低地址,則該位元組序是小端序;反之則是大端序。在網路程式設計中,位元組序是一個必須被考慮的因素,因為不同的處理器體系可能採用不同的位元組序。在多平臺的程式碼程式設計中,位元組序可能會導致難以察覺的bug。

名詞:

最低有效位the least significant bitlsb):是指一個二進位制數字中的第0位(即最低位),具有權值為2^0,可以用它來檢測數的奇偶性。與之相反的稱之為最高有效位。在大端序中,lsb指最右邊的位。

最高有效位the Most Significant Bitmsb):是指一個n位二進位制數字中的n-1位,具有最高的權值2^{n-1}。與之相反的稱之為最低有效位。在大端序中,msb即指最左端的位。

對於有符號二進位制數,負數採用反碼補碼形式,此時msb用來表示符號,msb為1表示負數,0表示正數

單位元組(abyte):大部分處理器以相同的順序處理位元(bit),因此單位元組的存放方法和傳輸方式一般相同。

多位元組:如整數(32位機中一般佔4位元組),多位元組物件被儲存為連續的位元組序列,資料的記憶體地址則是該記憶體地址的最小地址。

如long型資料的地址是ox001, ox002, ox003,ox004。則該資料的記憶體地址是ox001。

在不同的處理器的存放多位元組資料的方式主要有兩種:

大端序(英文名稱為big endian)指從最高位起存,位數最大的數字在最前,即高位元組存於記憶體低地址,低位元組存於記憶體高地址, 從最高有效位元組到最低有效位元組的順序儲存物件。

小端序(英文名稱為little endian)指從對低位起存,位數最小的數字在最前。 即低位元組存於記憶體低地址,高位元組存於記憶體高地址,從最低有效位元組到最高有效位元組的順序儲存物件。

簡單打個比方說,十進位制數12345。1的位數最高,是萬位;5的位數最低,是個位。 

大端序的話,就是從萬位開始存,表示為12345;
小端序的話,就是從各位開始存,表示為54321.

再如一個long型資料0x12345678的儲存表示:

大端序儲存表示:

記憶體地址

資料

記憶體低地址-->a

0x001

12

a+1

0x002

34

a+2

0x003

56

記憶體高地址-->a+3

0x004

78

小端序的儲存表示:

記憶體地址

資料

記憶體低地址-->a

0x001

78

a+1

0x002

56

a+2

0x003

34

記憶體高地址-->a+3

0x004

12

2)網路序

網路傳輸一般採用大端序,也被稱之為網路位元組序,或網路序IP協議中定義大端序為網路位元組序。

socketAPI定義了一組轉換函式,用於16和32bit整數在網路序和本機位元組序之間的轉換。htonl,htons用於本機序轉換到網路序;ntohl,ntohs用於網路序轉換到本機序。

3)位序

一般用於描述序列裝置的傳輸順序。一般硬體傳輸採用小端序(先傳低位),但I2C協議採用大端序。網路協議中只有資料鏈路層的底端會涉及到。

4)處理器體系

 1)小端序體系x86MOS Technology 6502Z80VAXPDP-11等處理器為Littleendian。

 3)可配置:ARMPowerPC (除PowerPC 970外), DEC AlphaSPARCV9MIPSPA-RISC and IA64的位元組序是可配置的。

5) 程式設計判斷大端序和小端序

#include <stdio.h>

bool IsBigEndian(long a)
{
    if(((char *)&a)[3] == 1)
        return true ;
    else
        return false ;
}

void main(){
    bool b ;
    long a = 0x12345678; 
    b = IsBigEndian(a );
	printf("%d", &a);
}
開啟VS的記憶體視窗,檢視記憶體儲存方式:

設定斷點:


開啟 debug——>window——>Memory

檢視變數a 的地址:0x002BFE50


檢視記憶體地址:

從上面看出我使用的x86 ,是小端序。

從而驗證 b =false是正確的。