TCP协议分析

1 TCP服务的特点

  传输层协议主要有两个:TCP协议和UDP协议。TCP协议相对于UDP协议的特点是:面向连接、字节流和可靠传输。
  对于TCP协议而言,发送端执行的写操作次数和接收端执行的读操作次数之间没有任何数量关系,这即字节流的概念:应用程序对数据的发送和接收是没有边界限制的。UDP则不同。发送端应用程序每执行一次写操作,UDP模块就将其封装成一个UDP数据报并发送之,接收端必须及时针对每一个UDP数据报执行读操作,否则就会丢包。
下图显示了TCP字节流服务和UDP数据报服务的上述区别:

TCP字节流服务

TCP协议分析

UDP数据报服务

TCP协议分析

TCP传输是可靠的。首先,TCP协议采用发送应答机制,即发送端发送的每个TCP报文段都必须得到接收方的应答,才认为这个TCP报文段传输成功;其次,TCP协议采用超时重传机制,发送端在发送出一个TCP报文段之后启动定时器,若在定时时间内未收到应答,它将重发该报文;最后,因为TCP报文段最终是以IP数据报发送的,而IP数据报到达接收端可能乱序、重复。所以TCP协议还会对接收到的TCP报文段重排、整理,再交付给应用层。

2 TCP头部结构

2.1 TCP固定头部结构

TCP头部结构如图所示:
TCP协议分析

16位端口号(port number):告知主机该报文段是来自哪里(源端口),以及传给哪个上层协议或应用程序(目的端口)的。
32位序号(sequence number):一次TCP通信(从TCP连接到断开)过程中某一个传输方向上的字节流的每个字节的编号。
32位确认号(acknowledgement number):用作对另一方发送来的TCP报文段的响应,其值是收到的TCP报文段、的序号值加1。
4位头部长度(header length):标识该TCP头部有多少个32bit字(4字节)。因为4位最大能表示15,故TCP头部最长是60字节。
6位标志位包含如下几项:

  • URG标志,表示紧急指针(urgent pointer)是否有效
  • ACK标志,表示确认号是否有效,带ACK标志的TCP报文段为确认报文段
  • PSH标志,提示接收端应用程序应该立即从TCP接收缓冲区中读走数据
  • RST标志,表示要求对方重新建立连接,带RST标志的TCP报文段为复位报文段
  • SYN标志,表示请求建立一个连接,带SYN标志的TCP报文段为同步报文段
  • FIN标志,表示通知对方本段要关闭连接了,带FIN标志的TCP报文段为结束报文段

16位窗口大小(window size):是TCP流量控制的一个手段。这里的窗口指的是接收通告窗口(Receiver Window,RWND),这告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据
16位校验和(TCP checksum):由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。
16位紧急指针(urgent pointer):是一个正的偏移量,这个字段是紧急指针相对当前序号的偏移。

3 TCP连接的建立和关闭

3.1 使用tcpdump观察TCP连接的建立和关闭

  详情见另外一篇文章《TCP三次握手与四次挥手分析》
三次握手过程如下:
TCP协议分析

四次挥手过程如下:
TCP协议分析

3.2 半关闭状态

  TCP连接是全双工的,所以它允许两个方向的数据传输被独立关闭。即通信的一端可以发送结束报文段给对方,告诉它本端已经完成了数据发送,但允许继续接收来自对方的数据,直到对方也发送结束报文段以关闭连接。这种状态称为半关闭(half close)状态。如下图所示:
TCP协议分析
在上图中,服务器和客户端应用程序判断对方是否已经关闭连接的方法是:read系统调用返回0(收到结束报文段)

4 TCP状态转移

  TCP连接的任意一端在任一时刻都处于某种状态,当前状态可以通过netstat命令查看。下图是完整的状态转移图,描绘了所有的TCP状态以及可能的状态转换:
TCP协议分析
图中粗虚线表示典型的服务器端连接的状态转移;粗实线表示典型的客户端连接的状态转移。CLOSED是一个假想的起始点

4.1 TCP状态转移总图

服务器的典型状态转移过程
  服务器通过listen系统调用进入LISTEN状态,被动等待客户端连接,因此执行的是所谓的被动打开。服务器一旦监听到某个连接请求(收到同步报文段),就将该连接放入内核等待队列中,并向客户端发送带SYN标志的确认报文段,此时该连接处于SYN_RCVD状态。如果服务器成功地接收到客户端发送回的确认报文段,这该连接转移到ESTABLISHED状态。ESTABLISHED状态是连接双方能够进行双向数据传输的状态。
  当客户端主动关闭连接(通过close或shutdown系统调用向服务器发送结束报文段),服务器通过返回确认报文段使连接进入CLOSE_WAIT。这个状态表示:等待服务器应用程序关闭连接。若服务器检测到客户端关闭连接后,也会立即给客户端发送一个结束报文段来关闭连接。这将使连接转移到LAST_ACK状态,以等待客户端对结束报文段的最后一次确认、一旦确认完成,连接就彻底关闭。
客户端的典型状态转移过程
  客户端通过调用connect系统调用主动与服务器建立连接,connect系统调用首先给服务器发送一个同步报文段,是连接转移到SYN_SENT状态,此后,connect系统调用可能因为如下原因失败返回:

  • 如果connect连接的目标端口不存在(为被任何进程监听),或者该端口仍被处于TIME_WAIT状态的连接所占用,这服务器将给客户端发送一个复位报文段,connect调用失败。
  • 若目标端口存在,但connect在超时时间内未收到服务器的确认报文段,则connect调用失败。

  connect调用失败将使连接立即返回到初始的CLOSED状态。若客户端成功收到服务器的同步报文段和确认,这connect调用成功返回,连接转移至ESTABLISHED状态。
  当客户端执行主动关闭时,它将向服务器发送一个结束报文段,同时连接进入FIN_WAIT_1状态。若此时客户端收到服务器专门用于确认目的的确认报文段,则连接状态转移至FIN_WAIT_2状态。
TCP协议分析

4.2 TIME_WAIT状态

  在TIME_WAIT状态,客户端连接要等待一段长为2ML(Maximum Segment LIfe,报文段最大生存时间)的时间,才能完全关闭。MSL是TCP报文段在网络中的最大生存时间。
TIME_WAIT状态存在的原因有两点:

  • 可靠地终止TCP连接
  • 保证让迟来的TCP报文段有足够的时间被识别并丢弃
    第一个原因:客户端需要停留在某个状态以处理重复收到的结束报文段(即向服务器发送确认报文段)
    第二个原因:一个TCP端口不能被同时打开多次(两次及以上),当一个TCP连接处于TIME_WAIT状态时,我们无法立即使用该连接占用着的端口来建立一个新连接。

TIME_WAIT状态要持续2MSL时间的原因:能够确保网络上两个传输方向上尚未被接收到的、迟到的TCP报文段都已经消失(被中转路由器丢弃)。一个连接的新的化身可以在2MSL时间之后安全地建立,而绝对不会收到属于原来连接的应用程序数据。

5 复位报文段

  在某些特殊条件下,TCP连接的一端会向另一端发送携带RST标志的报文段,即复位报文段,以通知对方关闭连接或重新建立连接。
  当客户端程序向服务器的某个端口发起连接,而该端口仍被处于TIME_WAIT状态的连接所占用时,客户端程序也将收到复位报文段。

6 TCP交互数据流

  TCP报文段所携带的应用程序数据按照长度分为两种:交互数据和成块数据。、
交互数据:仅包含很少的字节,使用交互数据的应用程序(或协议)对实时性要求高,比如telnet、ssh等
成块数据:其数据长度为TCP报文段允许的最大数据长度,使用成块数据的应用程序(或协议)对传输效率要求高,比如ftp。
在广域网上的交互数据流可能经受很大的延迟,并且携带交互数据的微小TCP报文段书库一般很多,这很可能导致拥塞发生。解决该问题的简单方法是使用Nagle算法
Nagle算法要求一个TCP连接的通信双方在任意时刻都最多只能发送一个未被确认的TCP报文段,在该TCP报文段的确认到达之前不能发送其他TCP报文段。另一方面,发送方在等待确认的同时,收集本端需要发送的微量数据,并在确认到来时以一个TCP报文段将他们全部发出。这样就极大地减少了网络上微小TCP报文段的数量。该算法的另一个优点在于其自适应性:确认到达的越快,数据发送也越快。

7 带外数据

  有些传输层协议具有带外(Out Of Band,OOB)数据的概念,用于迅速通告对方本端发生的重要事件。UDP没有实现带外数据传输,TCP亦没有真正的带外数据,但TCP的紧急方式利用传输普通数据的连接来传输紧急数据。其过程如下:假设一个进程已经往某个TCP连接的发送缓冲区写入了N个字节的普通数据,并等待其发送,在数据被发送前,该进程又向这个连接写入了3个字节的外带数据“abc”。此时,待发送的TCP报文段的头部将被设置URG标志,并且紧急指针被设置为指向最后一个外带数据的下一个字节。如下图所示:
TCP协议分析

8 TCP超时重传

  TCP服务必须能够重传超时时间内未收到确认的TCP报文段,为此TCP模块为每个TCP报文段都维护了一个重传定时器,该定时器在TCP报文段第一次被发送时启动。如果超时时间内未收到接收方的应答,TCP模块将重传TCP报文段并从中定时器。下次超时时间如何选择,以及最多执行多少次重传,就是TCP的重传策略。其策略如下:TCP一共执行5次重传,每次重传超时时间都增加一倍

9 拥塞控制

9.1 拥塞控制概述

  TCP模块还有一个任务:提高网络利用率,降低丢包率,并保证网络资源对每条数据流的公平性。这就是拥塞控制。
  拥塞控制四个部分:慢启动(slow start)、拥塞避免(congestion avoidance)、快速重传(fast retransmit)、快速恢复(fast recovery)。
  拥塞控制的最终受控变量是发送端向网络一次连续写入(收到其中第一个数据的确认之前)的数据量,称为SWND(Send Window,发送窗口)。发送端引入了一个状态变量称为拥塞窗口(Congestion Window,CWND)。下图显示了拥塞控制的输入和输出:
TCP协议分析

9.2 慢启动和拥塞避免

  TCP连接建立好之后,CWND将被设置初始值IW(Initial Window),其大小为2~4个SMSS。在新的Linux内核中,发送端最多能发送IW字节的数据,此后发送端每收到接收端的一个确认,其CWND就按照式(1-1)\text{(1-1)}增加:
CWND+=min(N,SMSS)(1-1) CWND += min(N, SMSS) \tag{1-1}
其中N是此次确认中包含的之前未被确认的字节数,这样CWND将按照指数形式扩大,这就是所谓的慢启动。慢启动算法的理由:TCP模块刚开始发送数据时并不知道网络的实际情况,需要一种试探的方式平滑地增加CWND的大小。
  若不施加其他手段,慢启动必然使得SWND很快膨胀,并最终导致网络阻塞,因此TCP拥塞控制中定义了另一个状态变量:慢启动门限(slow start threshold size,ssthresh)。当CWND的大小超过该值时,TCP拥塞控制将进入拥塞避免阶段。
拥塞避免算法使得CWND按照线性方式增加,从而减缓其扩大,实现方式如下:

  • 每个RTT时间内按照式(1-1)\text{(1-1)}计算新的CWND,而不论该RTT时间内发送端到多少个确认
  • 每收到一个对新数据的确认报文段,就按照式(1-2)\text{(1-2)}来更新CWND
    CWND+=SMSSSMSS/CWND(1-2) CWND += SMSS*SMSS/CWND \tag{1-2}

发送端判断拥塞发生的依据有如下两个:

  • 传输超时,或者是TCP重传定时器溢出。
  • 接收到重复的确认报文段

  拥塞控制对这两种情况有不同的处理方式,对第一种情况仍然使用慢启动和拥塞避免。对第二种情况则使用快速重传和快速恢复。若发送端检测到拥塞发生是由于第一种情况,则将执行重传并做如下调整:
ssthresh=max(FlightSize/2,2SMSS)CWMD<=SMSS(1-3) ssthresh = max(FlightSize/2, 2*SMSS) \tag{1-3} \\ CWMD<=SMSS
其中FlightSize是已经发送但未收到确认的字节数。

9.3 快速重传和快速恢复

  拥塞控制算法需要判断当收到重复的确认报文段时,网络是否真的发生了拥塞,或者说TCP报文段是否真的丢失了。具体做法是:若发送端连续收到3个重复的确认报文段,就认为是拥塞发生了。然后启用快速重传和快速恢复算法来处理拥塞,过程如下:

  1. 当收到第3个重复的确认报文段时,按照式(1-3)\text{(1-3)}计算ssthresh,然后立即重传丢失的报文段,并按照式(1-4)\text{(1-4)}设置CWND
    CWND=ssthresh+3SMSS(1-4) CWND = ssthresh +3 * SMSS \tag{1-4}
  2. 每次收到1个重复的确认时,设置CWND = CWDN + SMSS。此时发送端可以发送新的TCP报文段(若新的CWND允许的话)。
  3. 当收到新数据的确认时,设置CWND=ssthresh(ssthresh是新的慢启动门限值)。
      快速重传和快速恢复完成之后,拥塞控制将恢复到拥塞避免阶段,这一点由第3步操作可得知。