TCP四次挥手详解

来源:互联网 发布:myeclipse php 编辑:程序博客网 时间:2024/06/05 09:54

TCP四次挥手

1.前言

  • 关于 TCP 三握手以及后面文章用到的东西可以查看我的这篇博文:三次握手

2.四次挥手

  1. 所谓四次挥手(Four-Way Wavehand)即终止 TCP 连接,就是指数据传送完毕需要断开一个 TCP 连接时,需要客户端和服务端总共发送 4 个包以确认连接的断开。在 socket 编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:

四次挥手

  1. 由于 TCP 连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个FIN只意味着这一方向上没有数据流动,一个 TCP 连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。

    1. 第一次挥手(FIN=1,seq=u)

      1. 假设客户端想要关闭连接,客户端发送一个FIN标志位置为 1 的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。因为有可能服务器还要发送数据,所以发送自己的序列号seq=u,等待服务器确认。
      2. 发送完毕后,客户端进入FIN_WAIT_1状态。
    2. 第二次挥手(ACK=1,seq=v,ack=u+1)

      1. 服务器端确认客户端的FIN包,发送一个确认包(ACK=1(确认),seq=v(自己的序列号),ack=u+1(确认收到序列号u以前的包,并希望下次发送数据从 u+1 开始)),表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接(可能有数据发送)。
      2. 发送完毕后,服务器端进入CLOSE_WAIT状态,客户端接收到这个确认包之后,进入FIN_WAIT_2状态,等待服务器端关闭连接。
      3. 在这过程中 TCP 服务器进程会通知高层应用进程。然后从客户端到服务器这个方向的连接就释放了,TCP 连接处于半关闭状态。但是服务器 若发送数据,客户端仍要接收。
    3. 第三次挥手(FIN=1,ACK=1,seq=w,ack=u+1)

      1. 若服务器已经没有要向客户端发送的数据,其应用进程就通知 TCP 释放连接。
      2. 服务器端准备好关闭连接时,向客户端发送结束连接请求FIN置为 1,ACK=1seq=w(有数据发送过),ack=u+1
      3. 发送完毕后,服务器端进入LAST_ACK状态,等待来自客户端的最后一个ACK
    4. 第四次挥手(ACK=1,seq=u+1,ack=w+1)

      1. 客户端接收到来自服务器端的关闭请求,发送一个确认包(在确认报文段中ACK=1,确认号ack=w+1,自己的序号seq=u+1),并进入TIME_WAIT状态,等待可能出现的要求重传的ACK包。(这个确认包是内核进行发送的,上面不能发数据的是客户端的send函数)
      2. 服务器端接收到这个确认包之后,关闭连接,进入CLOSED状态。
      3. 客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的ACK,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入CLOSED状态。

3.问题

  1. 为什么关闭的时候是四次握手?

    1. 当关闭连接时,服务器端收到了客户端的FIN报文通知,这仅仅表示客户端没有数据发送给服务器端了,我们知道 TCP 是全双工通信,所以未必服务器端的全部数据发送给了客户端,所以服务器端未必会马上关闭 socket ,也许服务器端还需要发送一些数据给客户端之后,再发送FIN报文给客户端,表示同意现在关闭连接,所以服务器端的ACK报文和FIN报文大多数情况下都是分开发送的。
  2. TIME_WAIT存在的理由

    1. 可靠的实现TCP全双工链接的终止。

    1. 虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
    2. 如果客户端不维护这个状态信息,服务器将响应一个以RST(另一种类型的 TCP 分节),该分节将被解释为一个错误。如果TCP想要执行所有必要的工作以彻底终止某个连接上的两个方向上的数据流(全双工关闭),那它必须正确处理连接终止序列 4 个分节中任何一个分节丢失的情况。
    3. 允许老的重复的分节在网络中消逝。
    4. 假设在12.106.32.254的 1500 端口和206.168.1.112.219的 21 端口之间有一个 TCP 连接。我们关闭这个链接,过一段时间后在 相同的 IP 地址和端口建立另一个连接。后一个链接成为前一个的化身。因为它们的 IP 地址和端口号都相同。TCP 必须防止来自某一个连接的老的重复分组在连接已经终止后再现,从而被误解成属于同一链接的某一个某一个新的化身。
    5. 为做到上面一点,TCP将不给处于TIME_WAIT状态的链接发起新的化身。既然TIME_WAIT状态的持续时间是MSL的 2倍,这就足以让某个方向上的分组最多存活MSL秒即被丢弃,另一个方向上的应答最多存活MSL秒也被丢弃。 通过实施这个规则,我们就能保证每成功建立一个TCP连接时,来自该链接先前化身的重复分组都已经在网络中消逝了。
  • 为什么在第三次挥手时需要发送ack和seq

    1. 因为 TCP 是可靠的全双工传输,所以需要确保客户端的链接关闭正确,并且防止传送的数据包在网络中延迟出现的错误,如果后面又使用同样的端口建立了一个 TCP 链接而且现在要释放(完成了两次挥手),刚才延迟的包现在到了,这时也许服务器还有数据要发送,但是客户端收到延迟的包,就直接确认返回ACK,从而出现非正常关闭。
  • TIME_WAIT状态所带来的影响

    1. 当某个连接的一端处于TIME_WAIT状态时,该连接将不能再被使用。事实上,对于我们比较有现实意义的是,这个端口将不能再被使用。
    2. 某个端口处于TIME_WAIT状态(其实应该是这个连接)时,这意味着这个 TCP 连接并没有断开(完全断开),那么,如果你bind这个端口,就会失败。对于服务器而言,如果服务器突然坏掉了,那么它将无法再2MSL内重新启动,因为bind会失败。
    3. 解决这个问题的一个方法就是设置 socket 的SO_REUSEADDR选项。这个选项意味着你可以重用一个地址。
  • 4.细节

    1. 默认情况下(不改变 socket 选项),当你调用 close ( or closesocket,以下说 close 不再重复)时,如果发送缓冲中还有数据,TCP会继续把数据发送完。

    2. 发送了FIN只是表示这端不能继续发送数据(应用层不能再调用send发送),但是还可以接收数据。

    3. 应用层如何知道对端关闭?通常,在最简单的阻塞模型中,当你调用recv时,如果返回 0,则表示对端关闭。在这个时候通常的做法就是也调用close,那么 TCP 层就发送FIN,继续完成四次握手。如果你不调用close,那么对端就会处于FIN_WAIT_2状态,而本端则会处于CLOSE_WAIT状态。

    4. 在很多时候,TCP连接的断开都会由TCP层自动进行,例如你CTRL+C终止你的程序,TCP连接依然会正常关闭。

    5. 当 TCP 连接发生一些物理上的意外情况时,例如网线断开,linux 上的 TCP 实现会依然认为该连接有效,而 windows 则会在一定时间后返回错误信息。

    5.补充

    1. 2MSL

      1. MSL(Maximum Segment Lifetime),也就是报文最大生存时间,引用《TCP/IP详解》中的话:“它(MSL)是任何报文段被丢弃前在网络内的最长时间。”那么,2MSL也就是这个时间的 2 倍。RFC 1122建议 MSL 的值为 2 分钟,不过源自Berkeley的实现传统上改用30秒这个值。(也就是说TIME_WAIT状态的持续时间在1-4分钟之间。)
    2. TTL

      1. TTL是网络数据包为了防止数据包在网络中无限循环,而设定的网络数据包在网络传输中的最大的转发次数,因为每转发一次在路由器,就会转向下一跳,通常称为最大跳数。
      2. 具体含义即就是:我们本地机器发出一个数据包,数据包经一定数量路由器后传送到目的主机,但由于多种原因,一些数据包不能正常传送到目的主机,那如果不给这些数据包一个生存时间的话,这些数据包就会在网络上不断的传送,导致网络开销的增大。当数据包传送到一个路由器之后,TTL就自动减 1,如果减到 0 了还没有传送到目的主机,那么数据就会自动消失,发送数据的一方则请求超时。
      3. TTL的各个系统的默认值可以参考:TTL默认值

    5.TCP的有限状态机

    有限状态机

    • 解释:

      1. TCP 有限状态机的图中每一个方框都是 TCP 可能具有的状态。
      2. 每个方框中的大写英文字符串是 TCP 标准所使用的 TCP 连接状态名。状态之间的箭头表示可能发生的状态变迁。
      3. 箭头旁边的字,表明引起这种变迁的原因,或表明发生状态变迁后又出现什么动作。
      4. 图中有三种不同的箭头。

      1. 粗实线箭头表示对客户进程的正常变迁。
      2. 粗虚线箭头表示对服务器进程的正常变迁。
      3. 另一种细线箭头表示异常变迁。

    6.参考

    • 链接:http://www.cnblogs.com/renyuan/p/3431022.html
    • 链接:http://www.cnblogs.com/zmlctt/p/3690998.html
    • 链接:http://blog.csdn.net/gc348342215/article/details/70230537
    • 《UNIX网络编程卷1》
    • 《计算机网络》(谢希仁)