基础篇
Linux 系统是如何收发网络包的?
网络模型
为了使多种设备能通过网络相互通信,和为了解决不同设备在网络互连中的兼容性问题,国际标准化组织制定了开放式系统互连通信参考模型(Open System Interconnection Reference Model),也就是 OSI 网络模型,该模型主要有 7 层,分别是应用层、表示层、会话层、传输层、网络层、数据链路层以及物理层。
事实上,比较常见也比较实用的是四层模型,即 TCP/IP 网络模型,Linux 系统是按照这套网络模型来实现网络协议栈的。
TCP/IP 网络模型共有4层,分别是应用层、传输层、网络层和网络接口层,每一层负责的职能如下:
- 应用层,负责向用户提供一组应用程序,比如 HTTP、DNS、FTP 等;
- 传输层,负责端到端的通信,比如 TCP、UDP 等;
- 网络层,负责网络包的封装、分片、路由、转发,比如 IP、ICMP等;
- 网络接口层,负责网络包在物理网络中的传输,比如网络包的封帧、MAC 寻址、差错检测,以及通过网卡传输网络帧等;
常说的七层和四层负载均衡,是用 OSI 网络模型来描述的,七层对应的是应用层,四层对应的是传输层。
在以太网中,规定了最大传输单元(MTU)是1500
字节,也就是单次传输的最大 IP 包大小,如果超出就会在网络层分片,如果 MTU 越小,需要的分包就越多,网络的吞吐能力也就越差。
Linux 网络协议栈
Linux 网络协议栈的样子类似于 TCP/IP 的四层结构:
- 应用程序需要通过系统调用,来跟 Socket 层进行数据交互;
- Socket 层的下面就是传输层、网络层和网络接口层;
- 最下面的一层,则是网卡驱动程序和硬件网卡设备。
Linux 接收网络包的流程
网卡是计算机里的一个硬件,专门负责接收和发送网络包,当网卡接收到一个网络包后,会通过 DMA 技术,将网络包写入到指定的内存地址,也就是写入到 Ring Buffer,这个是一个环形缓冲区,接着就会告诉操作系统这个网络包已经到达。
最简单的告诉操作系统网络包到达的一种方式是触发中断,但是在高性能网络场景下,网络包的数量会非常多,频繁触发中断会导致 CPU 一直在处理中断,而其他任务可能无法继续前进。
为了解决频繁中断带来的性能开销,Linux 内核引入了 NAPI 机制,它是混合【中断和轮询】的方式来接收网络包,它的核心概念就是不采用中断的方式读取数据,而是首先采用中断唤醒数据接收的服务程序,然后poll
的方法来轮询数据。
因此,当有网络包到达时,会通过 DMA 技术,将网络包写入到指定的内存地址,接着网卡向 CPU 发起硬件中断,当 CPU 收到硬件中断请求后,根据中断表,调用已经注册的中断处理函数。
硬件中断处理函数会做如下的事情:
- 【暂时屏蔽中断】,表示已经知道内存中有数据了,告诉网卡下次再收到数据包直接写内存,不需要中断。
- 接着,发起【软中断】,然后恢复刚才屏蔽的中断。
硬中断由硬件设备触发,向CPU发送一个信号,打断当前的程序执行,触发硬中断处理程序。软中断由软件触发,在软中断处理过程中,CPU仍然处于内核上下文,可以被抢占,这意味着软中断处理过程中可以进行任务调度。
软中断的处理
内核中的 ksoftirqd 线程专门负责软中断的处理,当 ksoftirqd 内核线程收到软中断后,就会来轮询处理数据。
ksoftirqd 线程会从 Ring Buffer 中获取一个数据帧,用 sk_buff 表示,从而可以作为一个网络包交给网络协议栈进行逐层处理。
网络协议栈
首先会进入到网络接口层,在这一层检查报文的合法性,然后找出该网络包的上层协议的类型,比如是 IPv4,还是 IPv6,去掉帧头和帧尾,交给网络层。
到达网络层,取出 IP 包,判断网络包下一步的走向,当确认是发送给本机后,从 IP 头里看一看上一层协议的类型是 TCP 还是 UDP,接着去掉 IP 头,交给传输层。
传输层取出 TCP 头或 UDP 头,根据四元组【源 IP、源端口、目的 IP、目的端口】作为标识,找出对应的 Socket,并把数据放到 Socket 的接收缓冲区。
最后,应用层程序调用 Socket 接口,将内核的 Socket 接收缓冲区的数据【拷贝】到应用层的缓冲区,然后唤醒用户进程。
一个网络包的接收过程就是这样。
Linux 发送网络包的流程
发送网络包和接收网络包流程相反。
首先应用程序会调用 Socket 发送数据包的接口,系统调用会从用户态陷入到内核态中的 Socket 层,内核会申请一个内核态的 sk_buff 内存,将用户待发送的数据拷贝到 sk_buff 内存,并将其加入到发送缓冲区。
接下来,网络协议栈从 Socket 发送缓冲区取出 sk_buff,并按照 TCP/IP 协议栈从上到下逐层处理。
如果使用的是 TCP 传输协议发送数据,那么先拷贝一个新的 sk_buff,这是因为 TCP 协议支持丢失重传,而网卡在发送完成的时候会释放 sk_buff,因此在收到 ACK 之前,这个 sk_buff 不能被删除。所以内核的做法是每次调用网卡发送的都是一个拷贝,等到收到 ACK 再真正删除。
接着,对 sk_buff 填充 TCP 头。sk_buff 可以表示各个层的数据包,在应用层数据包叫 data,在 TCP 层称为 segment,在 IP 层叫 packet,在数据链路层称为 frame。
在层级之间传递数据时,不发生拷贝,只用 sk_buff 一个结构体来描述所有的网络包,这是通过调整 sk_buff 中 data
的指针做到,比如:
- 当接收报文时,从网卡驱动开始,通过协议层层往上传送数据报,通过增加 sk_buff->data 的值,来逐步剥离协议首部。
- 当要发送报文时,创建 sk_buff 结构体,数据缓存区的头部预留足够的空间,用来填充各层首部,在经过各下层协议时,通过减少 sk_buff->data 的值来增加协议首部。
相当于使用 sk_buff->data 这个指针指向各层的首部,就无需拷贝结构体,而是直接从data
指针指向的位置开始填充或者剥离
以下是发送报文时,data指针的移动过程:
至此,传输层的工作也就完成了。
然后交给网络层,在网络层里会做这些工作:选取路由(确认下一跳的 IP)、填充 IP 头、netfilter 过滤、对超过 MTU 大小的数据包进行分片。处理完这些工作后会交给网络接口层处理。
网络接口层会通过 ARP 协议获得下一跳的 MAC 地址,然后对 sk_buff 填充帧头和帧尾,接着将 sk_buff 放到网卡的发送队列中。
这一系列工作准备好后,会触发【软中断】告诉网卡驱动程序有新的网络包需要发送,驱动程序会从发送队列中读取 sk_buff,将这个 sk_buff 挂到 RingBuffer 中,接着将 sk_buff 数据映射到网卡可访问的内存 DMA 区域,最后触发真实的发送。
当数据发送完成后,网卡设备会触发一个硬中断来释放内存,主要是释放 sk_buff 内存和清理 RingBuffer 内存。
最后,当收到这个 TCP 报文的 ACK 应答时,传输层就会释放原始的 sk_buff。
发送网络数据的时候,涉及几次内存拷贝操作
第一次,调用发送数据的系统调用的时候,内核申请一个内核态的 sk_buff 内存,将用户待发送的数据拷贝到 sk_buff 内存,并将其加入到发送缓冲区。
第二次,在使用 TCP 传输协议的情况下,从传输层进入网络层的时候,每一个 sk_buff 会被克隆一个新的副本。目的是为了实现 TCP 的可靠传输。
第三次,当 IP 层发现 sk_buff 大于 MTU 时才需要进行。会再申请额外的 sk_buff,并将原来的 sk_buff 拷贝为多个小的 sk_buff。
TCP/IP 网络模型有哪几层?
1. 为什么要有 TCP/IP 网络模型?
对于不同设备上的进程间通信,需要网络通信,而设备是多样性的,所以要兼容多种多样的设备,因此协商出了一套通用的网络协议。
2. 应用层
最上层能直接接触到的是应用层,只需要专注于为用户提供应用功能(HTTP、FTP、Telnet、DNS、SMTP),不必关心数据是如何传输的。应用层工作在操作系统的用户态,当两个不同设备的应用需要通信时,应用就把应用数据传给下一层,也就是传输层,传输层及以下是工作在内核态。
3. 传输层
传输层是为应用层提供网络支持的,在传输层会有两个传输协议:
- TCP(传输控制协议 Transmission Control Protocol),大部分应用使用的是这个传输层协议,比如 HTTP 应用层协议。
- UDP,简单的只负责发送数据包,不保证数据包能否抵达对方。
TCP 相比 UDP 多了很多特性,比如流量控制、超时重传、拥塞控制等。这些都是为了保证数据包能可靠地传输给对方。
应用需要传输的数据可能会非常大,直接传输不好控制,因此当传输层的数据包大小超过 MSS(TCP 最大报文段长度),需要将数据包分块。在 TCP 协议中,把每个分块称为一个 TCP 段(TCP Segment)。
当设备作为接收方时,传输层则要负责把数据包传给应用,但是一台设备上可能会有很多应用在接收或者传输数据,因此需要 端口 编号将应用区分开来。(比如 80 端口通常是 Web 服务器用的,22 端口通常是远程登陆服务器用的。而对于浏览器(客户端)中的每个标签栏都是一个独立的进程,操作系统会为这些进程分配临时的端口号)。传输层的报文携带端口号后,接收方可以识别出该报文是发送给哪个应用。
4. 网络层
传输层协议只是服务号应用,让其作为应用间数据传输的媒介,而实际的传输功能是下一层,网络层。
网络层最常使用的是 IP 协议(Internet Protocol),IP 协议会将传输层的报文作为数据部分,再加上 IP 包头组装成 IP 报文,如果 IP 报文大小超过 MTU(以太网中一般为 1500 字节)就会再次进行分片,得到一个即将发送到网络上的 IP 报文。
网络层需要有区分设备的编号,一般用 IP 地址给设备进行编号,对于 IPv4 协议,IP 地址共32位,分成了四段(比如,192.168.100.1),每段是 8 位。这虽然做到了区分设备,但是寻址起来特别麻烦。
因此,需要将 IP 地址分成两种意义:
- 一个是网络号,负责标识该 IP 地址是属于哪个 【子网】的。
- 一个是主机号,负责表示同一【子网】下的不同主机。
这需要配合子网掩码才能算出 IP 地址的网络号和主机号。
比如 10.100.122.0/24,后面的/24
表示就是255.255.255.0
子网掩码(这个的二进制是11111111-11111111-11111111-00000000
,共24个1,CIDR 无类别域间路由中的简化表示为/24
)。子网掩码必须由连续的1开始,然后是0,以确保网络地址和主机地址的正确划分。
将 IP 地址和子网掩码按位与计算,即只有都为1时结果才为1,就可以得到网络号
IP 地址:10.100.122.2
00001010.01100100.01111010.00000010
子网掩码:255.255.255.0
11111111.11111111.11111111.00000000
按位与得到网络地址:
00001010.01100100.01111010.00000000
IP 地址 - 网络地址得到主机号:
00000000.00000000.00000000.00000010
在寻址的过程中,先匹配到相同的网络号(表示要找到同一个子网),然后找对应的主机。
除了寻址能力,IP 协议还有另一个重要的能力就是路由。实际场景中,两台设备是通过很多网关、路由器、交换机等众多网络设备连接起来的,就会形成很多条网络的路径,因此当数据包到达一个网络节点,就需要通过路由算法决定下一步走哪条路径。
路由器寻址工作中,就是要找到目标地址的子网,找到后进而把数据包转发给对应的网络内。
也就是说,路由器根据 IP 协议的寻址作用,进行路由,一看到 IP 地址的网络号就可以进行转发。到达网络后,交换机或者其他设备会根据 MAC 进行下一步转发。
5. 网络接口层
生成了 IP 头部之后,接下来要交给网络接口层(Link Layer)在 IP 头部的前面加上 MAC 头部,并封装成数据帧(Data frame)发送到网络上。
MAC 头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息,以太网在判断网络包目的地时和 IP 的方式不同,因此必须采用这种方式进行通讯,可以通过 ARP(ARP允许设备通过广播询问“谁拥有IP地址X,请告诉我你的MAC地址”,目标设备会响应其MAC地址。) 协议获取对方的 MAC 地址。
MAC 地址是网络接口卡的物理地址,也称为硬件地址或以太网地址,它是一个全球唯一的标识符,用于局域网内的设备识别,而主机号是逻辑层面的地址,用于网络层的设备识别。在数据包从网络传输到主机的过程中,首先使用 IP 地址(包括主机号)在网络间进行路由,到达目标网络后,再使用 MAC 地址在局域网内进行精确的设备定位和数据传输。
键入网址到网页显示,期间发生了什么?
1. 浏览器做的第一步工作是解析 URL
从而生成发送给 Web 服务器的请求信息,举例:
http://www.server.com/dir1/file1.html
协议+//+服务器名称+数据源的路径名,这个例子的 URL 实际上是请求服务器里的文件资源。
当没有路径名时,就代表访问根目录下事先设置的默认文件。对 URL 进行解析之后,浏览器确定了 Web 服务器和文件名,然后根据这些信息来生成 HTTP 请求信息:
2. 真实地址查询——DNS
通过浏览器解析 URL 并生成 HTTP 消息后,需要委托操作系统将消息发送给 Web 服务器。
在发送之前,需要查询服务器域名对应的 IP 地址,有一种服务器专门保存了 Web
服务器域名与 IP
的对应关系,它就是 DNS
服务器,起到通讯录的作用。
DNS 中的域名都是用句点来分隔的,比如www.server.com
,这里的句点代表了不同层次之间的界限,在域名中,越靠右的位置表示其层级越高,实际上域名有最后一个点www.server.com.
,.
是根域名在最顶层,它的下一层是.com
顶级域,在下面是二级域.server
,然后是子域www
。
所以域名的层级结构类似于一个树状结构,根域的 DNS 服务器信息保存在互联网中所有的 DNS 服务器中,这样一来,客户端只要能够找到任意一台 DNS 服务器,就可以通过它找到根域 DNS 服务器,然后再找到位于下层的某台目标 DNS 服务器。
- 域名解析的工作流程
客户端首先会发出一个 DNS 请求,问 www.server.com
的 IP 是什么,并发给本地 DNS 服务器(客户端 TCP/IP 设置中填写的 DNS 服务器地址)。
本地域名服务器在收到客户端的请求后,如果缓存里的表格能找到 www.server.com
,则它直接返回 IP 地址。如果没有,本地 DNS 会去问它的根域名服务器。
根 DNS 收到来自本地 DNS 的请求后,发现后置是 .com
,然后给到 .com
顶级域(TLD)服务器地址。
本地 DNS 收到 .com
地址后,去询问,之后又得到了 server.com.
的域名服务器地址,负责server.com
的权威DNS服务器包含了server.com
及其子域(如www.server.com)的详细记录。
本地 DNS 将查询后的 IP 地址返回客户端,客户端和目标建立连接。
在每次解析域名时,浏览器会先查看自身有没有对这个域名的缓存,如果没有,就去问操作系统,操作系统也会去查看自己的缓存,如果没有,就去 hosts 文件看,也没有,才会去问本地 DNS 服务器。DNS服务器
3. 指南好帮手——协议栈
通过 DNS 获取到 IP 后,就可以把 HTTP 的传输工作交给操作系统的协议栈。协议栈是操作系统的一部分,是一组实现网络通信协议的软件组件,它定义了数据在网络中的传输方式。
协议栈的内部分为几个部分,分别承担不同的工作。上下关系有一定的规则,上面的部分会向下面的部分委托工作,下面的部分收到委托的工作并执行。
应用程序(浏览器)通过调用 Socket 库。来委托协议栈工作。协议栈的上半部分为两块,分别是负责收发数据的 TCP 和 UDP 协议,这两个传输协议会接受应用层的委托执行收发数据的操作。
协议栈的下面一半是用 IP 协议控制网络包收发操作。此外 IP 中还包括 ICMP
协议和 ARP
协议。
ICMP
用于告知网络包传送过程中产生的错误以及各种控制信息。ARP
用于根据 IP 地址查询相应的以太网 MAC 地址。
IP 下面的网卡驱动程序负责控制网卡硬件,而最下面的网卡则负责完成实际的收发操作,也就是对网线中的信号执行发送和接收操作。
4. 可靠传输——TCP
HTTP 是基于 TCP 协议传输的,TCP 报文头部的格式:
首先,源端口号和目标端口号,为了指示数据应该发给哪个应用;接下来有包的序号,这是为了解决包乱序的问题;还有确认号,目的是解决丢包问题,如果没有收到就应该重新发送直至送达;
还有一些状态位,例如 SYN
是发起一个连接,ACK
是回复,RST
是重新连接,FIN
是结束连接等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。
还有一个窗口大小。TCP 要做流量控制,通信双方各声明一个窗口(缓存大小),标识自己当前能够的处理能力。
TCP 还会做拥塞控制,对于真正的堵塞它无能为力,但可以控制自己发送的速度。
TCP 传输数据之前,要先三次握手建立连接
在 HTTP 传输数据之前,首先需要 TCP 建立连接,TCP 连接的建立,通常称为三次握手:
- 一开始,客户端和服务端都处于
CLOSED
状态。先是服务器主动监听某个端口,处于LISTEN
状态。 - 然后客户端主动发起连接
SYN
,之后处于SYN-SENT
状态。 - 服务端收到发起的连接,返回
SYN
,并且ACK
客户端的SYN
,之后处于SYN-RCVD
状态。 - 客户端收到服务端发送的
SYN
和ACK
之后,发送对SYN
确认的ACK
,之后处于ESTABLISHED
状态,因为它一发一收成功了。 - 服务端收到
ACK
的ACK
之后,处于ESTABLISHED
状态,因为它也一发一收了。
三次握手的目的是确保双方都有能力发送和接收数据,第一次握手客户端发送SYN
包给服务器,进入SYN-SENT
状态,第二次握手服务器发送SYN-ACK
包作为SYN
的响应,进入SYN-RCVD
状态,第三次握手客户端发送ACK
包作为SYN-ACK
包的响应,以确认连接已经建立,此时客户端和服务端都处于ESTABLISHED
状态。
TCP 的连接状态查看,在 Linux 可以通过 netstat -napt
命令
TCP 分割数据
如果 HTTP 请求消息比较长,超过了 MSS
的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据发送,而不是一次性发送所有数据。
数据会被以MSS
的长度为单位进行拆分,拆分出来的每一块数据都会被放进单独的网络包中。也就是在每个被拆分的数据加上 TCP 头信息,然后交给 IP 模块来发送数据。
TCP 报文生成
TCP 协议里面会有两个端口,一个是浏览器监听的端口(通常是随机生成的),一个是 Web 服务器监听的端口(HTTP 默认端口号是80
,HTTPS 默认端口号是443
)。
在双方建立了连接后,TCP 报文中的数据部分就是存放 HTTP 头部 + 数据,组装好 TCP 报文之后,就交给下面的网络层处理。
至此,网络包的报文如图:
建立连接以后,TCP 头部会被添加到 HTTP 消息中,HTTP 报文时应用层生成的 HTTP 头部和消息体,这部分如果超过了 MSS 的长度,这时 TCP 就需要把 HTTP 的数据拆解成一块块的数据分别添加 TCP 头部。
5. 远程定位——IP
TCP 模块在执行连接、收发、断开等各阶段操作时,都需要委托 IP 模块将数据封装成网络包发送给通信对象。
IP 包头格式
在 IP 协议里面需要有源地址 IP和目标地址 IP
- 源地址 IP,即是客户端输出的 IP 地址;
- 目标地址,即通过 DNS 域名解析得到的 Web 服务器 IP。
因为 HTTP 是经过 TCP 传输的,所以在 IP 包头的协议号,要填写为06
(十六进制),表示协议为 TCP。
当客户端存在多个网卡时,会有多个 IP 地址,这个时候需要根据路由表规则,来判断哪一个网卡作为源地址 IP。
在 Linux 操作系统,我们可以使用 route -n
命令查看当前系统的路由表。
假设 Web 服务器的目标地址是 192.168.10.200
。
- 首先和第一条目的子网掩码进行与运算,得到结果
192.168.10.0
,但第一个条目的Destination
是192.168.3.0
,所以匹配失败。 - 再和第二个条目的子网掩码进行运算,得到的结果匹配成功,所以将使用
eth1
网卡的 IP 地址作为 IP 包头的源地址。 - 如果都没有匹配成功,那么使用第三个条目的这种默认网关,目标地址和掩码都是
0.0.0.0
,并且后续就把包发给路由器,Gateway
即是路由器的 IP 地址。
相当于,操作系统查看路由表决定哪个网络接口(网卡)最适合用于到达目标 IP 地址,如果目标 IP 和某个网卡的 IP 在同一子网内,那么这个网卡可能被优先选择,否则,使用默认网关,配置为负责转发到其他网络的路由器的 IP 地址。
至此,网络包的报文如图:
6. 两点传输——MAC
生成了 IP 头部之后,接下来网络包还需要在 IP 头部的前面加上 MAC 头部。
MAC 包头格式
MAC 头部是以太网使用的头部,它包含了接收方和发送方的 MAC 地址等信息。在 MAC 包头里需要发送方 MAC 地址和接收方 MAC 地址,用于两点之间的传输。
一般在 TCP/IP 通信里,MAC 包头的协议类型只使用:
0800
:IP 协议0806
:ARP 协议
MAC 地址是在网卡生产时写入到 ROM 里的,发送方的 MAC 地址只需要把这个值读取出来写入到 MAC 头部就可以,而接收方的 MAC 地址首先需要清楚这个包发给谁,这个使用上面说的路由表即可找到,然后使用ARP
协议帮我们找到路由器的 MAC 地址,ARP 协议会在以太网中以广播的形式,对以太网所有的设备询问,如果对方和自己处于同一个子网中,那么通过上面的操作可以得到对方的 MAC 地址。
在后续操作系统会把本次查询结果放到一块叫做ARP缓存的内存空间留着以后用,缓存时间几分钟。
所以在发包时:首先查询 ARP 缓存,当不存在对方的 MAC 地址时,则发送 ARP 广播查询。
跨局域网通信依赖于网络层的 IP 协议,而局域网内的通信使用的是 MAC 地址,因此 MAC 地址在各自的局域网是唯一的,避免发送到错误的设备,而 IP 地址在逻辑上是唯一的,因此对应唯一的 MAC 地址。而端口号是区分不同服务和应用程序的。
至此,网络包的报文如下图:
7. 出口——网卡
网络包只是存放在内存中的一串二进制数字信息,没有办法直接发送给对方。因此,需要将数字信息转换为电信号,才能在网线上传输。
负责执行这一操作的是网卡,通过网卡驱动程序控制网卡。
网卡驱动获取网络包之后,会将其复制到网卡内的缓存区中,接着会在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列。
- 起始帧分界符是一个用来表示包起始位置的标记
- 末尾的
FCS
(帧校验序列)用来检查包传输过程是否有损坏
最后网卡会将包转为电信号,通过网线发送出去。
8. 送别者——交换机
交换机的设计是将网络包原样转发到目的地。交换机工作在 MAC 层,也称为二层网络设备。
交换机的包接收操作
首先,电信号到达网线接口,交换机里的模块进行接收,将电信号转换为数字信号。
然后通过包末尾的FCS
校验错误,如果没问题则放到缓冲区,这部分操作基本和计算机的网卡相同,但交换机的工作方式和网卡不同。
计算机的网卡本身具有 MAC 地址,并通过核对收到的包的接收方 MAC 地址判断是不是发给自己的,如果不是则丢弃;相对地,交换机的端口不核对接收方 MAC 地址,而是直接接收所有的包并存放到缓冲区中。因此,和网卡不同,交换机的端口不具有 MAC 地址。
交换机的 MAC 地址表主要包含两个信息:
- 一个是设备的 MAC 地址
- 另一个是该设备连接在交换机的哪个端口上
交换机根据 MAC 地址表查找 MAC 地址,然后将信号发送到相应的端口。
如果地址表中找不到指定的 MAC 地址,这是因为具有该地址的设备一段时间没有向交换机发送过包,那么交换机会将包转发到除了源端口之外的所有端口,然后只有相应的接收者才接受包,其他设备则会忽略这个包。
此外,如果接收方 MAC 地址是一个广播地址,那么交换机会将包转发到除了源端口之外的所有端口。
以下两个属于广播地址:
- MAC 地址中的
FF:FF:FF:FF:FF:FF
- IP 地址中有
255.255.255.255
9. 出境大门——路由器
网络包经过交换机之后,到达了路由器,并在此被转发到下一个路由器或目标设备。
这一步转发的工作原理和交换机类似,也是通过查表判断包转发的目标。
不过在具体的操作过程上,路由器和交换机是有区别的。
- 因为路由器是基于 IP 设计的,俗称三层网络设备(网络层),路由器的各个端口都具有 MAC 地址和 IP 地址;
- 而交换机是基于以太网设计的,俗称二层网络设备(网络接口层或数据链路层),交换机的端口并不具有 MAC 地址。
这里的“X层”网络设备是OSI模型的叫法,OSI(开放系统互连)模型是一个七层模型,物理层、数据链路层,网络层,传输层,会话层,表示层和应用层,一般不会关注第五和第六层,其功能被集成到第四层或第七层设备中。这种叫法是历史遗留问题。
路由器的端口具有 MAC 地址,因此它就能够成为以太网的发送方和接收方;同时还具有 IP 地址,从这个意义上来说,它和计算机的网卡是一样的。
当转发包时,首先路由器端口会接收发给自己的以太网包,然后路由表查询转发目标,再由相应的端口作为发送方将以太网包发送出去。
路由器的包接收操作
路由器的转换模块的主要功能是实现不同网络接口之间的协议转换,而不单纯像交换机一样将电信号转换成数字信号
如果包没问题则检查 MAC 头部中的接收方 MAC 地址,如果是就放到接收缓冲区,否则就丢弃。
查询路由表确定输出端口
完成包接收操作之后,路由器就会去掉包开头的 MAC 头部。
MAC 头部的作用就是将包送达路由器,其中的接收方 MAC 地址就是路由器端口的 MAC 地址。因此,当包到达路由器之后,MAC 头部的任务就完成了,于是 MAC 头部就会被丢弃。
接下来,路由器会根据 MAC 头部后方的 IP
头部中的内容进行包的转发操作。
转发操作分为几个阶段,首先是查询路由表判断转发目标。
假设地址为10.10.1.101
的计算机要向地址为192.168.1.100
的服务器发送一个包,这个包先到达图中的路由器。
根据包的接收方 IP 地址查询路由表中的目标地址栏,路由匹配和之前一样,每个条目的子网掩码和192.168.1.100
IP 做与运算后,得到的结果与对应条目的目标地址相匹配,如第二条目的目标地址192.168.1.0
和与运算后的结果匹配,那么第二条目会被作为转发目标。
如果找不到匹配路由,会选择默认路由。
路由器的发送操作
接下来就会进行包的发送操作。
首先,我们需要根据路由表的网关列判断对方的地址。
- 如果网关是一个 IP 地址,则这个 IP 地址就是我们要转发到的目标地址,还未抵达终点,还需继续需要路由器转发。
- 如果网关为空,则 IP 头部中的接收方 IP 地址就是要转发到的目标地址,也是就终于找到 IP 包头里的目标地址了,说明已抵达终点。
见上面的路由表,网关是一个网络节点,充当两个不同网络或子网之间的桥梁,网关可以是一个路由器、一个服务器或者是一个有路由功能的计算机,当一个设备需要发送数据到另一个网络时,它会将数据发送到网关,然后由网关负责将数据转发到目的地。
知道对方的 IP 地址之后,接下来需要通过 ARP
协议根据 IP 地址查询 MAC 地址,并将查询的结果作为接收方 MAC 地址。
接下来是发送方 MAC 地址字段,这里填写输出端口的 MAC 地址。还有一个以太类型字段,填写0800
(十六进制)表示 IP 协议。
网络包完成后,接下来会将其转换成电信号并通过端口发送出去。
发送出去的网络包会通过交换机到达下一个路由器。由于接收方 MAC 地址就是下一个路由器的地址,所以交换机会根据这一地址将包传输到下一个路由器。
接下来,下一个路由器会将包转发给再下一个路由器,经过层层转发之后,网络包就到达了最终的目的地。
在网络包传输的过程中,源 IP 和目标 IP 始终是不会变的,一直变化的是 MAC 地址,因为需要 MAC 地址在以太网内进行两个设备之间的包传输。
10. 互相扒皮——服务器与客户端
数据包抵达服务器后,服务器会按顺序查看包结构,首先查看数据包的 MAC 头部是否和服务器自己的 MAC 地址符合,然后看数据包的 IP 头,根据 IP 头中协议项得知协议,然后查看 TCP 头,检查序列号,正确就放入缓存中然后返回 ACK。查看到端口号,HTTP 的服务器正在监听这个端口号,于是就将包发给 HTTP 进程。服务器的 HTTP 进程看到,原来这个请求是要访问一个页面,于是就把网页封装在 HTTP 响应报文里。
HTTP 响应报文也需要加上 TCP、IP、MAC 头部,源地址变成了服务器 IP 地址,目的地址是客户端 IP 地址。然后从网卡出去,交给交换机转发到路由器,路由器经过层层转发到客户端对应的路由器,再由交换机转发到客户端。客户端收到数据包之后开始层层拆分,得到 HTTP 响应报文后交给浏览器去渲染页面。
最终 TCP 断开连接,双方都可以主动断开连接,四次挥手过程如下:
- 客户端打算关闭连接,此时会发送一个 TCP 首部
FIN
标志位被置为1
的报文,也即FIN
报文,之后客户端进入FIN_WAIT_1
状态。 - 服务端收到该报文后,就向客户端发送
ACK
应答报文,接着服务端进入CLOSE_WAIT
状态。 - 客户端收到服务端
ACK
应答报文后,进入FIN_WAIT_2
状态。 - 等待服务端处理完数据后,也向客户端发送
FIN
报文,进入LAST_ACK
状态。 - 客户端收到服务端的
FIN
报文后,回一个ACK
应答报文,之后进入TIME_WAIT
状态。 - 服务端收到了
ACK
应答报文后,就进入了CLOSE
状态,至此服务端已经完成连接的关闭。 - 客户端在经过
2MSL
一段时间后,自动进入CLOSE
状态,至此客户端也完成连接的关闭。
相当于有两个 FIN 和 两个 ACK 包,因此通常称为四次挥手,而三次握手是 SYN 包,SYN-ACK 包和 ACK 包,三个独立的数据包交换,因此通常称为三次握手。