分配给套接字的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 <arpa/inet.h>
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 <arpa/inet.h>
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 <arpa/inet.h>
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);
即可。
习题
- IP地址族IPv4和IPv6有何区别?在何种背景下诞生了IPv6?
主要区别在于表示IP地址所用的字节数。为了应对IP地址耗尽问题而提出了IPv6。- 通过IPv4网络ID、主机ID及路由器的关系说明向公司局域网中的计算机传输数据的过程。
先查看网络地址(网络ID),将数据传到构成该网络的路由器后,再查看主机地址(主机ID)并将数据传给目标主机。- 套接字地址分为IP地址和端口号。为什么需要IP地址和端口号?或者说,通过IP可以区分哪些对象?通过端口号可以区分哪些对象?
IP地址区分计算机,端口号区分应用。- 请说明IP地址的分类方法,并据此说出下面这些IP地址的分类。
按第一个字节来分类。
a. 214.121.212.102 (C)
b. 120.101.122.89 (A)
c. 129.78.102.211 (B)- 计算机通过路由器或交换机连接到互联网。请说出路由器和交换机的作用。
路由器根据网络地址传递数据,交换机根据主机地址传递数据。- 什么是知名端口?其范围是多少?知名端口中具有代表性的HTTP和FTP端口号各是多少?
分配给特定应用程序的端口。1~1023。HTTP的默认端口号是80。默认情况下FTP协议使用TCP端口中的20和21这两个端口,其中20用于传输数据,21用于传输控制信息。- 向套接字分配地址的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
结构体中,再在传递时强制转换。- 请解释大端序、小端序、网络字节序,并说明为何需要网络字节序。
大端序指的是数据在内存中从把高位字节放到低位地址。小端序指的是数据在内存中从把低位字节放到低位地址。网络字节序是一种在网络中传输数据的约定,它是大端序。它的作用就是统一数据传输的标准。- 大端序计算机希望把4字节整型数据12传递到小端序计算机。请说出数据传输过程中发生的字节序变换过程。
4字节整型12在大端序计算机中保存为0x0c000000,在传输前转换成网络字节序,仍是0x0c000000。小端序计算机接收到后转换成小端序进行保存,即0x0000000c。- 怎样表示回送地址?其含义是什么?如果向回送地址传输数据将发生什么情况?
127.x.x.x(IPv4)或::1(IPv6)。计算机自身IP地址。数据被发送给本机的服务。
我的问题
- 多次调用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;
}
- 地址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容器测试的时候绑定的本机地址。 - 发送给127.0.0.1的数据是怎样被传递的?
数据包不会经过网卡,而是在TCP/IP栈的环回驱动程序中直接被放到IP输入函数。