Back
Featured image of post 计算机网络学习

计算机网络学习

概述

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 协议在发展的过程中,衍生出 IPv4IPv6 两个不同版本。其中,历史版本 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 应答,这个字段表示请求发起者的地址;

拓展阅读:RFC828

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 头部计算得出;

拓展阅读:RFC 792

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 包中发往目标主机。

拓展阅读:RFC 768

TCP:传输控制协议

TCP 协议简介

UDP 数据报是借助 IP 包提供的点对点传输能力来实现的端到端的传输,然而 IP 协议只是一种“尽力而为”的网络协议,它无法保证 IP 包一定能够送到目标主机。实际上,由于网路链路拥堵或者中间路由设备故障点存在,会有 IP 丢包的现象出现。此外,UDP 协议也没有流量控制机制、也没有拥塞控制机制,当遇到某些情况时是无法解决的。

为了解决 UDP 协议的局限性,一种更高级的传输协议被设计出来了 TCP(Transimission Control Protocol)它是一种 面向连接的流式协议 ,可为应用程序提供 可靠的字节流传输服务

  1. 连接主动发起方(一般是客户端),向被动连接方(一般是服务端)发出 SYN
  2. 被动连接方收到 SYN 后,向主动发起方回复 SYN+ACK
  3. 主动发起方收到 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 分组最多能够承载的数据量。
  • 窗口扩大因子。如果窗口字段表示的范围太小了,我们可用窗口字段大小与穿过扩大因子相乘,该相乘是指将数据左移。

拓展阅读:RFC793

四次挥手

一个 TCP 连接包含两个方向的传输通道,因此需要两对 FIN/ACK 分组,各自负责关闭对应的方向。因此,这两对 FIN/ACK 交互也被形象地称为 四次挥手

TCP 建立连接需要三次握手,关闭连接需要四次挥手,步骤相对繁琐。这意味着一个 TCP 连接应该有很多中间状态。

  1. 客户端发出 SYN 分组,连接进入 SYN_SENT 状态;
  2. 服务端收到客户端发来的 SYN 分组,它回复 SYN/ACK 分组,连接进入 SYN_RECV 状态;
  3. 客户端收到服务器的 SYN/ACK 分组,它回复 ACK 分组,连接进入 ESTABLISHED 状态;
  4. 服务器收到客户端的 ACK 分组,服务端连接也进入 ESTABLISHED 状态;
  5. 当连接处于 ESTABLISHED 状态时,客户端和服务端可以互相传输数据;
  6. 时序图中间的数据分组及其后的 ACK 分组,为实验中 SSH 服务向客户机返回自己的版本信息(这部分数据被 telnet 命令直接输出到屏幕中);
  7. 客户端准备退出时,它通过 FIN 分组通知服务端,连接进入 FIN_WAIT1 状态;
  8. 服务器收到客户端发来的 FIN 分组,它回复 ACK 分组进行确认,连接进入 CLOSE_WAIT 状态;
  9. 客户端收到服务器发来的 ACK 分组,连接进入 FIN_WAIT2 状态;
  10. 这时连接处于半关闭状态,服务器仍可以向客户端发送数据;
  11. 服务器发完剩余数据后,向客户端发送 FIN 分组,通知客户端关闭连接,服务端连接便进入 LAST_ACK 状态;
  12. 客户端收到服务器发来的 FIN 分组,回复 ACK 分组进行确认,客户端连接进行 TIME_WAIT 状态;
  13. 服务端收到 ACK 分组后,连接彻底关闭;
  14. 由于最后一个 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 确认。

新版 TCPReno )选择跳过慢启动,直接进入拥塞避免阶段,这就是所谓的 快速恢复

  • 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 时:

  1. 先查根域名服务器;
    • 根域名服务器就 13 台,IP 大家都知道,极少改动;
    • 根域名服务器保存根区列表,列表包含顶级域名的托管商,以及相关服务器信息;
    • 根域名服务器根据根区列表,告诉我们 .com 顶级域应该找谁查询;
  2. 根据根服务器返回结果,继续查询负责 .com 解析的服务器,一般叫做顶级域名服务器;
    • 主域名 fasionchan.com 注册后,需要将负责该域名解析的服务器,登记在 .com 顶级域名服务器上;
    • .com 顶级域名服务器根据这个信息,告诉我们 fasionchan.com 这个域名应该找谁查询;
  3. 根据顶级域名服务器返回结果,继续查询负责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, ...)
Built with Hugo
Theme Stack designed by Jimmy
© Licensed Under CC BY-NC-SA 4.0