【网络】计算机网络笔记——TCP协议简要笔记

TCP 在 IP 协议不可靠(尽力而为也就是无服务)之上建立了可靠的数据传输服务。它确保了发送方发送的字节流与接收方所接收到的字节流是完全相同的。那么它又是如何实现的呢?

累积确认机制

首先,我们需要清楚,TCP 报文段中具有两个字段——序号确认号

其中序号代表了这个报文段首字节的字节编号。其中,数据流的初始序号其实是随机选择的。

而对于确认号,则代表了接收方期望从发送方所收到的下一个字节的编号。

例如如假设接收方接收到了 723-773 以及 920-970 这两个报文段,但仍未收到 774-919 这段数据,则接收方会在确认号中填入 774,代表接收方下一次想从发送方接收到 774 开始的数据。

由于 774-919 这段数据是丢失了的,因此 TCP 不会确认在其之前先收到的这段失序的 920-970 报文段。对于这段失序报文段的处理,TCP 并没有规定。可能有如下的两种实现:

  1. 直接丢弃。
  2. 保留,等待缺少的字节填充间隔。

由于后者对网络更有利,因此通常采用的是后者的实现。

而这种对丢失数据后面的数据不再确认的机制,就是 TCP 的累积确认机制

超时重传机制

TCP 仅仅使用了一个单一的定时器,从而减少定时器管理的开销。在一个报文段发送时,如果这个定时器还没有被其他报文段所关联,则该报文段传给 IP 层时则会启动该定时器,我们可以理解为定时器与最早未被确认的报文段关联

而如果定时器的时间超过了根据算法所计算的时间间隔时,则代表发生了超时。TCP 是通过重传引起超时的报文段来处理超时事件的,之后便重启定时器。由于其具有累积确认机制,因此这个引起超时的报文段当然是还未被确认的第一个报文段。

接收 ACK

当 TCP 收到来自接收方的 ACK 时,假设其值为 n,则它会将 n 与它的 SendBase(首个未被确认的字节编号)进行比较,如果 n > SendBase,由于 TCP 采用了累计确认机制,因此说明 n 之前的所有字节都已被确认,因此 TCP 会将 SendBase 替换为 n 值,此时如果仍有未被确认的报文段,则重启定时器。

快速重传机制

如果每次报文段丢失后都是在等到超时后再进行重传,无疑是有些浪费时间的。因此 TCP 还采用了一种快速重传机制。如果接收方收到了一个序号大于下一个期望序号的报文段时,它就发现了报文段丢失,此时由于没有否定确认,因此接收方会向发送方发送一个对已经接收到的最后一个字节的重复 ACK,从而告诉发送方发生了丢包。当发送方收到了 3 个冗余 ACK 时,说明这个被确认过 3 次的报文段之后的报文段已丢失,此时发送方就会执行快速重传,也就是在定时器超时之前重传丢失报文段。

通过这样的快速重传机制,可以在一些情况下更快地处理丢失报文段。

流量控制机制

TCP 连接为每一侧的主机都会设置一个接收缓存。当收到正确、按序的字节时,都会讲数据存入接收缓存,而上层则会从缓存中读取数据。但这件事并不是到达后就立即读取。这时如果发送方发送的数据太多、太快,则有可能导致接收缓存溢出。

为了避免这种情况, TCP 为其应用程序提供了流量控制服务,从而消除这种缓存溢出的可能性。

TCP 通过让发送方维护了一个接收窗口,从而提供流量控制。由于其是全双工,因此双方都维护了一个接收窗口,假设 a 代表了接收方应用程序从接收缓存中读取的最后一个字节编号,b 代表了放入接收方接收缓存的最后一个字节编号,而 n 代表了接收方的接收缓存大小。则我们的接收窗口大小 rwnd = n - (b - a),也就是接收缓存中的剩余大小。

TCP 利用上述接收窗口的机制,从接收方通过将当前 rwnd 值放入发送给发送方的报文段中接收窗口字段。从而通知发送方其在接收缓存中还剩多少空间。

但上述有一个问题是,如果接收方接收缓存存满,已通知发送方后,发送方没有数据再发送给接收方,则接收方无法再通知发送方其接收缓存空间充足,这可能导致发送方无法向接收方发送数据。为了解决这个问题,如果接收方的接受窗口为 0 时,发送方会继续发送只有一个字节的报文段,直到接收方的 rwnd 不为 0。

连接建立(三次握手)

在 TCP 连接传输数据之前,首先要在两端之间建立起一条 TCP 连接,其步骤通常如下:

  • 第一步:客户端向服务端发送一个特殊的 TCP 报文段,其不包含应用层数据,但 SYN 字段被设为了 1。这种报文段称为 SYN 报文段。同时,客户会随机选择一个初始序号(client_isn),置于 SYN 报文段的序号字段,发送给服务器。
  • 第二步:SYN 报文段的 IP 数据报到达主机后,会从其中提取出 SYN 报文段,为该 TCP 连接分配缓存及变量,并向客户端发送允许连接报文段(其实一般都不是在第二步分配缓存,为了防止 SYN 洪泛拒绝服务攻击),该报文段通常被称为 SYNACK 报文段。SYNACK 报文段不包含应用层数据,但首部包含了三个重要信息:
    • SYN 字段置为 1
    • 确认号字段被置为 client_isn + 1
    • 选择服务器的初始序号置于序号字段(server_isn)
  • 第三步:收到 SYNACK 报文段后,客户端为该连接分配缓存及变量,客户主机向服务器发送另一个报文段,此报文段对服务器 SYNACK 报文段进行了确认(将 server_isn + 1 放入了确认号)。由于连接已建立,因此 SYN 为 0,此报文段可以携带客户端向服务端发送的数据。

可以发现,TCP 的连接总共有三步,伴随着三次报文段的发送。因此这个过程通常也被称为『三次握手』。

SYN 洪泛攻击及解决方案

如果有恶意机器发送大量的 SYN 报文段,但不完成第三次握手,则服务器会不断为这种半开的连接分配资源(建立缓存,分配变量),从而导致连接资源被消耗殆尽。因此出现了一种叫做 SYN cookie 的防御系统。

它是这样工作的:

  • 服务器收到 SYN 报文段时,不会为该报文段生成半开连接,而是生成一个初始 TCP 序列号,基于源、目的 IP及端口号及一个该服务器独有的秘密数通过一个复杂的算法而产生的,被称为 「cookie」。服务器会发送带有这种 cookie 的 SYNACK 分组。**但服务器不会记忆该 cookie **
  • 如果客户合法,会返回一个 ACK 报文段,其确认号为之前的 cookie 值 + 1,而在服务器则会对源、目的 IP及端口号及一个该服务器独有的秘密数进行一次同样的运算,如果与 ACK 中的值 - 1 相同的话,则会认为其是合法的,生成一个具有套接字的全开连接。

TCP 为什么是三次握手,而不是四次或两次呢

为什么不是两次握手

如果我们仅仅进行了两次握手,则服务端发送了 SYNACK 报文段后并不会收到客户端的确认,也就是说它只能自动认为客户端收到了这个 SYNACK 报文段。但客户端很有可能并未收到该报文段,此时客户端认为连接未建立,而服务端则已经为这条连接分配了资源。如果出现大量的此类情况,则服务端很有可能会崩溃。

为什么不是四次握手

其实按道理来说,应该是客户端发送 SYN 报文段,服务端收到以后,发送 ACK 报文段,接着发送一个 SYN 报文段。客户端收到后,向服务端发送 ACK 报文段。这样双方都能够确认对方收到了自己的 SYN 报文段。但为什么没有这样设计呢?

很显然,这样的四次握手的第 2、3 步都是从服务端发出,且它们所需要用到的数据字段并没有交叉。因此我们完全可以将这两次发送的过程合为一次。

因此,三次握手就足够了。

三次握手中如果 SYNACK 丢失了怎么处理

我们知道,当服务端收到 SYN 报文段后,会发送 SYNACK 报文段给客户端,但如果这个 SYNACK 报文段丢失了该怎么办呢?

服务端在发送 SYNACK 报文段后其实会进入 SYN_RCVD 状态,这个 SYNACK 报文段也是会出现超时的现象的,一旦超时,则服务端会重发该报文段。但如果重发了超过设定的次数,服务端仍未收到 ACK 报文段的话,服务端会自动关闭该连接。

但我们知道,此时客户端仍无法感知到连接的关闭。因此服务端还会向客户端发送 RST 报文段,让客户端感知到并关闭此连接。

三次握手的作用

三次握手有很多作用,如:

1、确认双方的接受能力、发送能力是否正常。

2、指定自己的初始化序列号,为后面的可靠传输做准备。

3、对于 HTTPS 协议,三次握手过程还会进行数字证书的验证以及加密密钥的生成。

连接拆除(四次挥手)

对于 TCP 连接,两个进程的任何一个都可以主动终止这个连接。比如假设客户端想要关闭应用,则会经过下面的几步:

  • 第一步:客户端向服务端发送一个特殊的报文段,其 FIN 字段会被设置为 1。代表客户端想要关闭此次连接。
  • 第二步:服务端收到该 FIN 报文段后,会向客户端发送一个 ACK 报文段。
  • 第三步:服务端向客户端发送一个特殊的报文段,其 FIN 字段被设置为 1 ,代表客户端关闭此次连接。

  • 第四步:客户端收到该 FIN 报文段后,会向服务端发送一个 ACK 报文段。

为什么不是三次挥手

之前提到,四次握手可以将第二步、第三步合为一步,服务端发送 SYN 报文段的同时也发送了 ACK 报文段,那为什么在四次挥手时又不能将第二步与第三步合为一步,在发送 ACK 的同时发送 FIN 了呢?

因为当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYNACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但关闭连接时,服务端收到 FIN 报文段时,并不会立即关闭 Socket,因此仅发送了一个 ACK 报文段,应答客户端。只有等到服务端的报文发送完毕后,它才会发送 FIN 报文段,告诉客户端自己要关闭连接。

因此,才需要四次挥手。

拥塞控制机制

网络拥塞,说明网络中资源的需求超过了可用的资源。若网络中许多资源同时供应不足,网络的性能就要明显变差,整个网络的吞吐量随之负荷的增大而下降。

为了在网络拥塞的情况下,使得网络中的路由器或链路不至于过载,我们需要防止过多的数据进入网络中,因此需要对这种情况进行控制,这就是所谓的拥塞控制

如何限制连接发送数据的速率

TCP 除了接收窗口 rwnd 外,其实还存在一种名为 cwnd 的拥塞窗口。在 TCP 的发送方中,其未被确认的数据量不会超过 rwnd 与 cwnd 中的最小值。

这种方法限制了未被确认数据流的最小值,从而间接限制了其发送速率。

如何感知发生了拥塞

如果在 TCP 中发生了超时或是收到了三个冗余 ACK,则说明发生了丢包事件。我们知道,如果过度拥塞时,会导致网络中的某些路由器缓存溢出,从而丢弃数据报。这就会引起发送方的丢包事件,这种丢包事件就是对发送方发生了丢包事件的一种指示。

使用这种方法的主要原因还是由于并不是所有网络层的协议都提供了完备的服务,它们不是所有都能向运输层提供拥塞控制的指示,比如 IP 协议就提供了一种『尽力而为服务』(实际上就是没有服务)。因此 TCP 通过这种方式来实现了拥塞控制的感知。

TCP拥塞控制算法

TCP 拥塞控制算法主要有三个部分:1. 慢启动、2. 拥塞避免、3.快速恢复,其中慢启动和拥塞避免是 TCP 的强制部分,而快速恢复对于 TCP 来说并不是必需的。

慢启动

TCP 连接开始时,会将 cwnd 设置为一个很小的值(1)。每当运输的报文段被确认,就增加 1 ,这样下去,每经过一次 RTT,则发送速率就会翻一倍。

因此, TCP 发送速率的起始速度是很慢的,但在慢启动阶段其发送速率会以指数级增长。

但这种指数级增长何时结束呢?

  • 当发生超时引起的丢包事件时,TCP 发送方会将 cwnd 重新设置为 1 并重新开始慢启动,并且,还会将一个名为 ssthresh(慢启动阈值)的值设置为之前 cwnd 的一半。
  • 当cwnd 到达或超过 ssthresh 的值时,继续采用这种指数增长的方式显然不是很合适,此时会进入更为保守的拥塞避免模式

拥塞避免

进入拥塞避免模式时,cwnd 的值大约为 ssthresh,即上次拥塞时 cwnd 值的一半。因此这个阶段距离拥塞可能并不遥远,TCP 不会再将 cwnd 的值翻一倍,而是每经过一次 RTT, cwnd 的值仅增加 1(也就是如果发送 n 个报文段,则每次确认只增加 1/n)

这种线性增长又是何时结束呢?

  • 当出现了超时引起的丢包事件时,与慢启动类似,cwnd 的值又会被设置为 1。
  • 当出现了三次冗余 ACK 引起的丢包事件时,则 ssthresh 的值会被更新为当前 cwnd 的一半。之后则进入了快速恢复状态。

快速恢复

快速恢复的思想是『数据包守恒』原则,即同一个时刻在网络中的数据包数量是恒定的:只有当“老”数据包离开了网络后,才能向网络中发送一个“新”的数据包。如果发送方收到一个重复的 ACK,则根据 TCP 的 ACK 机制表明有一个数据包离开了网络,于是 cwnd 加 1。

点赞

发表评论

电子邮件地址不会被公开。必填项已用 * 标注

%d 博主赞过: