TCP/IP 协议理论基础整理
因为 TCP/IP 协议中每个协议都是层层包装、依赖,分开说可能并不会更容易掌握这些理论知识,所以这里把这些协议都放在了一起。
名词解释
七层和四层模型
这里有一张图比较详细。
OSI 七层参考模型包括:
- 应用层:应用程序可直接使用的协议,如 HTTP、FTP。
- 表示层:主要是数据格式转化,比如压缩、加密。
- 会话层:定义了如何建立及开始会话,比如 TLS。
- 传输层:接受上一层的数据,并将其交给网络层,且保证数据有效到达。
- 网络层:控制子网的运行,提供标准的端对端的包传输。
- 数据链路层:定义了单个链路上如何传输数据。
- 物理层:传输介质,硬件。
四层协议包括:
- 应用层:向用户提供一组常用的应用程序。
- 传输层:提供应用间通信。
- 网络层:负责相邻计算机之间的通信。
- 数据链路层:将比特流转换为物理传输信号
包、帧、数据包/报、段、消息
以上五个术语都用来表述数据的单位,大致区分如下:
- 包可以说是全能性术语;
- 帧(frame)用于表示数据链路层中包的单位;
- 数据包(packet)是 IP 等网络层以上的分层中包的单位;
- 段(segment)则表示 TCP 数据流中的信息;
- 数据报(datagrams)一般用来指 UDP 的数据单元;
- 消息是指应用协议中数据的单位。
MTU
最大传输单元(Maximum Transmission Unit,MTU),它是指一种通信协议的某一层上面所能通过的最大数据包大小(以字节为单位)。如果 IP 层的数据大于链路层的 MTU,那么 IP 层就要进行分片(fragmentation),把数据报分成若干片,使每一片都小于 MTU。
路径 MTU 通常指的是从源地址到目标地址所经过的路径上的所有 IP 的最大传输单元的最小值,通过 MTU 发现机制可以确定一个路径上的 MTU。
RTT
一个连接的往返时间(Round Trip Time,RTT),是数据发送时刻到接收到确认的时刻的差值,即一个连接的往返时间。
RTO
重传超时时间(Retransmission Time Out),即从数据发送时刻算起,超过这个时间便执行重传。在网络波动中,RTT 是动态变化的,RTO 也是随着 RTT 动态变化。
TTL(IP)
生存时间值(Time To Live, TTL)设置了数据报可以经过的最多路由器数,它指定了数据报的生存时间。TTL 的初始值由源主机设置(通常为 32 或 64),一旦经过一个处理它的路由器,它的值就减 1。当该字段的值为 0 时,数据报就被丢弃,并发送 ICMP 报文通知源主机。
MSL(TCP)
报文最大生存时间(Maximum Segment Lifetime),它是任何报文段被丢弃前在网络内的最长时间。
MSS
最大报文长度(Maximum Segment Size, MSS)表示 TCP 传往另一端的最大块数据的长度,当一个连接建立时,连接的双方都要通告自己的 MSS 选项。MSS 选项只能出现在 SYN 报文段中,即双方通信的第一个报文段。
MSS 仅关注每个数据包中有效负载的大小。它是通过从 MTU 中减去 TCP 和 IP 标头的长度来计算的。
ISN
报文段中的初始序号(Initial Sequence Number)随时间的变化而变化,每个连接都有不同的 ISN,同一时刻两边机器的 ISN 也几乎不一样( 的概率一样)。这样选择序号的目的在于防止网络中被延迟的分组在以后又被传输,而导致某个连接的一方对它做出错误的解释。
比如 RFC 793 指出可以把 ISN 可以看作是一个 32 比特的计数器,每 4ms 加 1。这样一个 ISN 的周期大约是 4.55 小时,因此只要 MSL 的值小于 4.55 小时,就不会得到相同的 ISN。
MAC 层

媒体介入控制(Media Access Control)层,属于 OSI 模型中数据链路层下层子层。它定义了数据帧怎样在介质上进行传输。
ARP 协议
ARP 协议属于链路层协议。ARP 请求的数据帧中包含目的主机的 IP 地址,其含义为,如果你是这个 IP 地址的拥有者,请回答你的硬件地址。
当系统收到一份目的端为本机的 ARP 请求报文后,它就把硬件地址填进去,然后用两个目的端地址分别替换两个发送端地址,并把操作字段置为 2,最后把它发送回去。
IP 协议

首部字段
普通的 IP 首部长度为 20 个字节,除非含有选项字段。
版本(4 位)
协议版本是 4,就是 IPv4,6 就是 IPv6,但是 IPv6 的源地址和目的地址长度与 IPv4 不一样。
首部长度(4 位)
首部长度指的是首部占 32bit 的数目,包含任何选项。因此首部最长为 60 字节。
区分服务(8 位)
总长度(16 位)
指的是整个 IP 数据报的长度,以字节为单位,包含首部长度,故最长可达 65535 字节,实际都受链路层 MTU 的限制。
标识(16 位)
标识字段唯一地标识主机发送的每一份数据报,IP 分片相关。
标志(3 位)
IP 分片相关。
片偏移(13 位)
IP 分片相关。
生存时间 TTL(8 位)
协议(8 位)
由于 TCP、UDP、ICMP、IGMP 都要向 IP 传送数据,因此 IP 必须在生成的 IP 首部中加入某种标识,以表明数据属于哪一层。
- 1 表示 ICMP 协议
- 2 表示 IGMP 协议
- 6 表示 TCP 协议
- 17 表示 UDP 协议
首部校验和(16 位)
根据 IP 首部计算的校验和码。
IP 地址
IP 选路
路由表
路径 MUT 发现
traceroute 工具
IP 分片
任何时候 IP 层接收到一份要发送的 IP 数据报时,它要判断向本地哪个接口发送数据(IP 选路),并查询该接口获得其 MTU。IP 把 MTU 与数据报长度进行比较,如果需要则进行分片。分片可以发生在原始发送端主机上,也可以发生在中间路由器上。
ICMP 协议
ICMP 经常被认为是 IP 层的一个组成部分,全称是 Internet Control Message Protocol,即互联网控制报文协议。
ICMP 报文是在 IP 数据报内部被传输的,它的数据结构如图所示。

ICMP 报文的类型可大致分为两种,ICMP 差错报告报文和 ICMP 询问报文。
差错报告报文
ICMP 差错报告报文用于目标主机或到目标主机路径上的路由器向源主机报告差错和异常情况。共有以下五种类型。
- 终点不可达:当路由器或主机不能交付数据报时向源点发送终点不可达;
- 源点抑制:当路由器或主机由于拥塞而丢弃数据报时向源点发送源点抑制报文;
- 时间超时:当路由器收到生存时间 TTL 为 0 的数据报时,除了丢弃数据报,还要向源点发送时间超过报文;
- 参数问题:当路由器或目的主机收到的数据报的首部中有的字段的值不正确时,就丢弃该数据报,并向源点发送参数问题报文。
- 改变路由(重定向):路由器把改变路由报文发送给主机,让主机知道下次应该将数据报发送给另外一个路由器。
以下情况不会导致产生 ICMP 差错报文:
- 对 ICMP 差错报文不会再发送 ICMP 差错报文。
- 对不是第一个分片的 IP 数据报都不发送 ICMP 差错报文。
- 源地址或目的地址是广播地址、环回地址。
询问报文
ICMP 询问报文有四种类型:回送请求和回答报文、时间戳请求和回答报文、掩码地址请求和回答报文、路由器询问和通告报文,最常用的是前两类。
ICMP 回送请求报文是由主机或路由器向一个特定的目的主机发出询问,收到此报文的主机必须给源主机或路由器发送 ICMP 回送回答报文。这种报文用来测试目的站是否可达及了解其有关状态。
应用
ping 命令
ping 工作在应用层,它直接使用 ICMP 回答请求和回答报文。
traceroute 命令
traceroute 工作在网络层,使用了 ICMP 时间戳报文。
DNS 协议
DNS 协议负责查询一个域名的 IP 地址。
DNS 域名系统主要使用 UDP,另外有两种情况会用 TCP:
- 返回响应 TC(删减标志)比特位被设置为 1 时,意味着响应长度超过了 512 字节,此时名称解析器通常使用 TCP 重发原来的请求。
- DNS 的辅助服务器定时向主服务器执行的区域传送以了解主服务器数据是否发生变更时会使用 TCP。
为什么 DNS 使用 UDP ?
- 历史原因,30 年前的网络还不像现在这么拥堵。
- TCP 三次握手建立连接需要额外的时间和流量。
- DNS 需要的数据一般都很少,就是一个域名。
- UDP 的首部空间占用小,也意味着 IP 分片的可能性就小,继而丢失的可能就小。
UDP 协议

首部字段
UDP 数据包最大报文长度是多少?
UDP 的校验和是可选的,而 TCP 的校验和是必需的。
TCP 协议
TCP 提供了一种可靠的面相字节流的运输层服务。

首部字段(20 字节)
标志比特(6 位)
标识比特(control flag)共有六个:
- ACK:确认序号有效,除了首次 SYN,其余情况该位必须为 1。
- PSH:接收方应该尽快将这个报文段交给应用层。
- RST:重建连接。
- SYN:同步序号用来发起一个连接。
- FIN:断开连接时,通信双方相互发送 FIN 为 1 的报文段。
- URG:紧急指针。
序号
序号是 32 bit 的无符号数,它表示在这个报文段中的第一个数据字节。
确认序号
确认序号是上次已成功收到的数据字节序号加 1,只有在 ACK 标识为 1 时确认序号字段才有效。
首部长度(4 位)
也叫数据偏移,含义为首部中 32 bit 的数目,这个字节占 4 bit,即最多 字节。TCP 的首部长度不是固定的,正常是 20 字节,但是任选字段的长度是可变的,最多可以有 60 字节的首部。
窗口(16 位)
TCP 的流量控制由连接的每一端通过声明的窗口大小来提供,窗口大小为字节数,含义为接收端告诉发送端自己还有多少缓冲区可以接收数据。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。
起始于确认序号字段指明的值。
选项(长度可变)
填充
状态变迁
一个 TCP 连接是靠“连接状态”来表示的。

建立连接为什么需要三次握手
客户端和服务器双方都需要确认四种情况:
- 客户端的发送是正常的;
- 客户端的接收是正常的;
- 服务器的发送是是正常的;
- 服务器的接收是正常的
三次握手就能确认双方收发正常,缺一不可:
- 第一次握手 :Client 什么都不能确认;Server 确认了对方发送正常,自己接收正常
- 第二次握手 :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:对方发送正常,自己接收正常
- 第三次握手 :Client 确认了:自己发送、接收正常,对方发送、接收正常;Server 确认了:自己发送、接收正常,对方发送、接收正常
断开连接为什么需要四次挥手
2MSL
半关闭
重传机制
超时重传
TCP 通过在发送时设置一个定时器来解决没有收到确认的问题,当定时器溢出时还没有收到确认,它就重传该数据。
快速重传(fast recovery)
当发送端收到第 3 个重复的 ACK 时,会在定时器过期之前,重传丢失的报文段。
流量控制
流量控制的出发点是,发送方不能无限制地一次性发送所有数据,需要考虑接收方的处理能力。最主要的场景就是接收方的数据处理能力比发送方发出的效率慢的情况,特别需要流量控制机制的参与。
滑动窗口(sliding window)
滑动窗口是接收方使用的窗口(offered window)大小,用来通告发送方接受端的缓存大小。滑动窗口涉及的数据可以分为 4 部分:

- 已发送并收到 ACK 的数据;
- 已发送但未收到 ACK 的数据;
- 未发送但总大小在接收方处理范围内;
- 未发送且总大小超过接收方处理范围。
窗口关闭
坚持定时器(persist timer)
糊涂窗口综合征
当接收端的应用层读取的数据一直小于操作系统接收的数据时,接收端通告的缓冲区大小会越来越小,就会导致发送方的窗口也越来越小,直到发送端会一直发送每段只有几字节的 TCP 数据,就是“糊涂窗口综合征”的现象了。
该现象可在两端中的任何一端,接收方可通告一个小的窗口,而发送方也可以发送少量的数据。可以在任何一端采取措施避免出现“糊涂窗口综合征”的现象。避免出现“糊涂窗口综合征”的措施是只有以下条件之一满足时才发送数据:
- 可以发送一个满长度的报文段。
- 可以发送至少是接收方通告窗口大小一半的报文段。
- 能够发送手头的所有数据并且不希望接收 ACK(也就是说滑动窗口里没有未被确认的数据)。
- 连接禁用纳格算法(该算法要求一个 TCP 连接上最多只能有一个未被确认的分组,该分组的确认到达之前不能发送其他分组)。
拥塞控制
虽然有了流量控制,解决了发送方慢的情况,但如果发送端能持续发送数据,接收端都能处理的话,这时需要另一种拥塞控制策略了。
拥塞控制发挥作用的主要场景是发送方待发送的报文一直达不到窗口大小限制的情况。这时就会出现另一个问题,大量的数据包发出来可能会使网络拥堵,导致数据包的延迟、甚至丢失,然后发送端又会重传数据,又进一步导致网络负担加重。
于是还需要一系列的具体方式来进行拥塞控制,主要有四种方法:
- 慢启动
- 拥塞避免
- 拥塞发生
- 快速恢复
慢启动
慢启动(slow start)为发送方的 TCP 增加了另一个窗口:拥塞窗口(Congestion Window),记为 cwnd(以字节为单位),当与另一个网络建立 TCP 连接时,拥塞窗口被初始化为 1 个报文段(即另一端通告的报文段大小),每收到一个 ACK,拥塞窗口就增加一个报文段,发送方取拥塞窗口与通告窗口中最小值作为发送上限。
拥塞窗口是发送方使用的流量控制(可以一次发送几个报文段),而通告窗口是接收方使用的流量控制。前者是发送方感受到的网络拥塞的估计,而后者则与接收方在该连接上的可用缓存大小有关。
cwnd 除了一定要小于通告窗口,为了考虑网络的拥塞情况,也不能无限增大,还需要一定的机制能够对发送的流量进行有效控制,下面有请拥塞避免算法。
拥塞避免
在拥塞避免(congestion avoidance)算法中,有一个叫慢启动门限 ssthresh (slow start threshold)的状态变量,大小通常是 65535 个字节。
- 当
cwnd<ssthresh时,使用慢启动算法。 - 当
cwnd>=ssthresh时,就会使用拥塞避免算法。
拥塞避免算法的具体内容是,当触发拥塞避免机制后,每接到一个 ACK,cwnd 增加 ,与慢启动的窗口指数增长相比,这是一种线性增长。
拥塞发生
当拥塞发生时(超时或者收到重复请求),会通过重新设置 cwnd 或 ssthresh 的值来减小拥塞窗口大小:
- ssthresh 会被设置为当前窗口的一半大小,但至少是 2 个报文段。
- 如果是超时引起了拥塞,则 cwnd 被设置为 1(进入慢启动,一直到
cwnd>=ssthresh)。 - 如果是收到重复请求,会采用下面快速恢复的手段。
快速重传是拥塞发生时的一个更有效的处理办法,与其等待超时了重传全部未确认的报文段,不如使用快速重传立即重传某一个报文段。
快速恢复
如果拥塞发生类型是是收到重复请求,那说明网络情况相比超时来说还不是特别糟,所以可以使用一种快速重传+快速恢复的手段:
- 当收到第 3 个重复的 ACK 时,重传丢失的报文段。
- 然后将 ssthresh 设置为当前拥塞窗口 cwnd 的一半。
- 设置 cwnd = ssthresh + 3(3 个报文段的字节)。
- 如果再次收到重复的 ACK,将 cwnd + 1x报文段,并发送一个分组。
- 如果收到新的 ACK 时,设置 cwnd = ssthresh。即说明快速恢复已经取得效果,可以结束恢复过程,进入拥塞避免状态。
对 TCP 的攻击
SYN flood 攻击
而 SYN flood 在攻击时,首先伪造大量的源 IP 地址,分别向服务器端发送大量的 SYN 包,此时服务器端会返回 SYN/ACK 包,因为源地址是伪造的,所以伪造的 IP 并不会应答,服务器端没有收到伪造 IP 的回应,会重试 3~5 次并且等待一个 SYN Time(一般为30秒至2分钟),如果超时则丢弃这个连接。攻击者大量发送这种伪造源地址的 SYN 请求,服务器端将会消耗非常多的资源(CPU和内存)来处理这种半连接,同时还要不断地对这些 IP 进行 SYN + ACK 重试。最后的结果是服务器无暇理睬正常的连接请求,导致拒绝服务。
参考
除了《TCP/IP 详解:卷一》,还参考了一些线上资料。
RFC
https://datatracker.ietf.org/doc/html/rfc793
https://datatracker.ietf.org/doc/html/rfc7414
比较全面的资料
https://coolshell.cn/articles/11564.html
https://coolshell.cn/articles/11609.html
比较专题的研究
https://xiaolincoding.com/network/
源码