TCP/IP详解10-传输层:TCP:传输控制协议

TCP/IP详解10-传输层:TCP:传输控制协议

1. TCP为应用层提供的服务 和 TCP首部

TCP是一种面向连接的(一对一),提供可靠交付的和全双工通信的,基于字节流的端到端传输层通信协议。

  • 面向连接: TCP 在传输数据之前必须先建立连接,数据传输结束后要释放连接。
  • 一对一: 每一条 TCP 连接只能有 2 个端点,故 TCP 不提供广播或多播服务。
  • 可靠交付: TCP 提供可靠交付
  • 基于字节流: TCP 是面向字节流的。虽然应用进程和 TCP 的交互是一次一个数据块(大小不等),但 TCP 把应用程序交下来的数据仅仅看成是一连串的无结构的字节流, 而并不知道所传输的字节流的含义。

1.1 TCP的服务

TCP通过下列方式来提供可靠性:

  • 应用数据被分割成 TCP 认为最适合发送的数据块。这和 UDP 完全不同, 应用程序产生的数据报长度将保持不变。由 TCP 传递给 IP 的信息单位称为报文段或段( segment)。在18.4节我们将看到TCP如何确定报文段的长度。
  • 当 TCP 发出一个段后,它启动一个定时器,等待目的端确认收到这个报文段。 如果不能及时收到一个确认,将重发这个报文段。在第 21 章我们将了解 TCP 协议中自适应的超时及重传策略。
  • 当 TCP 收到发自 TCP 连接另一端的数据,它将发送一个确认。这个确认不是立即发送,通常将推迟几分之一秒,这将在 19.3 节讨论。
  • TCP 将保持它首部和数据的检验和。这是一个端到端的检验和,目的是检测数据在传输过程中的任何变化。如果收到段的检验和有差错,TCP 将丢弃这个报文段和不确认收到此报文段(希望发端超时并重发)。
  • 既然 TCP 报文段作为 IP 数据报来传输,而 IP 数据报的到达可能会失序,因此 TCP 报文段的到达也可能会失序。如果必要,TCP 将对收到的数据进行重新排序,将收到的数据以正确的顺序交给应用层。
  • 既然 IP 数据报会发生重复,TCP 的接收端必须丢弃重复的数据。
  • TCP 还能提供流量控制。TCP 连接的每一方都有固定大小的缓冲空间。TCP 的接收端只允许另一端发送接收端缓冲区所能接纳的数据。这将防止较快主机致使较慢主机的缓冲区溢出。

1.2 TCP的首部

TCP/IP详解10-传输层:TCP:传输控制协议

  • TCP 段中源端口、目的端口,加上 IP 首部中的源端 IP 地址和目的端 IP 地址构成四元组,可唯一确定一个 TCP 连接。
  • 序号:用于对字节流进行编号,例如序号为 301,表示第一个字节的编号为 301,如果携带的数据长度为 100 字节,那么下一个报文段的序号应为 401。
    • 当建立一个新的连接时,SYN 标志变 1。序号字段包含由这个主机选择的该连接的初始序号ISN(Initial Sequence Number)。该主机要发送数据的第一个字节序号为这个 ISN 加 1,因为 SYN标志消耗了一个序号(后续将会看到 FIN 标志也要占用一个序号)。
  • 确认号:期望收到的下一个报文段的序号。例如 B 正确收到 A 发送来的一个报文段,序号为 501,携带的数据长度为 200 字节,因此 B 期望下一个报文段的序号为 701,B 发送给 A 的确认报文段中确认号就为 701。
    • 只有 ACK 标志为 1 时,确认序号字段才有效。
  • 数据偏移(4位首部长度):指的是数据部分距离报文段起始处的偏移量,实际上指的是首部的长度。首部长度给出首部中 32 bit字的数目,这个字段占 4 bit,因此TCP最多有60字节的首部。
  • 6个标志比特
    • 确认 ACK:当 ACK=1 时确认号字段有效,否则无效。TCP 规定,在连接建立后所有传送的报文段都必须把 ACK 置 1。
    • 同步 SYN:在连接建立时用来同步序号。当 SYN=1,ACK=0 时表示这是一个连接请求报文段。若对方同意建立连接,则响应报文中 SYN=1,ACK=1。
    • 终止 FIN:用来释放一个连接,当 FIN=1 时,表示此报文段的发送方的数据已发送完毕,并要求释放连接。
    • RST:重建连接。置为1时表示TCP连接中出现了严重的差错, 必须释放连接, 然后重建连接, 也可用于拒绝非法的数据或拒绝连接请求
    • URG:紧急指针(urgent pointer)有效。
    • PSH:接收方应该尽快将这个报文段交给应用层。表示是带有PUSH标志的数据,接收到数据后不必等缓冲区满再发送(较少使用)
  • 16位窗口大小:窗口值作为接收方让发送方设置其发送窗口的依据。之所以要有这个限制,是因为接收方的数据缓存空间是有限的。
  • 检验和:覆盖了整个的 TCP 报文段:TCP 首部和 TCP 数据。这是一个强制性的字段,一定是由发端计算和存储,并由收端进行验证。TCP 检验和的计算和 UDP 检验和的计算相似,使用一个伪首部。
  • 紧急指针:是一个正的偏移量,和序号字段中的值相加表示紧急数据最后一个字节的序号。TCP 的紧急方式是发送端向另一端发送紧急数据的一种方式。
    • 只有当 URG 标志置 1 时紧急指针才有效。
  • 最常见的可选字段是最长报文大小,又称为 MSS (Maximum Segment Size)。每个连接方通常都在通信的第一个报文段(为建立连接而设置 SYN 标志的那个段)中指明这个选项。它指明本端所能接收的最大长度的报文段。
  • TCP 报文段中的数据部分是可选的

2. TCP连接的建立与终止

2.1 TCP 的三次握手和四次挥手

2.1.1 三次握手

为什么需要采用三次握手?

主要是为了防止两次握手情况下已失效的连接请求报文段突然又传送到服务端,而产生的错误。举例如下:

客户 A 向服务器 B 发出 TCP 连接请求,第一个连接请求报文在网络的某个节点长时间滞留,A 超时后认为报文丢失,于是再重传一次连接请求,B 收到后建立连接。数据传输完毕后双方断开连接。而此时,前一个滞留在网络中的连接请求到达了服务端 B,而 B 认为 A 又发来连接请求,若采用的是“两次握手”,则这种情况下 B 认为传输连接已经建立,并一直等待 A 传输数据,而 A 此时并无连接请求,因此不予理睬,这样就造成了 B 的资源白白浪费了;但此时若是使用“三次握手”,则 B 向 A 返回确认报文段,由于是一个失效的请求,因此 A 不予理睬,建立连接失败。第三次握手的作用:防止已失效的连接请求报文段突然又传送到了服务器。

三次握手过程

TCP/IP详解10-传输层:TCP:传输控制协议

  • 1)第一次握手:客户机的 TCP 首先向服务器的 TCP 发送一个连接请求报文段。这个特殊的报文段中不含应用层数据,其首部中的 SYN 标志位被置为 1。另外,客户机会随机选择一个起始序号 seq=x (连接请求报文不携带数据,但要消耗掉一个序号)。
  • 2)第二次握手:服务器的 TCP 收到连接请求报文段后,如同意建立连接,就向客户机发回确认,并在 OS 内核中为该TCP连接分配 TCP 缓存和变量在确认报文段中,SYN 和 ACK 位都被置为 1,确认号字段的值为 x+1 (表示希望收到的下一个字节的序号为 x+1),并且服务器随机产生起始序号 seq=y (确认报文不携带数据,但也要消耗掉一个序号)。
  • 3)第三次握手:当客户机收到确认报文段后,还要向服务器给出确认,并且也要在 client 端的 OS 内核中给该连接分配缓存和变量。这个报文段的 ACK 标志位被置 1,序号字段为 x+1,确认号字段为 y+1。

需要注意的是:服务器端的资源是在完成第二次握手时分配的,而客户端的资源是在完成第三次握手时分配的。这就使得服务器易于受到SYN洪泛攻击。

SYN攻击

如果理解了TCP三次握手的原理,再来理解SYN攻击相信不是什么难事了。下面来看看SYN攻击的原理。

在三次握手过程中,Server 发送 SYN-ACK 之后,收到 Client 的 ACK 之前的 TCP 连接称为半连接(half-open connect),此时 Server 处于SYN_RCVD 状态,当收到 ACK 后,Server 转入 ESTABLISHED 状态。

SYN 攻击就是 Client 在短时间内伪造大量不存在的 IP 地址,并向 Server 不断地发送 SYN 包,Server 回复确认包,并等待 Client 的确认,由于源地址是不存在的,因此,Server 需要不断重发直至超时,这些伪造的 SYN 包将产时间占用未连接队列(内核会为每个这样的连接分配资源的),导致正常的 SYN 请求因为队列满而被丢弃,从而引起网络堵塞甚至系统瘫痪。SYN 攻击是一种典型的 DDOS 攻击,检测 SYN 攻击的方式非常简单,即当 Server 上有大量半连接状态且源 IP 地址是随机的,则可以断定遭到 SYN 攻击了

2.1.2 四次挥手

四次挥手的原因

客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

四次挥手过程

TCP/IP详解10-传输层:TCP:传输控制协议

  • 1)第一次挥手:客户机打算关闭连接,就向其 TCP 发送一个连接释放报文段,并停止发送数据,主动关闭 TCP 连接,该报文段的 FIN 标志位被置1,seq=u,它等于前面已传送过的数据的最后一个字节的序号加1(FIN 报文段即使不携带数据,也要消耗掉一个序号)。
  • 2)第二次挥手:服务器收到连接释放报文段后即发出确认,确认号是 ack=u+1,而这个报文段自己的序号是 v,等于它前面已传送过的数据的最后一个字节的序号加 1。此时,从客户机到服务器这个方向的连接就释放了,TCP 连接处于半关闭状态。但服务器若发送数据,客户机仍要接收,即从服务器到客户机这个方向的连接并未关闭
  • 3)第三次挥手:若服务器已经没有要向客户机发送的数据,就通知 TCP 释放连接,此时其发出 FIN=1 的连接释放报文段(注意: 此时确认号字段值仍为 u+1, 因为这段时间里, 客户端并未发送任何数据到服务器)。
  • 4)第四次挥手:客户机收到连接释放报文段后,必须发出确认。在确认报文段中,ACK 字段被置为 1,确认号 ack=w+1,序号 seq=u+1。此时 TCP 连接还没有释放掉,必须经过时间等待计时器设置的时间 2MSL 后,A 才进入到连接关闭状态。

2.2 TCP的状态变迁图

TCP/IP详解10-传输层:TCP:传输控制协议

TCP/IP详解10-传输层:TCP:传输控制协议

注意:

  • 将图中左下角 4 个状态放在一个虚线框内,并标为“主动关闭”。其他两个状态(CLOSE_WAIT和LAST_ACK)也用虚线框住,并标为“被动关闭”
  • CLOSED 状态不是一个真正的状态,而是这个状态图的假想起点和终点。

上图中还有一些其他的状态迁移,这些状态迁移针对服务器和客户端两方面的总结如下:

  • LISTEN->SYN_SENT,对于这个解释就很简单了,服务器有时候也要打开连接的嘛。
  • SYN_SENT->SYN_收到(同时打开),服务器和客户端在 SYN_SENT 状态下如果收到 SYN 数据报,则都需要发送 SYN 的 ACK 数据报并把自己的状态调整到 SYN 收到状态,准备进入 ESTABLISHED
  • SYN_SENT->CLOSED,在发送超时的情况下,会返回到CLOSED状态。
  • SYN_收到->LISTEN,如果受到RST包,会返回到LISTEN状态。
  • SYN_收到->FIN_WAIT_1,这个迁移是说,可以不用到 ESTABLISHED 状态,而可以直接跳转到 FIN_WAIT_1 状态并等待关闭。

TCP/IP详解10-传输层:TCP:传输控制协议

最大报文段长度

最大报文段长度(MSS)表示 TCP 传往另一端的最大块数据的长度。当建立一个连接时,每一方都有用于通告它期望接收的 MSS 选项( MSS 选项只能出现在 SYN 报文段中)。如果一方不接收来自另一方的 MSS 值,则 MSS 就定为默认值 536 字节(这个默认值允许 20 字节的 IP 首部和 20 字节的TCP首部以适合 576 字节IP数据报)。

一般这个 SYN 长度是 MTU 减去固定 IP 首部和 TCP 首部长度。对于一个以太网,一般可以达到 1460 字节。当然如果对于非本地的 IP,这个 MSS 可能就只有 536 字节,而且,如果中间的传输网络的 MSS 更佳的小的话,这个值还会变得更小。

TCP的半关闭

TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。这就是所谓的半关闭

TCP/IP详解10-传输层:TCP:传输控制协议

TIME_WAIT状态

TIME_WAIT 状态也称为 2MSL 等待状态。每个具体 TCP 实现必须选择一个报文段最大生存时间 MSL(Maximum Segment Lifetime)。 它是任何报文段被丢弃前在网络内的最长时间。

  • 1)为了保证客户端发送的最后一个 ACK 报文段能够达到服务器
    • 这个 ACK 报文段可能丢失,因而使处在 LAST_ACK 状态的服务器收不到确认。这样的话,服务器会超时重传 FIN+ACK 报文段,客户端就能在 2MSL 时间内收到这个重传的 FIN+ACK 报文段,接着客户端重传一次确认,重启计时器。最后,客户端和服务器都正常进入到 CLOSED 状态。
    • 如果客户端在 TIME_WAIT 状态不等待一段时间,而是在发送完ACK报文后立即释放连接,那么就无法收到服务器重传的 FIN+ACK 报文段,因而也不会再发送一次确认报文。这样,服务器就无法按照正常步骤进入 CLOSED 状态。
  • 2)防止已失效的连接请求报文段出现在本连接中。客户端在发送完最后一个 ACK 确认报文段后,再经过时间 2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。

注意:服务器结束TCP连接的时间要比客户端早一些,因为客户机(最先提出close请求的一端)最后要等待2MSL后才可以进入CLOSED状态。

2MSL 等待的另一个结果是这个 TCP 连接在 2MSL 等待期间,定义这个连接的插口(客户的 IP 地址和端口号,服务器的 IP 地址和端口号)不能再被使用。这个连接只能在 2MSL 结束后才能再被使用

在连接处于 2MSL 等待时,任何迟到的报文段将被丢弃。因为处于 2MSL 等待的、由该插口对(socket pair)定义的连接在这段时间内不能被再用,因此当要建立一个有效的连接时,来自该连接的一个较早替身( incarnation)的迟到报文段作为新连接的一部分不可能不被曲解(一个连接由一个插口对来定义。一个连接的新的实例( instance)称为该连接的替身)。

FIN_WAIT_2 状态

这就是著名的半关闭的状态了,这是在关闭连接时,客户端和服务器两次握手之后的状态。在这个状态下,应用程序还有接受数据的能力,但是已经无法发送数据,但是也有一种可能是,客户端一直处于FIN_WAIT_2状态,而服务器则一直处于WAIT_CLOSE状态,而直到应用层来决定关闭这个状态。

同时打开

每一方必须发送一个 SYN,且这些 SYN 必须传递给对方。这需要每一方使用一个对方熟知的端口作为本地端口。这又称为同时打开( simultaneous open)

TCP/IP详解10-传输层:TCP:传输控制协议

两端几乎在同时发送 SYN,并进入 SYN_SENT 状态。当每一端收到 SYN 时,状态变为 SYN_RCVD,同时它们都再发 SYN 并对收到的 SYN 进行确认。当双方都收到 SYN 及相应的 ACK 时,状态都变迁为 ESTABLISHED。

一个同时打开的连接需要交换 4个报文段, 比正常的三次握手多一个。

同时关闭

双方都执行主动关闭,TCP协议也允许这样的同时关闭( simultaneous close)
TCP/IP详解10-传输层:TCP:传输控制协议

复位报文段

TCP 首部中的 RST 比特是用于“复位”的。一般说来,无论何时一个报文段发往基准的连接( referenced connection)(由四元组指明的连接)出现错误,TCP 都会发出一个复位报文段。

产生复位报文段的情况:

  • 到不存在的端口的连接请求:当连接请求到达时,目的端口没有进程正在听。对于 UDP,当一个数据报到达目的端口时,该端口没在使用,它将产生一个 ICMP端口不可达的信息。而TCP则使用复位。
  • 异常终止一个连接:需要注意的是 RST报文段不会导致另一端产生任何响 应,另一端根本不进行确认。收到RST的一方将终止该连接,并通知应用层连接复位。

半打开连接:如果一方已经关闭或异常终止连接而另一方却还不知道,我们将这样的 TCP 连接称为半打开(Half-Open)的。

2.3 例子

参见书18.2节

2.4 TCP 服务器的设计

前面曾经讲述过UDP的服务器设计,可以发现UDP的服务器完全不需要所谓的并发机制,它只要建立一个数据输入队列就可以。但是TCP不同,TCP服务器对于每一个连接都需要建立一个独立的进程(或者是轻量级的,线程),来保证对话的独立性。所以TCP服务器是并发的。而且TCP还需要配备一个呼入连接请求队列(UDP服务器也同样不需要),来为每一个连接请求建立对话进程,这也就是为什么各种TCP服务器都有一个最大连接数的原因。而根据源主机的IP和端口号码,服务器可以很轻松的区别出不同的会话,来进行数据的分发。