TCP/IP(6)--TCP协议详解

上一篇文章已经介绍了TCP和UDP报文的首部,这一篇详细解析TCP中确保可靠传输的方法。

TCP是如何确保可靠传输的?

为了保证可靠传输,TCP比UDP多了很多控制协议和算法。

  • 连接管理——3次握手和4次握手
  • 数据破坏——通过校验和
  • 丢包——应答与超时重发机制
  • 分片乱序——序列号
  • 窗口滑动——提高发送效率,对发送端和接收端流量进行控制
  • 加快通信速度——快速重发,三次收到重发消息进行重发
  • 流控制——避免网络流量浪费
  • 拥塞控制——慢启动算法,拥塞窗口

TCP中的确认应答机制

在TCP中当发送端的数据达到接受主机时,接受主机端都会返回一个消息,告诉对方我已经收到了。这个消息叫确认应答。发送确认应答时,TCP首部中的ACK标志位设1。

TCP中的确认应答通过序列号和确认应答号来实现。如下图所示,主机A发送第一个包时序列号为1,数据长度为1000,那么主机B收到包后发送一个数据包给主机A,在该包中将TCP首部中的32位确认号设为1001.相当于告诉对方1001之前的我都收到了,下次从1001开始发。
TCP/IP(6)--TCP协议详解_第1张图片

经受时延的确认应答:为了降低确认应答包的数量,TCP提出了经受时延的确认应答。接受端在收到数据后并不立即发送一个应答数据包,而是等待一段时间,如果有新的数据被接受就更新应答号,如果有其他数据要发送就坐上该数据包的顺风车。在系统的内核中维持了一个定时器,一般是200ms如果定时器溢出,即使没有其他数据到达,也发送该应答数据包。

Nagle算法: TCP是基于流的传输协议,在Rlogin和Telnet传输中会出现只有一个字节数据的TCP数据包。而一个TCP数据包的首部加上IP首部就有40个字节,很显然发这样的数据包划不来。为了减少这样的数据包,有人提出了Nagle算法。

Nagle算法简单讲就是,等待服务器应答包到达后,再发送下一个数据包。数据在发送端被缓存,如果缓存到达指定大小就将其发送,或者上一个数据的应答包到达,将缓存区一次性全部发送。

Nagle算法是从发送端角度考虑减少了数据包的个数,时延应答从接收端角度考虑减少了数据包的个数。

TCP的连接与断开(3次握手和4次握手)

TCP/IP(6)--TCP协议详解_第2张图片

  • 建立连接

    1. 客户端发送请求包,告诉服务器:“我想和你通信?”数据包中SYN位置为1,假设其序列号为x,客户端状态变成SYN_SENT;
    2. 服务器端接受到请求包后也发送一个请求包,告诉客户端:“现在可以建立连接”。数据包中SYN位置位1,假设其序列号为y,注意客户端序列号和服务器端序列号并没有关系,他们是由各自的内核按照一定的规则生成的。但是这个应答包的32位应答号,必须是x+1,之所以加1是因为客户端发过来的包SYN位被认为占一个数据。因此,告诉下一包从x+1开始发。发送后,服务器从监听状态变成SYN_RCVD状态。
    3. 客户端发送应答数据包,告诉服务器:“那我们开始发送数据吧”。数据包应答号为y+1。客户端变成ESTABLISHED状态,即可以传输状态。
    4. 服务器端接受到应答数据包后,变成ESTABLISHED状态。
  • 发送数据

    1. 客户端发送一个一个字节的数据,因此序列号为x+1;
    2. 服务端发送一个应答包,应答号为x+2,告诉客户端下次从x+2开始发;
  • 断开连接

    1. 客户端发送请求断开的数据包,告诉服务器:“数据传完了,我要断开了”。发送一个FIN包,序列号x+2。客户端转移到FIN_WAIT_1状态。
    2. 服务器端发送应答包,告诉客户端:“行,我知道了,你断开吧!”。应答号为x+3,服务器进入CLOSE_WAIT状态。客户端收到应答后,转移到FIN_WAIT_2状态。
    3. 服务器发送一个断开数据包,告诉客户端:“既然传完了,那我这边的开关也准备关了”。序列号为y+1,发送完后服务器进入LAST_ACK状态。
    4. 客户端发送一个应答包,告诉服务器:“好的,我知道你要断开了。”应答号为y+2。客户端进入TIME_WAIT状态。
      TIME_WAIT又称为2MSL等待状态,MSL是系统中定义的最大报文生存时间,任何TCP报文在网络中生存时间超过这个值就必须被丢弃。
      等待MSL的原因是防止最后一个ACK丢失后可以进行重发,如果ACK丢失后,服务器会重发FIN。

TCP状态迁移图:
TCP/IP(6)--TCP协议详解_第3张图片

其中:CLOSING状态是同时打开才发生的

为什么是3次和4次

为什么是3次

可能你会认为第3次好像是多余的。是因为信道是不可靠的,可能存在延时或者丢包,而三次是满足可靠传输的最小次数。

举例说明:如果只有两次,假设主机A发送的第一个请求包延时,主机A在等待一段时间后重新发送一个请求包,完成数据连接并断开。但是这个时候上次的发的请求包才到达主机B,这时主机B认为是又一次连接,因此发送一个请求包给A,但是A并没有发送新的请求因此会丢失该数据包。最后,B就一直等待A发送数据,浪费了资源。

除此之外,我个人认为3次握手更加安全,加大了攻击的难度。如果只有两次,一个发送一个应答,那么攻击着可以采用IP欺骗,发动SYN洪水攻击,并且服务端还都是ESTABLISHED状态。如何防御?难度更大了。对于三次握手的可以限制半连接的数量来达到一个防御的作用。

为什么是4次

TCP通信是一种全双工的通信,可以进行半关闭(与半打开区别:半打开是连接后的客户端和服务端有一端异常关闭了),所谓半关闭是指可以只关闭从A到B的方向,而B到A的方向还可以继续传输。因此,在客户端和服务器端分别进行关闭。

TCP窗口

窗口是TCP中为了解决应答机制等待时间过长而引入的方法,如果没有窗口,则TCP每发送一次数据就必须等待应答,收到应答后继续发送,如果没有收到则等待一段时间后重发,如果很长时间都无法收到应答则判断为网络断开。而使用窗口后,窗口的大小指无需等待应答可以连续发送多个数据包。

下图中窗口的大小为4000,一次性发送了4个数据包,每个数据包大小为1000。
TCP/IP(6)--TCP协议详解_第4张图片

TCP窗口在每个传输方向都有两个窗口,发送端窗口和接受端窗口,又因为TCP是全双工通信,因此有四个窗口。

发送端窗口
TCP/IP(6)--TCP协议详解_第5张图片

接收端窗口
TCP/IP(6)--TCP协议详解_第6张图片

发送端窗口分为已经发送但是未接到回应的部分和可以被发送的部分
接收端端口大小 = 被分配缓存区 - 已接受确认等待被进程消耗的区域,发送端窗口的大小有ACK应答包中的窗口大小确认。

窗口滑动

TCP/IP(6)--TCP协议详解_第7张图片
发送端窗口,根据应答包的确认号确定窗口的位置,根据应答包中窗口的大小确定窗口的大小,窗口是从左向右逐渐滑动。

窗口合拢:由左端边缘向右靠近,称为窗口合拢,在接受到数据后发生。
窗口张开:右右端向右移动,称为窗口打开,在处理完数据后发生。

如果接收端发送的应答包中窗口大小为0,则客户端会等待一段时间后发送探测包,重新确认窗口的大小。接受端如果处理完了数据也会重新发送应答包,通知发送端。反正死锁的发生。

引入窗口后,TCP的应答包如果部分丢失,无需重传,由后面的应答包保证。TCP为了提高效率,采用延时再确认应答,和选择性确认应答,即收到数据包后不立即发送应答包,而是等待收到下一个或多个包后发一个应答。

超时和重传

TCP是可靠的传输协议,意味着必须按序,无差错的传送数据和目的端。通过校验和,确认应答,重传来保证。校验和应答已经介绍过,这里主要讲解重传机制。重传分为两种:超时重传和快速重传。
超时重传(RTO)
当一个包被发送后,就开启一个定时器,如果定时时间到了,还未收到能确认该发送包的应答包,就重传一份数据。注意收到的应答包可能是该包也可能是后面包的,但是只要能确认该包被收到就行。另外如果,是因为网络延时造成重传,则接受端收到重复数据包后丢弃该包。
快速重传
当如果发送端收到一个包的三次应答包后,立即重传,比超时重传更高效。
TCP/IP(6)--TCP协议详解_第8张图片

拥塞控制

流量是根据发送方和接受方的缓冲区大小来确定的,保证数据处理不出现拥堵,具体实现方法是窗口滑动。而拥塞机制是考虑到网络中的拥堵,比如路由器要处理的数据过多,导致缓冲区溢出而丢包。而拥塞处理就是来解决这种情况,避免网络出现过载的现象。

判定拥塞出现的条件:网络中出现分组丢失(发生超时或收到重复确认)

拥塞避免算法中用到了慢启动快速重传快速恢复
拥塞避免算法需要维持两个变量:拥塞窗口慢启动阀值

慢启动算法(工作过程如下图所示):设置初始拥塞窗口大小为1,以后每收到一个应答拥塞窗口大小就加1(图中指定一个窗口大小是1000个字节),窗口大小呈指数级增长。客户端可发送数据的取拥塞窗口和应答包窗口两者中较小的那个。
TCP/IP(6)--TCP协议详解_第9张图片

拥塞避免算法:拥塞避免算法与慢启动的区别在于,收到一个应答后,拥塞窗口大小cwnd只增加1/cwnd。窗口大小整体呈现线性增长。

拥塞控制算法:

拥塞控制算法先采用慢启动算法,到达慢启动阀值后采用拥塞避免算法。

  1. 通信开始时,发送方的拥塞窗口大小为 1。每收到一个 ACK 确认后,拥塞窗口大小加1。
  2. 由于指数级增长非常快,很快地,就会出现确认包超时,认为发生了拥塞。
  3. 此时设置一个“慢启动阈值”,它的值是当前拥塞窗口大小的一半。
  4. 拥堵发生后将拥塞窗口大小设置为 1,重新进入慢启动过程。
  5. 由于现在“慢启动阈值”已经存在,当拥塞窗口大小达到阈值后,停止使用慢启动算法,开始采用拥塞避免算法。窗口大小开始线性增加。
  6. 随着窗口大小不断增加,如果收到三次重复确认应答,则进入“快速重发”阶段。对于这用拥塞情况,TCP 将“慢启动阈值”设置为当前拥塞窗口大小的一半,再将拥塞窗口大小设置成阈值大小(也有说加 3)。然后采用拥塞避免算法增加窗口大小。
  7. 随着窗口大小不断增加,如果发生超时。对于这种拥塞情况,TCP将满启动阀值设置为当前拥塞窗口的一半,然后将拥塞窗口设置为1。

下图为拥塞控制过程中,拥塞窗口的大小变化图。有了拥塞窗口后,实际窗口的大小为拥塞窗口和ACK包中传递的窗口大小两个中的最小值。
TCP/IP(6)--TCP协议详解_第10张图片

坚持定时器

TCP不对ACK应答报文进行确认,如果接受端缓冲被占满,发送一个窗口为0的应答,过了一段时间数据处理完毕,重新发送一个应答,告诉发送端窗口大小。不幸的是,如果这个包丢了,就会进入死锁状态——发送端等待更新窗口的应答包,接收端等待接收数据。

为了避免死锁了发生,TCP使用了一个坚持定时器来周期性地向接收方查询,以便发现窗口是否已经增大。这一过程也被称为窗口探查

参考文献:

[1]http://www.jianshu.com/p/d9edbba4035b
[2]图解TCP/IP第五版
[3]计算机网络教程:自顶向下方法(第五版)
[4]TCP/IP详解卷一

你可能感兴趣的:(TCP-IP)