TCP 协议简析
基础知识
作为网络基础知识,相信大家对 TCP/IP 5层协议模型
不会感到陌生:
- 应用层(应用层,表示层,会话层):具体的应用逻辑
- 传输层:提供进程间的通信服务
- 网络层:提供端系统之间的数据透明传送
- 链路层:数据在具体物理设备上的表示(设备驱动、网卡)
- 物理层:具体物理设备之间的链接(电缆)
其中的 TCP 与 IP 协议是互联网的基石。
IP 协议
IP Internet Protocol
协议解决了大规模、异构网络的互联互通问题:
- 将不同物理设备的帧转换为统一的 IP 数据报
datagram
,实现异构物理设备间的通信 - 为每个设备分配一个唯一的逻辑地址,屏蔽物理地址的差异,实现异构物理设备间的寻址
IP 协议提供不可靠 Unreliable
、无连接 Connectionless
的数据报传输功能:
- 不可靠:不能保证IP数据报能成功地到达目的地
Best Effort
的输服务。
当发生某种错误时,如某个路由器暂时用完了缓冲区,IP有一个简单的错误处理算法:丢弃数据报,然后发送ICMP消息报给信源端。
- 无连接:不维护任何关于后续数据报的状态信息
如果一信源向相同的信宿发送两个连续的数据报(先是A,然后是B),每个数据报都是独立地进行路由选择,可能选择不同的路线,因此B可能在A到达之前先到达。
受限于协议的特性,IP 数据报可能出现 丢包 与 乱序。因此基于IP协议的互联网络必然是一个不可靠的网络。
TCP 协议
TCP Transport Control Protocol
协议是一个面向连接的、可靠的、基于字节流的传输层协议。其主要目的是在不可靠的互联网络上提供可靠的端到端字节流传输。
互联网络的不同部分可能有截然不同的拓扑结构、带宽、延迟、数据包大小和其他参数。
TCP的设计目标是能够动态地适应互联网络的这些特性,而且具备面对各种故障时的健壮性。
其核心的功能点可以概括为两方面:
-
流量控制:保证数据传输效率
- 数据分片:在发送端对用户数据进行分片,在接收端进行重组,由TCP确定分片的大小并控制分片和重组
- 滑动窗口:TCP连接每一方的接收缓冲空间大小都固定,接收端只允许另一端发送接收端缓冲区所能接纳的数据,TCP在滑动窗口的基础上提供流量控制,防止较快主机致使较慢主机的缓冲区溢出
-
可靠传输:保证数据传输正确性
- 到达确认:接收端接收到分片数据时,根据分片数据序号向发送端发送一个确认
- 超时重发:发送方在发送分片时启动超时定时器,如果在定时器超时之后没有收到相应的确认,重发分片
- 失序处理:作为IP数据报来传输的TCP分片到达时可能会失序,TCP将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层
- 重复处理:作为IP数据报来传输的TCP分片会发生重复,TCP的接收端必须丢弃重复的数据
- 数据校验:TCP将保持它首部和数据的检验和,这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到分片的检验和有差错,TCP将丢弃这个分片,并不确认收到此报文段导致对端超时并重发
TCP 细节
报文结构
TCP 的基本传输单位是报文段 segment
,其结构如下:
-
端口号是 16bit 的无符号数,每个TCP段都包含源端和目的端的端口号,用于寻找发端和收端应用进程。
这两个值加上 IP首部中的源端IP地址和目的端IP地址唯一确定一个TCP连接。 -
序号是 32bit 的无符号数,用来标识由发送端发出的数据字节流,它表示在这个报文段中的的第一个数据字节。 TCP用序号对每个字节进行计数。
-
确认序号也是 32bit 的无符号数,用于标识发送确认的一端所期望收到的下一个序号。确认序号应当是上次已成功收到数据字节序号加1。
-
首部长度给出首部中32bit字的数目。需要这个值是因为任选字段的长度是可变的。然而,没有任选字段,正常的长度是20字节。这个字段占4bit,因此TCP最多有60字节的首部。
-
特殊标记位。它们中的多个可同时被设置为1。
- URG 紧急指针
- ACK 确认序号有效
- PSH 接收方应该尽快将这个报文段交给应用层
- RST 重建连接
- SYN 同步序号用来发起一个连接
- FIN 发端完成发送任务
-
窗口大小是一个16bit字段,其单位为字节,因而窗口大小最大为65535字节。发送端可以通过这个字段来声明接受窗口大小,告知对方自己期望接受的数据量,从而实现流量控制。
-
选项部分是用于支持 TCP 的一些扩展功能:数据分片、窗口放大、时间戳
数据分片与重组
路径MTU
目前最常见的局域网协是以太网Ethernet
,其网络拓扑为总线型拓扑,即多个计算机共享同个信道。计算机要发送信息时,会通过共享线路将信息广播到其他计算机上。当多个计算机同时发送消息时,彼此之间会有干扰,导致数据发送失败。
因此以太网引入了一个冲突检测collision detection
的功能,保证通过时刻只有一个计算机在网络上发送消息。
这造成了两个问题:
-
每次发送都要执行冲突检测,需要额外的通信开销(数据封装、设备资源)。如果每次只传输一个字节,传输性能会十分低下,因此应该尽可能减少传输次数,即一次尽可能传输更多的数据。
-
信道是共享的,同一时刻只能有一个计算机在发送消息。如果每次都传输完整的数据,那么其他计算机会等待很久才能响应,这对于一些实时通信任务来说是个噩梦。
以太网定义其基本的通信单位是数据帧 frame
,其最基本的格式如下:
为了在效率与公平上达到一个平衡,以太网定义每个数据帧的最大为 1518。除去固定 14 字节的头部与 4 字节的尾部,数据负载的长度最多为 1500 字节。这个 1500 就是我们常说的以太网的最大传输单元 —— MTU。
[lhop@localhost ~]$ netstat -i
Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
eth0 1500 0 947894 0 0 0 597731 0 0 0 BMRU
lo 65536 0 0 0 0 0 0 0 0 0 LRU
MTU 是由底层网络的特性所决定的,如果两台主机之间的通信要通过多个网络,那么每个网络的链路层就可能有不同的 MTU。通信路径中的最小 MTU,决定了整个通信链路的 MTU 上限,它被称作 路径MTU。
IP 分片
IP包头中用2个字节描述 IP 报文总长度,因此IP数据包的最大长度是64K字节(65535)。如果IP层有一个数据报要传,而且数据的长度比链路层的MTU还大,那么IP层就需要进行分片fragmentation
,把数据报分成若干片并保证每一片都小于MTU。
IP层接收到一份要发送的IP数据报时,它要判断向本地哪个接口发送数据(选路),并查询该接口获得其MTU。IP把MTU与数据报长度进行比较,如果需要则进行分片。
数据分片 可以发生在原始发送端主机上,也可以发生在中间路由器上。数据重组 由目的端的IP层来完成,其目的是使分片和重新组装过程对传输层 (TCP和UDP) 是透明的
当IP数据报被分片后,每一片都成为一个分组,具有自己的IP首部,并在选择路由时与其他分组独立。这样,当数据报的这些片到达目的端时有可能会失序,但是在IP首部中有足够的信息让接收端能正确组装这些数据报片。
尽管IP分片过程看起来是透明的,但有一点让人不想使用它:即使只丢失一片数据也要重传整个数据报。其原因如下:
如果对数据报分片的是中间路由器,而不是起始端系统,那么起始端系统就无法知道数据报是如何被分片的,无法只重传数据报中的一个数据报片。
传输层分片
当在以太网上传输数据时,TCP 协议在建立连接时,会根据以太网的 MTU 与 IP 层的头部长度来协商一个最大报文段长度 MSS = MTU - IP头部长度 = 1472。
- 发送端向 IP 层发送报文前,会按照 MSS 分割成多个报文段,然后逐个向 IP 层发送。
- 接收端从 IP 层接收到报文后,会将分片进行重组。
整个过程在传输层完成,避免了 IP 层的分片处理。
当使用 UDP 协议时,用户发送的报文会被原封不动的发送给 IP 层。
UDP包头内有总长度字段,同样为两个字节,因此UDP数据包的总长度被限制为 65535 从而保证这样恰好可以放进一个IP包内,简化了 UDP/IP 的实现。
当 IP 层需要发送大于 MTU 的数据报时,IP 层会对数据进行分片—重组处理。如果 UDP 数据报较大,并且网络状况较差的话,频繁的重传会造成传输效率的低下。
因此,使用 UDP 进行数据传输时,发送端最好将 UDP 数据报的大小控制在 MSS 以内。
到达确认与超时重传
前面说过,TCP 提供一种面向连接的、可靠的字节流服务。而 IP 层本身不存在连接的概念,因此TCP 需要在进程间维护一个传输字节流的连接。此外,为了保证消息不会产生乱序,TCP 会为在连接中传输的每个字节分配一个唯一的序列号,用于保证字节直接的顺序不会被打乱。
三次握手
建立连接是由 Client 向 Server 发起的:
- Client 会给 Server 发送一个
SYN
包 - Server 每收到一个新的
SYN
包都会创建一个半连接,然后向 Client 发送SYNACK
包 - Client 在收到 Server 的
SYN/ACK
包后会发出ACK
,Server 收到该ACK
后,三次握手就完成并产生了一个 TCP 全连接
在建立连接时,双方会在 SYN 报文中协商一个初始序号 ISN
:
- 发送端每次发送新数据时,会递增这个序号
seq
,表示该报文段首字节的字节流编号。 - 接受方接收到报文后,会返回一个确认序号
ack
,表明这个序号之前的数据已经收到。
SYN 报文的特殊之处在于:
虽然其不附带有字节数据,但是其本身需要占用一个字节序号,因此 ACK 包中返回的确认序号需要在 ISN 的基础上递增一个字节。
四次挥手
断开连接可以由 Client 或 Server 任意一端发起,也可以双方同时发起:
- 主动关闭:主动向对端发送
FIN
包关闭链接,然后会接收ACK
- 被动关闭:接收到对端的
FIN
包后再发送FIN
包,也会向对端回ACK
主动关闭方维持 TIME_WAIT
状态的意义:
最后发送的这个
ACK
包可能会被丢弃掉或者有延迟,这样对端就会再次发送FIN
包。如果不维持TIME_WAIT
这个状态,那么再次收到对端的FIN
包后,本端就会回一个 Reset 包,这可能会产生一些异常
超时重传
前面提到,TCP 数据报可能会在传输过程中丢失:
- 发送方发送的分组丢失
- 接收方响应确认的分组丢失
上面两种丢失情况,对于发送方来说结果是相同的:接收不到确认分组。因此 TCP 引入了超时重传机制来解决报文丢失的问题:
发送端每发送一个报文段,TCP会为其保留一个副本并设定一个计时器并等待确认信息。如果计时器超时,而发送的报文段中的数据仍未得到确认,则重传这一报文段,直到发送成功为止。
影响超时重传机制协议效率的一个关键参数是重传超时时间 RTO
:
- RTO 过大将会使发送端经过较长时间的等待才能发现报文段丢失,降低了连接数据传输的吞吐量
- RTO 过小可能将一些延迟大的报文段误认为是丢失,造成不必要的重传,浪费了网络资源
TCP协议采用自适应算法记录数据包的往返时延 RRT
,并根据往返时延设定 RTO 的取值:
RTT = 传播时延 + 排队时延(路由器和交换机)+数据处理时延(应用程序)
一般来说,RTO 的取值会略大于 RTT 以保证数据包的正常传输。
拥塞窗口与接收窗口
拥塞窗口
在实际的应用中,一个 TCP 连接传输数据时不可能独占网络,因此网络的可用带宽是动态变化的。为了最大化的利用网络带宽,TCP 实现了一个动态调整传输速率的拥塞控制机制:
- 网络空闲时提升传输速度,提高数据传输效率
- 网络拥塞时降低传输速度,避免丢包触发不必要的超时重传
该机制的核心是发送端的拥塞窗口:
在发送端设置一个大小为
cwnd
发送窗口结构,cwnd 根据网络的拥塞情况(TCP 重传率)动态变化,发送端只能发送小于或等于拥塞窗口大小的数据。
拥塞控制是 TCP 协议的核心,并且对传输性能有着至关重要的影响。其中一个著名的拥塞算法是 TCP Reno
,大致可将其分为 4 个阶段:
- 慢启动:
初始时 cwnd 为 1,然后逐渐地增大发包数量。这个阶段每经过一个 RTT,发包数量就会翻倍,直到 cwnd 增大到一个阈值
ssthreshold
(该阶段的 cwnd 以指数形式增长)
- 拥塞避免:
在这个阶段 cwnd 不再成倍增加,而是一个 RTT 增加 1,即缓慢地增加 cwnd,以防止网络出现拥塞(该阶段的 cwnd 线性增长)
- 快速重传 / 超时重传:
-
超时重传
发送端超过 RTO 后仍然收不到 ack 响应,会据此判断此时网络已经出现了拥塞:- ssthreshold = cwnd / 2
- cwnd = 1
- 进入慢启动阶段
-
快速重传
发送端连续收到 3 个重复的 ack 序号后,会据此判断数据包出现了丢失,但此时网络未出现拥塞情况(拥塞窗口不必恢复到初始值):- cwnd = cwnd / 2
- ssthreshold = cwnd
- 进入快速恢复阶段
- 快速恢复:
- cwnd = cwnd + 3 * MSS
- 重传数据
- 再每收到一个重复ACK,cwnd = cwnd + 1
- 直到收到不重复ACK,设置cwnd = ssthreshold,进入拥塞避免阶段
接收窗口
除了网络状况外,发送方还需要知道接收方的处理能力。如果接收方的处理能力差,那么发送方就必须要减缓它的发包速度。接收方的处理能力是通过接收窗口 rwnd
来表示的:
- 接收方在收到数据包后,会给发送方回一个 ack,并后把自己的 rwnd 大小写入到 TCP 头部的 win 这个字段,这样发送方就能根据这个字段来知道接收方的 rwnd
- 发送方在发送下一个 TCP 数据报的时候,会先对比发送方的 cwnd 和接收方的 rwnd,得出这二者之间的较小值,然后控制发送的数据量不能超过这个较小值
rwnd
的最大值由最大接收缓冲区空间确定,并且用户可以动态指定每个 TCP 连接的接收缓冲大小。然而 TCP 头部中的 win 这个字段只有 16bit,能够表示的大小最大只有 65535(64K)。为了支持更大的接收窗口rwnd
以满足高性能网络,TCP 提供了窗口缩放选项 WSopt
来突破这一限制。
此外,针对一些 RTT 较大的高带宽网络,提高 rwnd
有助于提升传输效率,为此 TCP 引入了能够根据网络状况自动调整接收缓存的算法,这种自动调整技术也称为 DRS (Dynamic Right-Sizing)
。