数据包历险记
每天多学一点:一个数据包从发出到被接收一路究竟是怎么过来的
从“当键入网址后,到网页显示,其间究竟发生了什么” 开始说起
总的来说,大致可以分为以下几个步骤:
- 浏览器解析
URL
,生成HTTP
报文 - 通过
DNS
查询真实的IP
地址 - 使用
TCP/IP
协议栈对数据包进行切割、打包、封装 - 利用
MAC
地址确定收发两点的终始点 - 通过网卡讲数据包从数字信号转换为电信号,通过网线传输到交换机/路由器
- 通过路由器将数据包转发到公网下一个节点
- 通过层层转发,数据包到达服务器
- 服务器开始对数据包进行解包,取出数据,生成响应
接下来,对每个步骤进行详细的拆解,拿着放大镜,一起好好剖析数据包一路上的所见所闻
应用层
HTTP
第一步,浏览器解析用户输入的
URL
,生成HTTP
请求报文:
第二步,生成的 HTTP
消息需要委托操作系统发送给服务器,发送之前,需要根据服务器域名,查询出对应的 IP
地址,这时候,需要用到 DNS
服务器来完成 HOST -> IP
的转换
DNS
域名解析的工作过程:
传输层 & 网络层
协议栈
通过
DNS
获取到IP
后,数据包的传输工作将交给协议栈进行下一步的处理
应用程序通过调用 Socket
库,将数据包委托给协议栈进行处理
协议栈分为上下两部分:
- 上部分负责收发数据的
TCP/UDP
协议 - 下部分利用IP协议控制网络数据包的收发操作,包括:
ICMP
用于反馈数据包传输过程中产生的错误及负责各种控制信息ARP
根据IP地址查询相应的以太网MAC地址
IP
下层的网卡驱动负责控制网卡硬件,网卡硬件负责实际的收发操作,完成数据包 数字信号 <---> 电信号
的转换
TCP(传输层)
TCP
报文格式:
- 源端口号和目标端口号:定位收发应用
- 序号: 解决包乱序的问题
- 确认号:解决丢包问题
- 状态位:
- URG:紧急指针(urgent pointer)有效。
- ACK:确认序号(acknowledgment number)有效。
- SYN:发起一个新连接。
- RST:重置连接。
- PSH:接收方应该尽快将这个报文交给应用层,而不是等到整个缓冲区都填满了再交付。
- FIN:发送方已经没有数据要发送了。
TCP
是面向连接的,带状态位的包的收发,会引起双方状态变更
- 窗口大小:用于流量控制和拥塞控制
TCP
三次握手
建立 TCP
连接实质是双方维护一个状态机,在连接建立的过程中,双方的状态变化时序图如下:
- 初始,ClIENT 与 SERVER 都处于
CLOSED
状态 - 服务器首先开始监听某个端口,此时处于
LISTEN
状态 - CLIENT 主动发起连接
SYN
, 之后处于SYN_SENT
状态 - SERVER 返回一个
SYN
, 并且ACK
回复 CLIENT 的SYN
, 之后处于SYN_RECV
状态 - CLIENT 收到 SERVER 的
SYN
和ACK
后,ACK
回复 CLIENT 的ACK
,然后处于ESTABLISHED
状态,因为此时已经确认一发一收成功 - SERVER 收到 CLIENT 回复的
ACK
之后,也处于ESTABLISHED
,因为此时也已确认一发一收成功
三次握手的目的是确保双发都有收发数据的能力
TCP
数据分割
MTU
: Maximum Transmission Unit,物理层最大传输单元,以太网中一般为 1500
字节
MSS
: Max Segment Size,除去 IP
和 TCP
报文头部之后,一个网络包能容纳的TCP数据的最大长度。即,TCP
协议单次能够运载的最大数据量
FCS
: Frame Check Sequence,帧校验序列
如果 HTTP
报文数据超过了 MSS
的长度,这时 TCP
会将报文以 MSS
长度切割成一块块的数据包分开发送。每一块数据包都会拼接 TCP
与 IP
头信息,交由 IP
协议来发送数据。
双方建立连接后,TCP
报文中的数据部分就是存放 HTTP
报文的部分,组装好 TCP
报文之后,就交由网络层进行进一步处理
IP协议簇 (网络层)
TCP
协议在执行连接、收发、断开等操作时,都需要委托 IP
协议将数据封装成网络包发送给通信接收方
IP
报文格式:
- 源地址IP:客户端出口
IP
地址 - 目标地址:通过
DNS
域名解析得到的服务器的IP
一个设备可能不仅仅有一张网卡,当设备拥有多个网卡时,源地址IP应该填写哪个地址呢?
这时候需要根据路由表规则,来判断哪一个网卡作为源地址IP
假设系统环境是 Linux
环境,使用 route -n
命令查看当前系统的路由表
可以看出,第一条记录为默认网关, 当所有地址匹配不到时,会将数据包发送到网关 10.0.20.1
举个栗子,根据上面的路由表,假设目标地址为 172.18.58.3
- 首先,先和第2条记录的子网掩码
255.255.252.0
进行与运算, 得到结果为172.18.56.0
与Destination
的10.0.20.0
不匹配,则继续匹配下一条记录 - 与第3条记录的子网掩码
255.255.0.0
做与运算,得到结果为172.18.0.0
与Destination
的172.17.0.0
不匹配,则继续匹配下一条记录 - 与第4条记录的子网掩码
255.255.0.0
做与运算,得到结果为172.18.0.0
与Destination
的172.18.0.0
匹配成功,所以将使用docker0
网卡的IP地址作为IP包头的源地址
数据链路层 & 物理层
MAC & ARP(数据链路层)
MAC
头部是以太网使用的头部,包含了收发双方的MAC地址等信息
一般在 TCP/IP
通信里,MAC包头的协议类型只使用:
0800
:IP
协议0806
:ARP
协议
MAC
收发双方确认过程:
发送方的MAC地址是在网卡生产时写入到 ROM
里的,只需要将这个值读取出来写入到MAC头部即可
接收方的MAC地址获取需要根据上面得到的目标地址 IP
,查询到对应的 MAC
地址,写入到MAC头部
根据
IP
获取设备的MAC
地址
此时需要通过 ARP
协议找到路由器的 MAC
地址,ARP
协议会在以太网中以广播的形式,发送查询 MAC
地址的数据包,如果有设备应答,则将设备的MAC地址写入到MAC头部
至此,就完成了 MAC
头部的构造,后续操作系统会将查询结果存放到 ARC
缓存,缓存时间为几分钟
在发包时:
- 先查询
ARP
缓存,如果命中,则无需发送ARP广播,直接使用缓存中的MAC地址 - 当缓存Miss时,则发送ARP广播查询
在
Linux
系统中,可以使用arp -a
命令来查看ARP
缓存中的内容
网卡(数据链路层 & 物理层)
IP
协议生成的数据包只是存放在内存中的二进制数字信息,无法直接跨设备传送。因此,需要将进行 数字信息 -> 电信号
的转换,才能在网线中传输
这一过程由操作系统控制驱动程序操作网卡完成,网卡驱动从 IP
模块获取到数据包之后,将其复制到网卡的缓存区中,在其开头加上报头和起始帧分界符,在末尾加上用于检测错误的帧校验序列
- 起始帧分界符是一个用来表示包起始位置的标记
- 末尾的
FCS
用来检查包传输过程中是否有损坏
最后网卡会将数据包转化成电信号,通过网线发送出去
交换机(数据链路层)
交换机的设计是将网络包原封不动的转发到目的地
- 首先,对到达网口的电信号进行接收并转换为数字信号
- 通过包末尾的
FCS
校验错误,通过则放到缓冲区
计算机的网卡本身具有MAC地址,并通过核对收到的包接收方的MAC地址判断是否是发给自己的,不是发给自己的包则丢弃;相比较,交换机的端口不核对接收方的MAC地址,而是直接接收所有的包并放到缓存区中。因此,与网卡不同,交换机的端口不具有MAC地址
将包存入缓冲区后,需要查询这个包的接收方MAC地址是否已经在MAC地址表里有记录了,交换机MAC地址表主要包含两个信息:
- 设备的MAC地址
- 该设备连接在交换机的哪个端口上
交换机内部有一张MAC地址与网线端口的映射表。当接收到包时,会将相应的端口号和发送MAC地址写入表中
当交换机根据MAC地址表查询MAC地址,然后将信号发送到对应的端口。若地址表中找不到指定的MAC地址,可能是:
- 具有该地址的设备还没有向交换机发送过包
- 该设备一段时间未上线导致地址从地址表中被移除了
这种情况下,交换机无法判断应该将包转发到哪个端口,此时会将包转发到除了源端口外的所有端口上,只有相应的接收者才会收到包,其他设备则会忽略这个包
此外,如果接收方的MAC地址是一个广播地址,那么交换机会将包发送到除了源端口外的所有端口。以下两个属于广播地址:
- MAC地址中的
FF:FF:FF:FF:FF:FF
- IP地址中的
255.255.255.255
路由器(网络层 & 数据链路层)
路由器与交换机的区别
网络包通过交换机后,到达路由器,并被转发到下一个路由器或目标设备。这一步的转发工作原理与交换机类似,也是通过查表判断包转发的端口,不过在具体的操作过程上,两者有如下区别:
- 路由器是基于
IP
设计的,俗称三层网络设备,路由器的各个端口都具有MAC地址和IP地址 - 交换机是基于以太网设计的,俗称二层网络设备,交换机的端口不具有MAC地址
路由器基本原理
- 路由器的端口具有MAC地址,可以作为以太网的收发方;同时还具有IP地址,从这个意义上来说,它与计算机的网卡是一样的
- 当转发包时,首先路由器会接收发给自己的以太网包,然后路由表查询转发端口,由相应的端口作为发送方将以太网包发送出去
路由器的包收发操作
- 电信号到达网口,将电信号转换为数字信号,通过包末尾的
FCS
进行错误校验 - 检查MAC头部中的接收方MAC地址,如果是发给自己的就放到缓冲区中,否则丢弃这个包
完成包接收操作后,路由器会去掉包开头的MAC头部,根据目标更新收发双方的MAC地址,直到数据包到达最终目的地
路由表转发与之前说的
Linux
路由表部分一致
服务器与客户端(解包 <=> 封装)
至此,一个数据包从发出开始,一路披荆斩棘,历经坎坷,总算到达目的地了
Bravo!