使用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.0×12”。
我們在前面的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] = ''; 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 bit,lsb):是指一個二進制數字中的第0位(即最低位),具有權值為2^0,可以用它來檢測數的奇偶性。與之相反的稱之為最高有效位。在大端序中,lsb指最右邊的位。
最高有效位(the Most Significant Bit,msb):是指一個n位二進制數字中的n-1位,具有最高的權值 |
單字節(abyte):大部分處理器以相同的順序處理位元(bit),因此單字節的存放方法和傳輸方式一般相同。 多字節:如整數(32位機中一般占4字節),多字節對象被存儲為連續的字節序列,數據的內存地址則是該內存地址的最小地址。 如long型數據的地址是ox001, ox002, ox003,ox004。則該數據的內存地址是ox001。 在不同的處理器的存放多字節數據的方式主要有兩種: 大端序(英文名稱為big endian):指從最高位起存,位數最大的數字在最前,即高字節存于內存低地址,低字節存于內存高地址, 從最高有效字節到最低有效字節的順序存儲對象。 小端序(英文名稱為little endian):指從對低位起存,位數最小的數字在最前。 即低字節存于內存低地址,高字節存于內存高地址,從最低有效字節到最高有效字節的順序存儲對象。 簡單打個比方說,十進制數12345。1的位數最高,是萬位;5的位數最低,是個位。 大端序的話,就是從萬位開始存,表示為12345; 再如一個long型數據0x12345678的存儲表示: 大端序存儲表示: 內存地址數據內存低地址–> a0x00112a+10x00234a+20×00356內存高地址–>a+30×00478 小端序的存儲表示: 內存地址數據內存低地址–>a0x00178a+10x00256a+20×00334內存高地址–>a+30×00412 |
2)網絡序
網絡傳輸一般采用大端序,也被稱之為網絡字節序,或網絡序。IP協議中定義大端序為網絡字節序。
socketAPI定義了一組轉換函數,用于16和32bit整數在網絡序和本機字節序之間的轉換。htonl,htons用于本機序轉換到網絡序;ntohl,ntohs用于網絡序轉換到本機序。
3)位序
一般用于描述串行設備的傳輸順序。一般硬件傳輸采用小端序(先傳低位),但I2C協議采用大端序。網絡協議中只有數據鏈路層的底端會涉及到。
4)處理器體系
1)小端序體系:x86,MOS Technology 6502,Z80,VAX,PDP-11等處理器為Littleendian。
2)大端序體系:Motorola 6800,Motorola 68000,PowerPC 970,System/370,SPARC(除V9外)等處理器為Big endian
3)可配置:ARM, PowerPC (除PowerPC 970外), DEC Alpha, SPARCV9, MIPS, PA-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是正確的。
轉自:http://blog.csdn.net/hguisu/article/details/7449955
原創文章,作者:s19930811,如若轉載,請注明出處:http://www.www58058.com/2988