上一章中,我們已經了可以分配與初始化各種類型的套接口。這些是由一個常量進行初始化的簡單例子。由一個使用變化地址的C字符串設置一個地址需要更多的編程努力。在這一章,我們將會關注建立網絡地址的傳統問題,以及瞭解可以在這一領域幫助我們的函數。
在這一章,我們瞭解到如下內容:
網絡地址分類
IP網絡掩碼
私有的以及保留的IP地址
IP轉換函數
然而在我們開始之前,這是一個很好的機會來回顧一下IP地址的設計。這樣我們就會更爲理解我們將要進行的工作。
網絡IP地址
IP地址是由四個十進制數組成的,其中由十進制的點來分隔,通常爲點。每一個十進制值以網絡字節順序來表示一個字節的無符號值。在這裏我們記住網絡字節順序要求最重要的字節先出現(大端順序)。
每一個字節都作爲一個無符號的8位值。這將每一個字節的值限制在0到255之間。因爲這個值是無符號的,這個值不可以是負的,加號也是不允許的。例如,考慮下地址192.168.0.1,網絡順序的第一個字節必須爲十進制的192。
當我們看到一個電影在屏幕上顯示192.168.300.5的IP地址,我們就會知道這個製作者對於TCP/IP編程瞭解較少。雖然這個地址在句法上是正確的,但是十進制的300超過了最大的無符號數255。
網絡地址分類
網絡地址是由下面的兩個組件構成的:
網絡號(最重要位)
主機號(次重要位)
網絡號標識主機可以連接到的網絡。主機號標識一個特定網絡中的一個主機(例如我們的PC)。
正如我們已經知道的,IP地址是32位的(或者是4個8位字節)。然而,網絡號與主機號組件之間的分隔並不是固定的位置。分隔線取決於網絡地址的分類,這是由地址的最重要的字節的檢測來決定的。下表顯示了IP地址是如何分類的:
Table 3.1: Internet Address Classes
Class Lowest Highest Network Bits Host Bits
A 0.0.0.0 127.255.255.255 7 24
B 128.0.0.0 191.255.255.255 14 16
C 192.0.0.0 223.255.255.255 21 8
D 224.0.0.0 239.255.255.255 28 N/A
E 240.0.0.0 247.255.255.255 27 N/A
A,B,C類定義了主機的特定IP地址。對於D類與E類地址,在地址沒有主機位可用。D類地址用於多播,其中28位用於描述一個多播組。E類地址的27位保留的。
下圖描述了32IP地址的分隔。下圖顯示的常用的A,B,C類地址:
理解網絡掩碼
有時我們必須決定一個地址的網絡掩碼。如是要我們設置我們的網絡時這尤爲正確。所以,什麼是一個網絡掩碼。
如 果我們將一個IP地址作爲32位,網絡ID是由地址的最重要的位來標識的。另外,同一個地址的主機ID是由次重要的位來決定的。網絡掩碼是一個簡單的值, 我們可以用來與一個地址進行按位與,從而只保留網絡ID。下圖顯示了IP地址192.168.9.1是如何進行掩碼從而得到網絡ID位的。
結果就得到了IP地址中表示網絡部分的最重要的位,而沒有主機ID。下圖演示了一個網絡掩碼如何轉換爲一個十進制IP地址的:
如果我們必須設置我們的網絡,我們就必須確定我們的網絡掩碼是多少。下表列出A,B,C類地址的網絡掩碼:
Class Lowest Highest Netmask
A 0.0.0.0 127.255.255.255 255.0.0.0
B 128.0.0.0 191.255.255.255 255.255.0.0
C 192.0.0.0 223.255.255.255 255.255.255.0
有時,在一個網絡軟件中,我們的軟件必須可以分類一個網絡地址。有時,這是通過確定一個默認的網絡掩碼來簡單完成的。
下面提供了一個簡單的例子程序來演示如何由一個套接口地址分類一個IP地址。
在這個程序中,在一個網絡套接口地址結構中設置了四個不同的IP地址。然後對這個地址進行檢測與分類。這就演示瞭如何分類連接到我們服務器的遠程客戶端的IP地址。
/*
* netmask.c
*
* Classify an IP address:
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
int main(int argc,char **argv)
{
int x; /* Index variable */
struct sockaddr_in adr_inet; /* AF_INET */
int len_inet; /* length */
unsigned msb; /* most significant byte */
char class;
char *netmask;
static struct
{
unsigned char ip[4];
}addresses[]={
{{44,135,86,12}},
{{127,0,0,1}},
{{172,16,23,95}},
{{192,168,9,1}}
};
for (x=0;x<4;x++)
{
/*
* set up the socket address,to
* demonstrate how to classify it;
*/
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
memcpy(&adr_inet.sin_addr.s_addr,addresses[x].ip,4);
len_inet = sizeof adr_inet;
/*
* classify this address
*
* 1 get the most significant byte
* 2 classify by that byte
*/
msb = *(unsigned char*)&adr_inet.sin_addr.s_addr;
if((msb &0x80) == 0x00)
{
class = 'A';
netmask = "255.0.0.0";
}
else if((msb &0xc0) == 0x80)
{
class = 'B';
netmask = "255.255.0.0";
}
else if((msb &0xe0) == 0xc0)
{
class = 'C';
netmask = "255.255.255.0";
}
else if((msb &0xf0) == 0xe0)
{
class = 'D';
netmask = "255.255.255.255";
}
else
{
class = 'E';
netmask = "255.255.255.255";
}
printf("Address %u.%u.%u.%u is class %c "
"netmask %s/n",
addresses[x].ip[0],
addresses[x].ip[1],
addresses[x].ip[2],
addresses[x].ip[3],
class,
netmask);
}
return 0;
}
通過這個程序例子,我們就可以瞭解如何來分類一個正在處理的IP地址。
分配IP地址
在前一個例子中,我們已經瞭解瞭如何來分類一個IP地址。IP地址是由一個名InterNIC的組織分配給各種個人或是組織的。然而一些範圍的IP地址是設置爲私有的,而其他的一些保留爲特殊的用途。
私有IP地址
通常IP地址必須由InterNICd rs.internic.net進行註冊。然而,如果我們的系統並沒有直接連接到網絡,我們並不需要一個全球唯一的地址。相反,我們可以使用私用的IP地址。
緊隨着的第一個問題就是"我們應使用什麼IP地址?"。在這一節,我們就會解答這個問題。
RFC 1597是一個描述私有IP地址是如何分配的網絡標準文檔。下表是一個簡要的描述:
Class Lowest Highest Netmask
A 10.0.0.0 10.255.255.255 255.0.0.0
B 172.16.0.0 172.31.255.255 255.255.0.0
C 192.168.0.0 192.168.255.255 255.255.255.0
A,B,C類IP地址的選擇在很大程度上取決於單個的網絡數量以及我們要建立的主機數。如果網絡以及主機數很小,那個一個C類就足夠了。相對於,一個A類地址允許一個網絡,但是卻有大量的主機數目。B類地址提供了大量的網絡數與主機數。
保留IP地址
存在大量的保留IP地址,而這些內容位RFC 1166中。作爲保留系列地址的一個例子,在下表中將Amateur廣播IP地址系列作爲例子。現在AX.25協議已經構建進入Linux內核,將會有更多的廣播業餘愛好者使用這些IP地址。
Class Lowest Highest Netmask
A 44.0.0.0 44.255.255.255 255.0.0.0
處理IP地址
爲了簡化將字符串格式的IP地址轉換爲可用的套接口地址的編程負擔,提供了一些函數來完成這樣的工作。這些函數將會在下面的部分中進行描述。
使用inet_addr(3)函數
我們首先要了解的函數是一個較老的函數,這在新的代碼中將不會再使用。然而,我們仍可以在已存在的網絡代碼中看到這個函數,所以我們應瞭解他,並且知道他的限制。
inet_addr(3)函數概要如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long inet_addr(const char *string);
這個函數接受一個輸入的C字符串參數string,並且將其解析爲一個32位的網絡地址值。32位地址值是以網絡字節順序返回的。
如果輸入參數string並不是一個可用的地址值,則會返回INADDR_NONE。其他的返回值則代表轉換的值。
下面的這個例子程序演示瞭如何使用這個函數。當這個程序運行時,會將包含一個IP地址的C字符串轉換爲一個網絡順序的32位IP地址。然後把這個值放入AF_INET套接口地址,並且綁定到這個套接口。
/*
* inetaddr.c
*
* Example using inet_addr(3)
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
* this function reports the error and
* exits back to the shell
*/
static void bail(const char *on_what)
{
fputs(on_what,stderr);
fputs("/n",stderr);
exit(1);
}
int main(int argc,char **argv)
{
int z;
struct sockaddr_in adr_inet; /* AF_INET */
int len_inet; /* length */
int sck_inet; /* Socket */
/* create a socket */
sck_inet = socket(AF_INET,SOCK_STREAM,0);
if(sck_inet == -1)
bail("socket()");
/* Establish address */
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
adr_inet.sin_addr.s_addr = inet_addr("127.0.0.95");
if(adr_inet.sin_addr.s_addr == INADDR_NONE)
bail("bad address");
len_inet = sizeof adr_inet;
/* Bind it to the socket */
z = bind(sck_inet,(struct sockaddr *)&adr_inet,len_inet);
if(z == -1)
bail("bind()");
/* Display our socket address */
system("netstat -pa --tcp 2>/dev/null"
" | grep inetaddr");
return 0;
}
程序運行結果如下:
$ ./inetaddr
tcp 0 0 127.0.0.95:9000 *:* CLOSE 992/inetaddr
$
inet_aton(3)函數
inet_aton是一個改進的方法來將一個字符串IP地址轉換爲一個32位的網絡序列IP地址。這個函數的概要如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *string, struct in_addr *addr);
inet_aton函數接受兩個參數。參數描述如下:
1 輸入參數string包含ASCII表示的IP地址。
2 輸出參數addr是將要用新的IP地址更新的結構。
如果這個函數成功,函數的返回值非零。如果輸入地址不正確則會返回零。使用這個函數並沒有錯誤碼存放在errno中,所以他的值會被忽略。
對於這個函數有一點迷惑的就是這個函數調用所需要的兩個參數。如果我們定義了一個AF_INET套接口地址:
struct sockaddr_in adr_inet; /* AF_INET */
提供給inet_aton函數調用的參數指針爲 &adr_inet.sin_addr
下面這個程序使用inet_aton函數,而不是我們在前面所談到的in_addr函數。
/*
* inetaton.c
*
* Example using inet_aton
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
/*
* this function reports the error and
* exits back to the shell
*/
static void bail(const char *on_what)
{
fputs(on_what,stderr);
fputs("/n",stderr);
}
int main(int argc,char **argv)
{
int z;
struct sockaddr_in adr_inet; /* AF_INET */
int len_inet; /* length */
int sck_inet; /* Socket */
/* Create a Socket */
sck_inet = socket(AF_INET,SOCK_STREAM,0);
if(sck_inet == -1)
bail("Socket()");
/* Establish address */
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
if( !inet_aton("127.0.0.1",&adr_inet.sin_addr))
bail("bad address");
len_inet = sizeof adr_inet;
/* Bind it to the socket */
z = bind(sck_inet,(struct sockaddr *)&adr_inet,len_inet);
if(z == -1)
bail("bind()");
/* Display our socket address */
system("netstat -pa --tcp 2>/dev/null"
" | grep inetaton");
return 0;
}
程序的運行結果如下:
S$ ./inetaton
tcp 0 0 127.0.0.23:9000 *:* CLOSE 1007/inetaton
使用inet_ntoa(3)函數
有時一個套接口地址代表一個連接到我們服務器的用戶的地址,或者是一個UDP包。將一個網絡順序的32位值轉換爲一個點分隔的IP地址值是不方便的。從而提供了inet_ntoa函數。這個函數的概要如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
char *inet_ntoa(struct in_addr addr);
這個函數僅需要一個輸入參數addr。注意struct in_addr是網絡套接口地址的一個內部部分。這個地址被轉換爲函數個內部的的static緩衝區。字符數組的指針作爲返回值返回。只有當下一次調用這個函數時結果纔會可用。
如果在我們的程序中addr作爲一個sockaddr_in結構而存在,那麼下面的代碼就顯示瞭如何使用inet_ntoa函數來執行這個轉換。IP地址轉換爲一個字符串,並且使用printf函數進行輸出。
struct sockaddr_in addr; /* Socket Address */
printf("IP ADDR: %s/n",
inet_ntoa(addr.sin_addr));
下面提供一個完整的程序例子。
/*
* inetntoa.c:
*
* Example using inet_ntoa(3):
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char **argv)
{
struct sockaddr_in adr_inet; /* AF_INET */
int len_inet; /* length */
/*
* Establish address (pretend we got
* this address from a connecting client):
*/
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
if(!inet_aton("127.0.0.23",&adr_inet.sin_addr))
puts("bad address");
len_inet = sizeof adr_inet;
/*
* Demonstrate use of inet_ntoa(3):
*/
printf("The IP Address is %s/n",
inet_ntoa(adr_inet.sin_addr));
return 0;
}
這個程序的運行結果如下:
$ ./inetntoa
The IP Address is 127.0.0.23
使用inet_network(3)
有時會有這樣的情況,將一個點分隔的IP地址轉換爲一個32位的主機順序的值是比較方便的。當我們要執行掩碼值從地址中得到主機位或是網絡位是更爲方便。
inet_network的函數概要如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long inet_network(const char *addr);
這個函數需要一個在參數addr中包含一個點分隔的地址輸入字符串。返回值是IP地址的32位值,但是以主機順序的格式而存在。然而,如果輸入值不合法,返回結果就會爲0xFFFFFFFF。
擁有一個主機端順序的返回值意味着我們可以安全的執行掩碼或是位操作。如果返回值爲網絡端順序,那麼對於不同的CPU平臺就會有不同的操作。
下面的例子程序演示瞭如何使用inet_network函數。下面顯示瞭如何從一個C地址得到一個網絡地址:
unsigned long net_addr;
net_addr =
inet_network("192.168.9.1") & 0xFFFFFF00;
賦給net_addr的值應爲)0xC0A80900(或者是點分隔的192.168.9.0)。與操作屏蔽掉低8位從而得到網絡ID,而沒有主機ID。
/*
* network.c
*
* Example using inet_network(3):
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char **argv)
{
int x;
const char *addr[]={
"44.135.86.12",
"127.0.0.1",
"172.16.23.95",
"192.168.9.1"
};
unsigned long net_addr;
for(x=0;x<4;x++)
{
net_addr = inet_network(addr[x]);
printf("%14s = 0x%08lX net 0x%08lX/n",
addr[x],net_addr,(unsigned long)htonl(net_addr));
}
return 0;
}
程序的運行結果如下:
$ ./network
44.135.86.12 = 0x2C87560C net 0x0C56872C
127.0.0.1 = 0x7F000001 net 0x0100007F
172.16.23.95 = 0xAC10175F net 0x5F1710AC
192.168.9.1 = 0xC0A80901 net 0x0109A8C0
$
使用inet_lnaof(3)函數
inet_lnaof函數將一個包含在套接口地址中的網絡字節順序的IP地址轉換爲一個主機ID,而沒有網絡ID。返回值爲主機端順序。
這個函數省去我們確定IP地址然後得到主機ID部分的繁瑣操作。這個函數的概要如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long inet_lnaof(struct in_addr addr);
輸入參數必須爲我們通常操作所用的套接口地址的struct in_addr成員。這個值必須爲網絡字節順序,而這也正是這個函數所要求的。下面的例子演示瞭如何使用一個sockaddr_in地址爲調用這個函數:
struct sockaddr_in addr; /* Socket Address */
unsigned long host_id; /* Host ID number */
host_id = inet_lnaof(addr.sin_addr);
下表列出了一些可以應用inet_lnaof函數的值以及返回結果。
IP Number Class Hexadecimal Dotted-Quad
44.135.86.12 A 0087560C 0.135.86.12
127.0.0.1 A 00000001 0.0.0.1
172.16.23.95 B 0000175F 0.0.23.95
192.168.9.1 C 00000001 0.0.0.1
我們可以注意到在上表中A類地址在反回結果中只有第一個字節爲0,而B類地址在返回結果高十六位爲0.最後C類地址前三個字節爲0,只保留最後一位的主機號。
使用inet_netof(3)函數
inet_netof函數是與inet_lnaof函數相對的。inet_netof函數返回網絡ID而不是主機ID。在其他方面,這兩個函數是相同的。這個函數的概要如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
unsigned long inet_netof(struct in_addr addr);
如下面的例子所示:
struct sockaddr_in addr; /* Socket Address */
unsigned long net_id; /* Network ID number */
net_id = inet_netof(addr.sin_addr);
下表列出這個函數的一些例子返回值:
IP Number Class Hexadecimal Dotted-Quad
44.135.86.12 A 0000002C 0.0.0.44
127.0.0.1 A 0000007F 0.0.0.127
172.16.23.95 B 0000AC10 0.0.172.16
192.168.9.1 C 00C0A809 0.192.168.9
使用inet_makeaddr(3)函數
使用inet_netof與inet_lnaof函數我們可以得到主機ID與網絡ID。要使用網絡ID與主機ID重新組合爲IP地址,我們可以使用inet_makeaddr函數。這個函數的概要如下:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
struct in_addr inet_makeaddr(int net,int host);
這個函數的參數描述如下:
1 net參數是網絡ID,右對齊並且是主機端順序。這也由函數inet_netof函數的返回值相同。
2 host參數是主機ID,主機端順序。這也由函數inet_lnaof返回值相同。
返回值存放在sockaddr_in套接口地址中的struct in_addr成員中。這個值是網絡字節順序。
下面所演示的例子程序使用了inet_netof,inet_lnaof,inet_makeaddr三個函數。sockaddr_in結構中的IP地址將會被分解爲主機ID與網絡ID。然後套接口地址清零,並且由剛纔得到的網絡部分與主機部分重新進行組合。
/*
* makeaddr.c
*
* Demonstrate inet_lnaof,inet_netof
* and inet_makeaddr functions
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc,char **argv)
{
int x;
struct sockaddr_in adr_inet; /* AF_INET */
const char *addr[] =
{
"44.135.86.12",
"127.0.0.1",
"172.16.23.95",
"192.168.9.1"
};
unsigned long net,hst;
for(x=0;x<4;x++)
{
/*
* Create a socket address
*/
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
if(!inet_aton(addr[x],&adr_inet.sin_addr))
puts("bad address");
/*
* Split address into Host & Net ID
*/
hst = inet_lnaof(adr_inet.sin_addr);
net = inet_netof(adr_inet.sin_addr);
printf("%14s : net=0x%08lx host=0x%08lx/n",
inet_ntoa(adr_inet.sin_addr),net,hst);
/*
* Zero the address to prove later that
* we can reconstruct this value:
*/
memset(&adr_inet,0,sizeof adr_inet);
adr_inet.sin_family = AF_INET;
adr_inet.sin_port = htons(9000);
adr_inet.sin_addr = inet_makeaddr(net,hst);
/*
* Now display the reconstructed address;
*/
printf("%14s : %s/n/n",
"inet_makeaddr",
inet_ntoa(adr_inet.sin_addr));
}
return 0;
}
程序的運行結果如下:
$ ./makeaddr
44.135.86.12 : net=0x0000002C host=0x0087560C
inet_makeaddr : 44.135.86.12
127.0.0.1 : net=0x0000007F host=0x00000001
inet_makeaddr : 127.0.0.1
172.16.23.95 : net=0x0000AC10 host=0x0000175F
inet_makeaddr : 172.16.23.95
192.168.9.1 : net=0x00C0A809 host=0x00000001
inet_makeaddr : 192.168.9.1
$