Chap.3 地址族与数据序列

分配给套接字的IP地址与端口号

只需通过IP地址的第一个字节即可判断网络地址占用的字节数:
A类地址的首字节范围:0~127
B类地址的首字节范围:128~191
C类地址的首字节范围:192~223

端口号是在统一操作系统内为区分不同套接字而设置的,因此无法将1个端口号要分配给不同套接字(但TCP与UDP之间可以重复)。可分配的端口号范围是065535,其中01023是知名端口号。

地址信息的表示

struct sockaddr_in
{
    sa_family_t       sin_family;     // 地址族
    uint16_t          sin_port;       // 16位TCP/UDP端口号,以网络字节序保存
    struct in_addr    sin_addr;       // 32位IP地址,以网络字节序保存
    char              sin_zero[8];    // 不使用,填充0
}
struct in_addr
{
    in_addr_t         s_addr;         // 32位IPv4地址
}


网络字节序与地址变换

CPU向内存保存数据的方式有两种(以4字节整型数值1为例):

  • 大端序:高位字节存放到位地址(00000000 00000000 00000000 00000001)。
  • 小端序:高位字节存放到位地址(00000001 00000000 00000000 00000000)。

目前主流的Intel系列CPU以小端方式保存数据。网络字节序是通过网络传输数据时约定的统一方式,是大端序。

传输时是从低位地址开始传输的。

帮助转换字节序的函数:

  • unsigned short htons(unsigned short);
  • unsigned short ntohs(unsigned short);
  • unsigned long htonl(unsigned long);
  • unsigned long ntohl(unsigned short);

前两者用于端口号转换,后两者用于IP地址转换。

# gcc endian_conv.c -o conv
# ./conv  
Host ordered port: 0x1234 
Network ordered port: 0x3412 
Host ordered port: 0x12345678 
Network ordered port: 0x78563412 

这是在小端序CPU中运行的结果。如果在大端序CPU中运行,网络和主机序的字节应是一样的。

网络地址的初始化与分配

以下函数不仅完成从字符串到十进制IP地址的转换,同时也进行了网络字节序转换。

#include 
in_addr_t inet_addr(const char *string);

该函数的调用:

# gcc inet_addr.c -o addr
# ./addr 
Network ordered integer addr: 0x4030201 
Error occured!

inet_aton函数与inet_addr函数类似,只是它利用了in_addr结构,更加方便编程。

#include 
int inet_aton(const char *string, struct in_addr *addr);    // 成功返回1,失败返回0

inet_aton函数的调用过程:

# gcc inet_aton.c -o aton
# ./aton
Network ordered interger addr: 0x4f7ce87f

当然还有从十进制IP地址转换回字符串的函数:

#include 
char * inet_ntoa(in_addr addr);    // 成功返回字符串,失败返回NULL

注意该函数在自己的域申请了内存地址来存放返回的结果,在下次调用这个函数之前应将返回的结果保存到别的变量中,以免再次调用时被覆盖。测试一下:

# gcc inet_ntoa.c -o ntoa
# ./ntoa 
Dotted-Decimal notiation1: 1.2.3.4 
Dotted-Decimal notiation2: 1.1.1.1 
Dotted-Decimal notiation3: 1.2.3.4

如果服务器不特意绑定自己的某一个地址,而想接收从每个网卡发给指定port的数据,就可以使用INADDR_ANY,它代表本机地址0.0.0.0。使用时只需addr.sin_addr.s_addr=htonl(INADDR_ANY);即可。

习题

  1. IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?
    主要区别在于表示IP地址所用的字节数。为了应对IP地址耗尽问题而提出了IPv6。
  2. 通过IPv4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程。
    先查看网络地址(网络ID),将数据传到构成该网络的路由器后,再查看主机地址(主机ID)并将数据传给目标主机。
  3. 套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?
    IP地址区分计算机,端口号区分应用。
  4. 请说明IP地址的分类方法,并据此说出下面这些IP地址的分类。
    按第一个字节来分类。
    a. 214.121.212.102 (C)
    b. 120.101.122.89 (A)
    c. 129.78.102.211 (B)
  5. 计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用。
    路由器根据网络地址传递数据,交换机根据主机地址传递数据。
  6. 什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP和FTP端口号各是多少?
    分配给特定应用程序的端口。1~1023。HTTP的默认端口号是80。默认情况下FTP协议使用TCP端口中的20和21这两个端口,其中20用于传输数据,21用于传输控制信息。
  7. 向套接字分配地址的bind函数原型是int bind(int sockfd, struct sockaddr *myaddr, socklen_t addr_len);而调用时则用bind(serv_sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr));此处serv_addr与函数原型不同,传入的是sockaddr_in结构体变量,请说明原因。
    首先,struct sockaddr是一个统一的结构,可保存各种协议的地址与端口号等信息,所以bind函数必须使用这个通用的结构。其次,struct sockaddr除了第一个参数协议族以外,后面需要填充的地址与端口号(还有填充0的部分)是一个14字节的char型数组,由于对字节的操作比较麻烦,可以先将它们保存到sockaddr_in结构体中,再在传递时强制转换。
  8. 请解释大端序、小端序、网络字节序,并说明为何需要网络字节序。
    大端序指的是数据在内存中从把高位字节放到低位地址。小端序指的是数据在内存中从把低位字节放到低位地址。网络字节序是一种在网络中传输数据的约定,它是大端序。它的作用就是统一数据传输的标准。
  9. 大端序计算机希望把4字节整型数据12传递到小端序计算机。请说出数据传输过程中发生的字节序变换过程。
    4字节整型12在大端序计算机中保存为0x0c000000,在传输前转换成网络字节序,仍是0x0c000000。小端序计算机接收到后转换成小端序进行保存,即0x0000000c。
  10. 怎样表示回送地址?其含义是什么?如果向回送地址传输数据将发生什么情况?
    127.x.x.x(IPv4)或::1(IPv6)。计算机自身IP地址。数据被发送给本机的服务。


我的问题

  1. 多次调用inet_ntoa函数的结果为什么会被覆盖?
    字符串是在其内部静态分配的,后面的每次调用都会覆盖上一次的值。
char * 
inet_ntoa (struct in_addr in) 
{ 
  __libc_once_define (static, once); 
  char *buffer; 
  unsigned char *bytes; 
 
  /* If we have not yet initialized the buffer do it now.  */ 
  __libc_once (once, init); 
 
  if (static_buf != NULL) 
    buffer = static_buf; 
  else 
    { 
      /* We don't use the static buffer and so we have a key.  Use it
     to get the thread-specific buffer.  */ 
      buffer = __libc_getspecific (key); 
      if (buffer == NULL) 
    { 
      /* No buffer allocated so far.  */ 
      buffer = malloc (18); 
      if (buffer == NULL) 
        /* No more memory available.  We use the static buffer.  */ 
        buffer = local_buf; 
      else 
        __libc_setspecific (key, buffer); 
    } 
    } 
 
  bytes = (unsigned char *) ∈ 
  __snprintf (buffer, 18, "%d.%d.%d.%d", 
          bytes[0], bytes[1], bytes[2], bytes[3]); 
 
  return buffer; 
} 
  1. 地址127.0.0.1和0.0.0.0的区别?
    IPv4中,0.0.0.0地址被用于表示一个无效的,未知的或者不可用的目标。在服务器中,0.0.0.0指的是本机上的所有IPv4地址,如果一个主机有两个IP地址,192.168.1.1和10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。在路由中,0.0.0.0表示的是默认路由,即当路由表中没有找到完全匹配的路由的时候所对应的路由。
    用途总结:
    a. 当一台主机还没有被分配一个IP地址的时候,用于表示主机本身(DHCP分配IP地址的时候)。
    b. 用作默认路由,表示”任意IPV4主机”。
    c. 用来表示目标机器不可用。
    d. 用作服务端,表示本机上的任意IPV4地址。
    127.0.0.1属于{127,}集合中的一个,而所有网络号为127的地址都被称之为回环地址,所以回环地址!=127.0.0.1,它们是包含关系,即回环地址包含127.0.0.1。
    回环地址:所有发往该类地址的数据包都应该被loop back。
    用途总结:
    a. 回环测试,通过使用ping 127.0.0.1测试某台机器上的网络设备,操作系统或者TCP/IP实现是否工作正常。
    b. DDos攻击防御:网站收到DDos攻击之后,将域名A记录到127.0.0.1,即让攻击者自己攻击自己(?)。
    c. 大部分Web容器测试的时候绑定的本机地址。
  2. 发送给127.0.0.1的数据是怎样被传递的?
    数据包不会经过网卡,而是在TCP/IP栈的环回驱动程序中直接被放到IP输入函数。


附录

Github
127.0.0.1和0.0.0.0地址的区别
网络篇-“Ping 127.0.0.1”工作原理你知道吗?

你可能感兴趣的:( Chap.3 地址族与数据序列)