概述
OSI 参考模型
网络通信是一个庞大的工程,其中网络协议就采用分层设计的思想。国际化标准组织提出了 开放式系统互联模型(open system interconnection model),简称 OSI 模型。
物理层(physical layer),负责管理通信设备和通信媒介,对设备的针脚、电压、线缆等做了详尽的定义。物理层为我们屏蔽了物理设备的底层细节,为上层提供信息比特传输能力。至于它对于线缆、电压等的规定,软件研发人员一般不需要关注。
数据链路层(data link layer)以 帧(frame)为传输单元,负责网络寻址、错误侦测,可以解决同一网络内多台主机的通信问题,最终实现局域网通信。
网络层(network layer)以 数据包(packet)为传输单元,负责路径选择和数据包转发。网络层建立在数据链路层的基础上,它实现了全网通信能力,可以将数据传送到网络中任一的节点。数据链路层只实现了局域网内的通信能力,为网络层提供服务,将包送至下一节点。两者分工明确,彼此协作。
传输层(transport layer)在网络层基础上,为应用进程提供端到端的通信服务,支持面向连接的数据流、流量控制以及可靠性保障。一个节点上通常有许多应用进程,为此传输层引入了端口的概念,实现了从进程到进程的传输能力。在 段(segment)中包含端口信息,网络转发后目标节点在包中取出段,再根据端口号送至目标进程。
应用层(application layer)负责应用的通信逻辑,并提供多样的网络应用服务。
- 会话层(session layer)为通信实体实现会话和连接管理功能,主要提供 用户认证、权限控制 等服务。
- 表示层(presentation layer)为不同终端的用户提供一致的数据表示和变换方法,比如 数据的编解码、数据的加密压缩、数据的压缩解压 等等。
TCP/IP 协议栈
网络访问层(network access layer)负责管理物理介质,并提供将数据从当前节点传输到下一节点的能力,相当于 物理层 + 数据链路层。
- 不同的通信介质,有不同的接入设备,采用的协议也不同:Ethernet,以太网协议;PPP,点对点协议;DSL,用户数字线路 等等。
网络互连层(internet layer)在网络访问层提供的局域网通信能力之上,实现网际通信能力,负责路径选择和数据包转发,相当于 网络层。
- IP 协议在该层以 IP 包为通信单元,通过该协议通信的主机需要分配一个 IP 地址,包中用 IP 地址来标识包的来源和目的地。
传输层(transport layer)在网络互连层点到点传输能力基础上,实现端到端的进程间通信。
- UDP 协议引入了一个端口号,当 UDP 段搭载在 IP 包中送给主机后,系统根据段中的数据提交给对应的进程。
- TCP 协议相对 UDP协议 来说,它为进程提供可靠、有序的数据流。
应用层(application layer)定义具体网络应用的通信逻辑,让应用进程间的写作成为可能。
- 网络应用协议常见的有:HTTP、HTTPS、FTP、SMTP 等等。
Ethernet:以太网协议
物理层+数据链路层
物理层的任务主要是确定与传输媒体接口的一些特性:
- (1)机械特性:指明接口接线器的形状和尺寸、引脚数目和排列、固定和锁定壮志等。
- (2)电气特性:指明接口电缆的各线上出现的电压的范围。
- (3)功能特性:指明某条线上出现的某一电平的电压的意义。
- (4)过程特性:指明对于不同功能的各种可能事件的出现顺序。
对于数据链路,我们先知道链路是指一段物理线路,中间没有任何交换节点;数据链路则是说,我们在这条物理线路上还需要一些通信协议来控制数据的传输。
一般通过网络适配器来实现这些协议,它通常包括了数据链路和物理层的功能。
以太网帧结构
在以太网中,数据通信达基本单位是 以太网帧(frame),由 头部(header)、数据(data)以及 校验和(checksum)三部分构成。
以太网帧头部包含 3 个字段:
- 目的地址,长度为 6 个字节,用于标记数据由哪台机器接收。
- 源地址,长度为 6 个字节,用于标记数据由哪台机器发送。
- 类型,长度为 2 字节,用于标记数据如何处理,
0x0800
表示该帧数据是一个 IP 包。
以太网数据是任何需要发送的信息,长度可变,46 至 1500 字节均可。
以太网校验和,长度为 4 个字节。因为物理信号可能受到环境干扰,网络设备传输的比特流会出错,为了保证传输以太网帧的时候是完好无损的,我们就要用到校验和。
- 发送者负责为每个以太网帧计算校验和,并计算结果填写在校验和字段中;接收者接收到以太网帧后,重新计算校验和并与校验和字段对比;如果两个校验和不一致,说明该帧在传输时出错了。
MAC 地址
MAC(Media Access Control Address)地址也是以太网地址,也叫硬件地址、物理地址、网卡地址。
- 物理层上,网卡负责比特流和电信号之间相互转换。
- 软件层上,内核协议栈负责封装以太网帧,并调用网卡驱动发送;接收数据时,负责验证 目的地址、校验和 并取出数据部分交予上层协议栈处理。
MAC 地址由 6 个字节(48位)组成。
- 3 字节长的 厂商代码(OUI),由国际组织分配给不同的网络设备商。
- 3 字节长的 序列号(SN),由厂商分配给它生产的网络设备。
MAC 地址很难直接用 ASCII 码来解读,所以我们将一个 8 位 字节的字符,分成 高 4 位 和 低 4 位,并且每个部分用 16 进制的字符来表示。
在 Linux 系统中,我们用 ip link
命令来查看网卡。
[root@iZ7xvfomazz4187zib4aurZ ~]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:16:3e:03:2c:8d brd ff:ff:ff:ff:ff:ff
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 02:42:61:8f:61:b8 brd ff:ff:ff:ff:ff:ff
我们也可以通过套接字编程直接向系统获取,Linux 套接字支持通过 ioctl
系统调用获取网络设备,大致步骤如下:(补充内容请看附录)
1、创建套接字。
2、准备 ifreq
结构体,保存网卡设备信息。
3、将查询网卡名填充到 ifreq
。
4、调用 ioctl
系统,向套接字发送 SIOCGIFHWADDR
请求获取物理地址。
# include <net/if.h>
# include <stdio.h>
# include <string.h>
# include <sys/ioctl.h>
# include <sys/socket.h>
void mac_ntoa(unsigned char *n, char *a){
// 格式化数据,并写入字符串 a
sprintf(a, "%02x:%02x:%02x:%02x:%02x:%02x", n[0], n[1], n[2], n[3], n[4], n[5]);
}
int main(int argc, char *argv[]){
if (argc < 2){
fprintf(stderr, "no iface given\n");
return 1;
}
// 创建套接字
int s = socket(AF_INET, SOCK_STREAM, 0);
if (-1 == s){
perror("Fail to create socket");
return 2;
}
// 填充字符串名字
struct ifreq ifr;
strncpy(ifr.ifr_name, argv[1], 15);
// 调用 ioctl 驱动获得地址
int ret = ioctl(s, SIOCGIFHWADDR, &ifr);
if (-1 == ret){
perror("Fail to get mac address");
return 3;
}
// 转换为可读形式
char mac[18];
mac_ntoa((unsigned char *)ifr.ifr_hwaddr.sa_data, mac);
// 输出结果
printf("IFace: %s\n", ifr.ifr_name);
printf("MAC: %s\n", mac);
return 0;
}
MTU 最大传输单元
如果待发送的数据超过帧的最大承载能力,就需要先对数据进行分片,然后再通过若干个帧进行传输。
待发送的数据总共 4000 字节,假设以太网设备一帧最多只能承载 1500 字节。很明显,数据需要划分成 3 片,再通过 3 个帧进行发送。
编程发送以太网帧
实现 sendether 命令,用于发送以太网帧,它的用法是:
- -i -iface:用于发送以太网帧的网卡名。
- -t -to:目的 MAC 地址。
- -T -type:以太类型。
- -d -data:待发送数据。
send_ether -i enp0s8 -t 0a:00:27:00:00:00 -T 0x1024 -d "Hello, world!"
使用 enp0s8
网卡,向 0a:00:27:00:00:00
发送一个类型为 0x1024
的以太网帧。
IP:互联网协议
网络层
在数据链路层中,我们能够实现同一网络内多台主机之间的通信问题,网络适配器将数据封装为以太网帧然后发送给其他主机。
为了能够实现更大规模的网络互联,我们在数据链路层的基础上添加了网络层。
网络层做了什么呢?
它给参与网络层通信的主机都分配一个唯一地址,并且这些地址是按照网络拓扑结构进行分配的,保证一个组织内部的地址是连续的。
然后,它将多个不同的以太网用转发设备进行互联,这个转发设备维护路由表,规定了目的地址和下一条都对应关系。
IP 包结构
IP 是 互联网协议 ( internet protocol ) 的简称,是 TCP/IP 协议栈中的网络层协议。IP 协议在发展的过程中,衍生出 IPv4 和 IPv6 两个不同版本。其中,历史版本 IPv4 目前仍广泛使用;后继版本 IPv6 世界各地正在积极部署。
IP 地址详解
- 主机号比特全为 0 ,是网络的起始地址,用于表示网络本身,一般称为 网络地址 ;
- 主机号比特全为 1 ,是网络的结束地址,用于向网络内的所有主机进行广播,一般称为 广播地址 ;
子网掩码
我们可以通过子网长度来划定子网个数,一般使用掩码来记录 IP 地址中的网络号部分。
掩码位数与 IP 地址一样,1
表示该位属于网络号,0
表示该位属于主机号。如果你学过 C 语言,应该知道通过按位与操作 &
,掩码可以快速取出一个 IP 地址的网络号。
这就是所谓的 子网掩码 ,它也可以用 点分十进制表示法 来表示,用来描述 IP 地址的网络号部分。因此,子网 10.0.0.x
可以表示成 10.0.0.0/255.255.255.0
;A 类网络 10.x.x.x
可以表示成 10.0.0.0/255.0.0.0
。
实际上,描述网络号还有更简洁的方法:在 IP 地址后面加上斜杆和网络号的位数。例如:
10.0.0.0/255.255.255.0
可以表示成10.0.0.0/24
;10.0.0.0/255.0.0.0
可以表示成10.0.0.0/8
;
TTL,IP 包存活时间
环 ( cycle ),一种首尾相连的特殊路径,是图论中重点研究的对象。如果 IP 包陷入环路的包无法清理,最终将耗尽线路的带宽和路由器的处理资源,影响正常数据的传输,造成大量丢包。
IP 包中的 TTL 字段是 Time To Live 的缩写,表示 IP 包的存活时间,如果 IP 包存活时间超过 TTL,路由就会将它丢弃。这样就算 IP 包陷入循环,经过一小段时间,它也会从网络中消失,不会造成更大的影响。
但因为网络路由器数量巨大,很难做到时间同步,因此,在实际工程实现中,TTL 并不是直接保存存活时间,而是保存 IP 包失效前可以经过的路由跳数。IP 包每经过一跳路由,TTL 都会减一;当 TTL 减到 0 ,路由便将它丢弃。
[root@yikuanzz ~]# cat /proc/sys/net/ipv4/ip_default_ttl
64
网络规划
假设,公司现在要给每个部分组建一个局域网,每个局域网通过一台路由器接到核心路由网络。
我们还注意到,不同的部门,主机数量也有多有少:
部门 | 主机数量 |
---|---|
行政部 | 50 |
研发部 | 100 |
市场部 | 80 |
人力部 | 50 |
法务部 | 50 |
因此,每个部门局域网所需要的网段,大小也是不一样的。假设管理员拿到了一个地址段 192.168.170.0/23
,如何将地址划分给图中的子网呢?
按照上述划分,我们就可以将各个部门的子网都分配好了。
ARP:地址解析协议
ARP 协议原理
我们知道,可以通过 IP 地址来指定数据包发送的目标地址,但是在数据链路层封装为帧的时候,怎么知道目标 IP 的 MAC 地址呢?
最直觉的方法就是让主机发送广播,询问网络中的其他主机来去得到目标 IP 的 MAC 地址。
ff:ff:ff:ff:ff:ff 是一个特殊的广播地址,目的地址为 ff:ff:ff:ff:ff:ff 的以太网帧,将被送到以太网中的每一台主机。交换机收到目的地址为 ff:ff:ff:ff:ff:ff 的帧后,也会将它广播到所有端口。
当主机收到目标 IP 发给自己的 MAC 地之后将其放在映射表中,后面传输的时候就可以直接从缓存中查询了。当然,为了保证时效性,ARP 缓存是要设置有效期到,如果缓存失效了,系统就必须重新发起 ARP 查询并且缓存最新结果。
那么,ARP 协议就是对这个过程进行统一的规范,并且 ARP 报文是内嵌在以太网帧之中的。
一个 ARP 报文中有 9 个字段,分别是:
- 硬件类型( hardware type ),数据链路层协议类型,例如:
1
表示以太网协议; - 协议类型( protocol type ),网络层协议类型,例如
0x0800
表示 IP 协议; - 硬件地址长度( hardware address length ),数据链路层地址长度,对以太网来说,地址长度为 6 字节;
- 协议地址长度( protocol addess length ),网络层地址长度,对 IP 协议来说,地址长度为 4 字节;
- 操作码( operation ),报文的操作类型,
1
表示 请求 ( request ),2
表示 应答 ( reply ); - 源硬件地址(sender hardware address),即发送者的数据链路层地址;
- 对于 ARP 请求,该字段表示请求发起者的地址;
- 对于 ARP 应答,该字段表示请求应答者的地址,也就是发起者查找的地址;
- 源协议地址( sender protocol address ),即发送者的网络层地址;
- 目标硬件地址(target hardware address),即接收者的数据链路层地址;
- 对于 ARP 请求,这个字段被忽略;
- 对于 ARP 应答,这个字段表示请求发起者的地址;
- 目标协议地址( target protocol address),即接收者的网络层地址;
- 对于 ARP 请求,这个字段就是待查找地址;
- 对于 ARP 应答,这个字段表示请求发起者的地址;
ARP 攻击
因为 ARP 协议没有真伪校验机制,如果黑客进入到主机所在的网络,他就可以轻易地监听主机发送的网络信息。
在 ARP 广播的时候,黑客不断地发送伪造的 ARP 回应给主机, 当然黑客为了保持隐蔽,它还是会将包转发给网关。这样,主机以为自己将包发送给了网关,而网关也误以为自己收到了主机发送的数据。
那么,怎么防止 ARP 攻击呢?
-
双绑措施
在路由和终端上同时进行 IP 地址和 MAC 地址的绑定,相当于人工管理 ARP 缓存,自行维护 IP 地址和 MAC 地址的对应关系。既然将 ARP 协议弃之不用,黑客就无法发起攻击了。
这个方案虽然可以奏效,但维护非常繁琐。换个网卡或 IP 地址,都需要重新修改配置。当流动电脑临时接入时,也要即时进行绑定,费时费力。
-
交换机端口绑定
跟双绑措施类似,只不过将 IP 和交换机端口进行绑定,缺点也是类似的。
-
PPPoE
使用 PPPoE 协议对网络流量进行二次封装,为每个用户都分配账号密码,上网时必须通过认证。这样 ARP 报文在一个认证的通道中传输,也就不会遭受攻击了。
但 PPPoE 也不是完美的,由于二次封装的存在,传输效率会打些折扣。更严重的是,PPPoE 方式下局域网内无法互访。如果局域网内需要部署文件服务器、打印机,就有麻烦了。
其实最关键的一点在于,坚持使用 HTTPS 这样的加密协议。这样就算遭遇 ARP 劫持,信息也不会被窃取。在陌生的网络环境中,应该尽量不用 HTTP 这样的明文协议。
ICMP:互联网控制报文协议
ICMP 协议概述
互联网控制消息协议 ( Internet Control Message Protocol )简称 ICMP ,是 IP 的辅助协议,同样位于网络层,负责网际通信中 控制信息 和 差错信息 的传送。
ICMP 有赖 IP 提供的网际通信能力,它的报文作为数据承载在 IP 包中进行传输。
ICMP 报文同样分为 头部 和 数据 两部分,其中头部字段如下:
- 类型 ( type ),顾名思义用来标识 ICMP 报文的类型,例如 目的不可达 ( destination unreachable )差错报文;
- 代码 ( code ),进一步划分 ICMP 报文的类型,标识错误的原因,例如目的不可达可以进一步分成网络不可达、主机不可达以及端口不可达等等;
- 校验和 ( checksum ),用于差错校验,由 ICMP 头部计算得出;
UDP:用户数据报协议
传输层概述
在网络层中,我们实现点对点之间的数据传输,就是将数据封装为 IP 包,在包头指定目标主机的 IP 地址,然后在数据链路层封装为以太网帧,发送给路由器,再由路由器进行分发。
当然,一台主机上不可能指挥允许一个进程,点到点之间的数据传输并不能满足我们的需求,也就是说,我们需要进程与进程之间的传输。
于是在上述基础上,我们添加了传输层,引入了端口(port)的概念,用来区分不同的通信端点。
一台主机上可以有很多个通信端口,应用进程可以关联到一个或多个端口。当进程需要发送数据时,它必须申请一个端口,数据从该端口发送出去;当某个端口有数据到达时,操作系统负责将数据提交给对应的应用进程。
这种从进程到进程的传输能力,可以叫做端到端的传输。
我们有一些知名的端口号:
服务 | 端口 |
---|---|
FTP文件传输 | 20 、 21 |
SSH安全远程登录 | 22 |
SMTP邮件传输 | 25 |
DNS域名系统 | 53 |
Web | 80 、 443 |
UDP 数据报格式
UDP 是 用户数据包协议 ( user datagram protocol )的简称,它是一种简单的数据报式传输层协议。UDP 数据报结构非常简单,头部只包含端口号等若干个字段。
由于 UDP 位于传输层,因而报文有时也称为 UDP 段或 UDP 分组。UDP 报文也分为头部和数据两个部分,结构跟我们在上节讨论的传输层段几乎一模一样。其中,头部只有 4 个字段:
- 源端口( source port ),发送方的端口号;
- 目的端口( destination port ),接收方的端口号;
- 报文长度( length ),即整个 UDP 报文的长度,包括头部和数据,单位为 字节 。
- 检验和( checksum ),与 IP 校验不同,UDP 整个报文都会参与校验和计算,除此之外,UDP 还会在报文前面拼接一个 IP 伪头部,同时参与校验和计算;
UDP 报文需要借助 IP 协议提供的主机通信能力,作为数据搭载在 IP 包中发往目标主机。
TCP:传输控制协议
TCP 协议简介
UDP 数据报是借助 IP 包提供的点对点传输能力来实现的端到端的传输,然而 IP 协议只是一种“尽力而为”的网络协议,它无法保证 IP 包一定能够送到目标主机。实际上,由于网路链路拥堵或者中间路由设备故障点存在,会有 IP 丢包的现象出现。此外,UDP 协议也没有流量控制机制、也没有拥塞控制机制,当遇到某些情况时是无法解决的。
为了解决 UDP 协议的局限性,一种更高级的传输协议被设计出来了 TCP(Transimission Control Protocol)它是一种 面向连接的流式协议 ,可为应用程序提供 可靠的字节流传输服务 。
- 连接主动发起方(一般是客户端),向被动连接方(一般是服务端)发出
SYN
; - 被动连接方收到
SYN
后,向主动发起方回复SYN+ACK
; - 主动发起方收到
SYN
后,向被动连接方回复ACK
;
其中,SYN
指令表示序号同步请求,ACK
表示确认,即对同步请求进行确认。
TCP 将数据组织成连续的字节流,每个字节均可由一个唯一的序号来标识。
TCP 在发送数据时,会将数据的 起始序号 和 长度 告诉对端,接收方收到数据后,将发送 ACK 对数据进行确认。ACK 中包含确认序号,它的值为最后一个已接收字节的序号加一,也就是接收方期望收到的新数据的起始序号。
那么,TCP 如何实现 流量控制 和 拥塞控制 的呢?
-
流量控制。根据 TCP 协议规定,接收方需要维护一个 接收窗口 ,我们可以将它看作内存中的一个缓冲区。在连接建立和数据传输的过程中,接收方会将自己的接收窗口大小通告给发送方。发送方必须保证,发送的数据不超过接收窗口。如果接收窗口被占满,发送方就暂停发送新数据。
-
拥塞控制。TCP 发送方自己在内部维护了一个 发送窗口 ,也叫做 拥塞窗口 。这是一个为发送策略算法服务的虚拟概念,表示可以发送的字节数(包含已经发送但仍未确认部分)。发送窗口一开始呈指数增长,比如每收到一个 ACK 就将它翻倍;当接收窗口增大到一定水平,增长速度降为线性增长。这就是 TCP 的 慢启动 过程,在网络状况良好的前提下,不断提高发送速度。如果网络发生拥塞,有数据丢包,这时 TCP 必须重传数据。发送方在重传数据的同时,还会降低发送窗口大小。这种情况下,大部分 TCP 实现会将发送窗口降为原来的一半,以便快速响应,避免进一步堵塞网络。这个机制也被称为 指数退避 。
最后是 TCP 的关闭,假设主机①数据发送完毕,它向主机②发出 FIN ,成为主动关闭方;主机②则成为被动关闭方,它回复 ACK 后,从主机①到主机②的数据流关闭。这时,连接处于 半关闭状态 ,反方向的数据流仍然有效。也就是说,这时主机②还可以向主机①发送数据。当主机②也发完数据,它同样发出 FIN 指令,告诉主机①数据流关闭;主机①收到 FIN 并回复 ACK 后,连接就完全关闭了。这就是 TCP 连接关闭的主要步骤,也被形象地称为“四次挥手”。
TCP 报文段格式
由于 TCP 协议位于传输层,它的传输单元一般叫做 TCP段( segment ),也可译为 TCP分组 。当然了,也有不少文献将它笼统地称为 TCP报文 。
三次握手
- MSS 选项。最大分组长度(maximum segment size)就表示一个 TCP 分组最多能够承载的数据量。
- 窗口扩大因子。如果窗口字段表示的范围太小了,我们可用窗口字段大小与穿过扩大因子相乘,该相乘是指将数据左移。
四次挥手
一个 TCP 连接包含两个方向的传输通道,因此需要两对 FIN/ACK 分组,各自负责关闭对应的方向。因此,这两对 FIN/ACK 交互也被形象地称为 四次挥手 。
TCP 建立连接需要三次握手,关闭连接需要四次挥手,步骤相对繁琐。这意味着一个 TCP 连接应该有很多中间状态。
- 客户端发出 SYN 分组,连接进入 SYN_SENT 状态;
- 服务端收到客户端发来的 SYN 分组,它回复 SYN/ACK 分组,连接进入 SYN_RECV 状态;
- 客户端收到服务器的 SYN/ACK 分组,它回复 ACK 分组,连接进入 ESTABLISHED 状态;
- 服务器收到客户端的 ACK 分组,服务端连接也进入 ESTABLISHED 状态;
- 当连接处于 ESTABLISHED 状态时,客户端和服务端可以互相传输数据;
- 时序图中间的数据分组及其后的 ACK 分组,为实验中 SSH 服务向客户机返回自己的版本信息(这部分数据被 telnet 命令直接输出到屏幕中);
- 客户端准备退出时,它通过 FIN 分组通知服务端,连接进入 FIN_WAIT1 状态;
- 服务器收到客户端发来的 FIN 分组,它回复 ACK 分组进行确认,连接进入 CLOSE_WAIT 状态;
- 客户端收到服务器发来的 ACK 分组,连接进入 FIN_WAIT2 状态;
- 这时连接处于半关闭状态,服务器仍可以向客户端发送数据;
- 服务器发完剩余数据后,向客户端发送 FIN 分组,通知客户端关闭连接,服务端连接便进入 LAST_ACK 状态;
- 客户端收到服务器发来的 FIN 分组,回复 ACK 分组进行确认,客户端连接进行 TIME_WAIT 状态;
- 服务端收到 ACK 分组后,连接彻底关闭;
- 由于最后一个 ACK 分组可能会丢,客户端必须在 TIME_WAIT 状态等待一段时间,以便对服务器重传的 FIN 分组进行确认;
其中,可以再看一下的是 TIME_WAIT 状态中的 2MSL时长,MSL 是最大分组寿命( maximum segment lifetime )的简称,即一个 TCP 分组被丢弃前能够在网络中存在的最长时间。
将 TIME_WAIT 状态维持 2MSL 时长是出于这样的考虑:
假设最后一个 ACK 分组刚好在存活时间耗尽前到达对端主机,这时已经过了 MSL 时间。对端收到 ACK 后,就会立即关闭连接,不可能再发送 FIN 分组。但如果对端在收到 ACK 前刚刚重传了 FIN 分组,就必须再经过 MSL 时间才能保证 FIN 分组从网络中消失。因此,连接必须维持 TIME_WAIT 状态 2MSL 时间后才能释放,否则就可能对潜在的新连接造成干扰。
- 如果最后一个 ACK 分组可以到达对端,最多只需要等待 2MSL 时间即可保证网络中没有对端重传的 FIN 分组;
- 如果最后一个 ACK 分组丢失了,对端在 MSL 内已经重传好多次了;
- 如果重传的 FIN 分组有一个可以到达本端,TCP 回复 ACK 后会重置定时器在 TIME_WAIT 继续等待 2MSL 时长;
- 如果重传的 FIN 分组都丢了,说明网络质量很差,再等下去也没有意义了;
主动关闭方一般是客户端,并发一般不高,因此 TIME_WAIT 状态基本不会造成任何影响。如果一个高并发服务(比如 Web 服务)存在大量短连接,则可能留下很多 TIME_WAIT 状态的连接。由于 TIME_WAIT 状态套接字无法立即回收,它们将占用大量的系统资源,对服务的性能造成严重影响。
滑动窗口,TCP的流量控制
-
TCP 协议规定:当接收方收到一个数据后,必须回复 ACK 给发送方。这样发送方就能得到反馈,如果数据发出去后很长时间都没有收到 ACK 确认,说明数据很有可能已经丢失了。TCP 每次发送数据后,都会启动一个定时器。如果定时器超时还没收到对方确认,TCP 就会重新发送数据。
-
由于承载 TCP 报文段的 IP 包是独立路由的,可能走不同的网络路径,无法保证一定按照发送顺序送达目标主机。 TCP 协议需要向上提供连续字节流传输服务,如果报文段错序到达,TCP 必须根据序号重新排列数据。另一方面,数据到达后目标主机后,接收方应用程序可能忙于其他事情,无法及时处理。鉴于这两个点,TCP 接收方需要在内存中准备一个接收缓冲区,用于临时保存数据。
接收缓冲区大小是有限的,如果应用进程处理缓慢,发送方还拼命发送,最终肯定会压垮接收方。因此,当缓冲区有变化时,接收方应该通过 窗口大小 字段,将它的剩余大小告知发送方。
接收方通告的窗口大小通常称为 通告窗口( advertised window ),可缩写为 awnd 。它起到约束发送方发送速度的作用:
- 如果接收方应用进程繁忙,迟迟未读取缓冲区里的数据,那么窗口大小将慢慢变小;
- 当窗口大小降为零,发送方就停止发送新数据;
通过通告窗口,发送方可以实时感知接收方缓冲区的状态,然后根据缓冲区剩余空间动态调整发送速度,这就是 TCP 的 流量控制( flow control )机制。
从数据发送方来说,数据状态的变化就是用滑动窗口来实现的。
TCP 协议规定,接收方收到数据后,必须发送 ACK 进行确认,以此实现可靠传输。然而,就算是一个小小的 ACK 确认,也需要一个完整 TCP 分组报文来承载,开销很大!
众所周知,最小的 TCP 分组包含 20 字节的 TCP 头部和 20 字节的 IP 头部,总共 40 字节。试想发送一个 40 字节的 TCP 报文,仅仅只为了告诉发送方 4 字节的确认号,效率得有多低!有效信息才占 10% !
为了提高传输效率,TCP 实现了 延迟确认( delayed ACK )机制。延迟确认顾名思义就是收到数据不立马确认,而是等上一段时间,跟其他数据一起发送。
拥塞窗口,TCP的拥塞控制
为实现可靠传输,TCP 实现了接收确认机制:当数据发生丢包时,重传数据。当网络链路负载很重,甚至发生拥塞时,丢包就会很频繁。这时如果 TCP 还拼命地重传数据,将进一步压垮网络!
网络拥塞(network congestion)是说,每条网络线路都是有带宽的,如果网络流量超超过带宽,丢包就是不可避免的。
- 维护拥塞窗口,限制数据发送量;
- 根据网络当前状态,实时调节拥塞窗口:
- 如果长时间未收到 ACK 而发生数据重传,说明网络可能拥塞,缩小拥塞窗口,降低发送速度;
- 每收到一个有效 ACK 都增大拥塞窗口,提高发送速度,因为这通常意味着网络状态良好;
TCP 还维护了 拥塞窗口( congestion window ),根据链路的拥塞程度来约束发送速度。
拥塞窗口跟滑动窗口类似,同样规定了发送方此刻能够发送出去的字节数,只不过它通过评估网络链路的拥塞程度,并由一定的算法计算而来的。 $$ W = min(awnd, cwnd) $$ 其中,awnd 为通知窗口大小,cwnd 为拥塞窗口大小。
TCP 学习 cwnd 的算法可以总结为:慢启动、拥塞避免、快速重传和快速恢复。
收到重复的确认,说明发出去的数据发生丢包,意味着网络可能发生拥塞。TCP 同样会将 ssthresh 设为当前拥塞窗口 cwnd 的一半,并重新执行慢启动算法加以应对。
快速重传算法会受到捎带确认机制的制约。试想接收方刚好没有数据要发,因此 ACK 确认被延迟,乃至合并,发送方就不会检测到重复确认。
为了解决这个矛盾,TCP 规定接收方接到乱序数据后就立马发送 ACK 确认。
新版 TCP( Reno )选择跳过慢启动,直接进入拥塞避免阶段,这就是所谓的 快速恢复。
- 将 ssthresh 设为当前窗口的一半;
- 将当前窗口设为 ssthresh ;
- 执行拥塞避免,窗口大小线性扩张;
实际上,在 TCP 检测到网络拥塞时,窗口和 ssthresh 都是立马减半,因而被称为 乘法减小 或者 指数退避 。每次降低一半,这种下降速度其实也是很快的。
DNS:域名系统
域名系统的概述
域名是 网域名称 ( domain name )的简称,它是一串以点号分隔的字符串,用于标识一台或一组计算机。域名可作为 IP 地址的别名,更便于记忆。
实际上,域名是一个分层次的命名空间,各种域名都隶属于根域 .
。位于第一层的域名称为 一级域名 或 顶级域名 ;第二层的域名称为 二级域名 ;以此类推。
以 www.fasionchan.com.
为例,从右往左读依次是:
- 根
- 一级域名(顶级域名):
com
- 二级域名:
fasionchan
- 三级域名:
www
域名注册后,所有人拥有域名的管理权:不仅可以修改域名关联的 IP ,还可以分配子域名。域名 fasionchan.com
被注册后,可以修改它关联的 IP ,还可以随意添加子域名 www.fasionchan.com
。
域名的第一级是 顶级域 ,包括
- 通用顶级域 ,例如
.com
、.net
和.org
等; - 国家和地区顶级域 ,例如
.cn
、.us
等;
通用顶级域 | 含义 |
---|---|
.com | 商业公司 |
.edu | 教育机构 |
.net | 互联网服务供应商 |
.org | 非营利组织、国际机构等 |
国家和地区顶级域 | 含义 |
---|---|
.cn | 中国 |
.hk | 中国香港 |
.mo | 中国澳门 |
.tw | 中国台湾 |
.jp | 日本 |
.us | 美国 |
域名系统 ( domain name system ,简称 DNS ),是互联网提供的一项名字服务。我们可以将 DNS 看作一个分布式数据库,它保存着域名和 IP 的映射关系。
有了这个对应关系,我们就可以通过 域名 ( domain name )来访问网络服务,不用再苦苦记忆 IP 地址。要知道域名 www.fasionchan.com
比 IP 地址 163.181.33.224
好记多了。
域名注册后,所有人可将域名关联的 IP 登记到域名系统。这是一个分布式数据库,以域名为键,以 IP 为值。域名系统提供一些服务器用户查询,这就是 DNS服务器( dns server )。
DNS服务器工作原理
全球域名的最高管理机构是 ICANN ( Internet Corporation for Assigned Names and Numbers ),它是一个总部位于美国加州的组织。ICANN 负责管理整个域名系统的运作,主要工作是规划 顶级域名 ( top level domain ,简写为 TLD )。
顶级域名是域名的第一级,可分为两种:
- 通用顶级域 ,例如
.com
、.net
、.edu
、.org
等等; - 国家地区顶级域 ,例如
.cn
、.hk
、.jp
、.us
等等;
理论上,查询任何域名都需要先查询 ICANN 的根域。因为只有根域才能知道:某个域由谁托管,服务器是哪些。事实上也确实如此,ICANN 维护着一张映射表,记录了每个顶级域名和对应的托管商。
根域名服务器 ( root name server )保存 DNS 的根区列表。
根域名服务器列表可在 root-servers.org 上查询。 Anycast 路由技术:分散在不同地理位置的多台服务器,可以使用相同的 IP 地址。当发送方向这个 IP 地址发送数据时,路由协议会自动选择一个最近的节点。
[root@yikuanzz ~]# dig . NS
; <<>> DiG 9.11.4-P2-RedHat-9.11.4-26.P2.el7_9.15 <<>> . NS
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 24903
;; flags: qr rd ra; QUERY: 1, ANSWER: 13, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;. IN NS
;; ANSWER SECTION:
. 7 IN NS j.root-servers.net.
. 7 IN NS g.root-servers.net.
. 7 IN NS c.root-servers.net.
. 7 IN NS h.root-servers.net.
. 7 IN NS e.root-servers.net.
. 7 IN NS l.root-servers.net.
. 7 IN NS m.root-servers.net.
. 7 IN NS d.root-servers.net.
. 7 IN NS b.root-servers.net.
. 7 IN NS a.root-servers.net.
. 7 IN NS i.root-servers.net.
. 7 IN NS f.root-servers.net.
. 7 IN NS k.root-servers.net.
;; Query time: 0 msec
;; SERVER: 100.100.2.136#53(100.100.2.136)
;; WHEN: Wed Jun 05 14:48:29 CST 2024
;; MSG SIZE rcvd: 433
当我们查询一个域名时,必须从根服务器开始,逐层查询,这就是所谓的 迭代查询 ( iterative query )。
当我们查询一个域名,例如 www.fasionchan.com
时:
- 先查根域名服务器;
- 根域名服务器就 13 台,IP 大家都知道,极少改动;
- 根域名服务器保存根区列表,列表包含顶级域名的托管商,以及相关服务器信息;
- 根域名服务器根据根区列表,告诉我们
.com
顶级域应该找谁查询;
- 根据根服务器返回结果,继续查询负责
.com
解析的服务器,一般叫做顶级域名服务器;- 主域名
fasionchan.com
注册后,需要将负责该域名解析的服务器,登记在.com
顶级域名服务器上; .com
顶级域名服务器根据这个信息,告诉我们fasionchan.com
这个域名应该找谁查询;
- 主域名
- 根据顶级域名服务器返回结果,继续查询负责
fasionchan.com
解析的服务器,一般叫做权威域名服务器;fasionchan.com
子域信息一般都登记在权威服务器上;- 权威服务器取出
www.fasionchan.com
对应记录,并返回给我们,查询结束; - 如果某个子域由其他权威服务器负责,我们还需要继续迭代,直到查询完毕;
一般来说,本地主机会通过本地的 递归解析器 去对网址进行递归,递归解析对客户端来说是完全透明的,客户端完全不用关心递归解析器背后的其他 DNS 服务器。
此外,递归解析器还会将查询结果在本地缓存起来。当域名再次被查询时,它可直接返回缓存结果,无须重新查询其他 DNS 服务器。正因如此,递归解析器通常被称为 DNS缓存服务器 。
DNS 报文格式
DNS 是一个典型的 Client-Server 应用,客户端发起域名查询请求,服务端对请求进行应答。
DNS 一般采用 UDP 作为传输层协议(TCP也行),端口号是 53。请求报文和应答报文均作为数据,搭载在 UDP 数据报中进行传输。
DNS 报文分为 请求 和 应答 两种,结构是类似的,大致分为五部分:
- 头部( header ),描述报文类型,以及其下 4 个小节的情况;
- 问题节( question ),保存查询问题;
- 答案节( answer ),保存问题答案,也就是查询结果;
- 授权信息节( authority ),保存授权信息;
- 附加信息节( additional ),保存附加信息;
- QR 位标记报文是一个查询请求,还是查询应答;
- 0 表示查询请求;
- 1 表示查询应答;
- 操作码(opcode)占 4 位,表示操作类型;
- 0 是标准查询;
- 1 是反向查询;
- 2 是服务器状态请求;
- AA 表示 权威回答(authoritative answer),意味当前查询结果由域名的权威服务器给出;
- TC 表示 截短(truncated),使用 UDP 时,如果应答超过 512 字节,只返回前 512 个字节;
- RD 表示 期望递归(recursion desired),在请求中设置,并在应答中返回;
- 该位为 1 时,服务器必须处理这个请求:如果服务器没有授权回答,它必须替客户端请求其他 DNS 服务器,这也是所谓的 递归查询 ;
- 该位为 0 时,如果服务器没有授权回答,它就返回一个能够处理该查询的服务器列表给客户端,由客户端自己进行 迭代查询 ;
- RA 表示 可递归(recursion available),如果服务器支持递归查询,就会在应答中设置告知客户端;
- 保留位,未来扩展。
- 响应码(response code)表示请求结果;
- 0 表示没有差错;
- 3 表示名字差错,该差错由权威服务器返回,表示待查询的域名不存在;
问题节支持保存多条问题记录,记录条数则保存在 DNS 头部中的问题记录数字段。这意味着,DNS 协议单个请求能够同时查询多个域名,虽然通常只查询一个。
- 待查域名(Name),字段长不固定。
- 查询类型(Type),除了关联 IP 地址,还可以关联其他信息常见类型包括:
- 1 表示 A 记录,即 IP 地址;
- 28 表示 AAAA 记录,即 IPv6 地址;
- 类(Class)通常为 1,表示 TCP/IP 互联网地址;
服务端处理查询请求后,需要向客户端发送应答报文;域名查询结果作为资源记录,保存在答案以及其后两节中.
- 有效期( TTL ),域名记录一般不会频繁改动,所以在有效期内可以将结果缓存起来,降低请求频率;
- 数据长度( Resource Data Length ),即查询结果的长度;
- 数据( Resource Data ),即查询结果;
1、请求报文实例。
2、应答报文实例
附录:补充知识点
Linux 内核 - ioctl
ioctl
是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,如果需要扩展新的功能,通常以增设 ioctl()
命令的方式实现。
# include <sys/ioctl.h>
int ioctl(int fd, int cmd, ...)