计算机网络-TCP三次握手与四次挥手

TCP是可靠协议?

很明确地说,从通信意义上推敲,TCP一点都不可靠。一个抽象的协议,怎么可能左右介质来保证可靠,不存在的。但凡是经由某种介质的通信行为均不可能是绝对可靠的!

正好比我们现实生活中的保险,其实它什么都不能阻止,什么风险也保证不了它的不发生,它保证不了飞机不会掉下来,也无法阻止人生病…事实上,TCP就是通信中的保险业。

参考两军问题我们知道网络通信中存在一致性确认问题,通信双方都要保证数据一致性。但是一致性是不可能的,谁也无法保证数据在传输过程中是否丢失。那么通信技术还有什么意义呢?

那么信道到底不可靠到什么程度?是100%不可靠吗?如果是的话,意味着断路,即双方是不可达的,无论我们发送多少次数据包,均会丢失,这样我们马上可以结束这个没有意义的讨论,因此,所谓的不可靠只是说信道会出现概率性丢包,丢包概率pp一定是介于开区间(0,1)之间的!

这个意义十分重大,这意味着,只要我们重试消息的次数足够多,就一定能收到来自对端针对消息的确认!,这是完全确定的一个结论。

事实上,通信协议从来都不是为了满足完全的一致性需求,其次,通信传输的是字节电脉冲,消息可以重发,这使得我们的TCP协议在信道传输不可靠程度上得到缓和,也就是我们买保险一样,规避损失。

TCP为何要三次握手

TCP为什么是3次握手,而不是2次,也不是4次,5次呢?所谓的TCP建立连接的握手,实质上就是建立一个双向的可靠通信连接,一边一个来回,每一边都自带超时重传来确保可靠性(而不是靠握手的次数)。TCP的3次握手是优化的结果,其实它应该是4次握手,由于是从零开始的建立连接,因此将SYN的ACK以及被动打开的SYN合并成了一个SYN-ACK,仅此而已。

这个问题的本质是, 信道不可靠, 但是通信双发需要就某个问题达成一致. 而要解决这个问题, 无论你在消息中包含什么信息, 三次通信是理论上的最小值. 所以三次握手不是TCP本身的要求, 而是为了满足”在不可靠信道上可靠地传输信息”这一需求所导致的. 请注意这里的本质需求,信道不可靠, 数据传输要可靠. 三次达到了, 那后面你想接着握手也好, 发数据也好, 跟进行可靠信息传输的需求就没关系了. 因此,如果信道是可靠的, 即无论什么时候发出消息, 对方一定能收到, 或者你不关心是否要保证对方收到你的消息, 那就能像UDP那样直接发送消息就可以了”。

TCP请求报文内容

  • Seq:序号,用来标识TCP发端向TCP收端发送的数据字节流,是本报文段发送的数据组的第一个字节的序号,在TCP传送的流中,每一个字节一个序号。一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号Seq=400。序号确保了TCP传输的有序性。
  • Ack:确认序号,即接收到的上一次远端主机传来的seq+1,再发送给远端主机。提示远端主机已经成功接收上一次所有数据。只有ACK标志位为1时,Ack才有效。
  • ACK:确认序号有效
  • SYN:同步,在连接建立时用来同步序号。当SYN=1而ACK=0时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使SYN=1和ACK=1. 因此,SYN置1就表示这是一个连接请求或连接接受报文。
  • FIN:当 FIN = 1 时,表明此报文段的发送方的数据已经发送完毕,并要求释放连接。

TCP11种状态

  1. CLOSED:初始状态,表示TCP连接是“关闭着的”或“未打开的”。
  2. LISTEN :表示服务器端的某个SOCKET处于监听状态,可以接受客户端的连接。
  3. SYN_RCVD :表示服务器接收到了来自客户端请求连接的SYN报文。在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat很难看到这种状态,除非故意写一个监测程序,将三次TCP握手过程中最后一个ACK报文不予发送。当TCP连接处于此状态时,再收到客户端的ACK报文,它就会进入到ESTABLISHED 状态。
  4. SYN_SENT :这个状态与SYN_RCVD 状态相呼应,当客户端SOCKET执行connect()进行连接时,它首先发送SYN报文,然后随即进入到SYN_SENT 状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT 状态表示客户端已发送SYN报文。
  5. ESTABLISHED :表示TCP连接已经成功建立。
  6. FIN_WAIT_1 :这个状态得好好解释一下,其实FIN_WAIT_1 和FIN_WAIT_2 两种状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET进入到FIN_WAIT_1 状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2 状态。当然在实际的正常情况下,无论对方处于任何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1 状态一般是比较难见到的,而FIN_WAIT_2 状态有时仍可以用netstat看到。
  7. FIN_WAIT_2 :上面已经解释了这种状态的由来,实际上FIN_WAIT_2状态下的SOCKET表示半连接,即有一方调用close()主动要求关闭连接。注意:FIN_WAIT_2 是没有超时的(不像TIME_WAIT 状态),这种状态下如果对方不关闭(不配合完成4次挥手过程),那这个 FIN_WAIT_2 状态将一直保持到系统重启,越来越多的FIN_WAIT_2 状态会导致内核crash。
  8. TIME_WAIT :表示收到了对方的FIN报文,并发送出了ACK报文。 TIME_WAIT状态下的TCP连接会等待2*MSL(Max Segment Lifetime,最大分段生存期,指一个TCP报文在Internet上的最长生存时间。每个具体的TCP协议实现都必须选择一个确定的MSL值,RFC 1122建议是2分钟,但BSD传统实现采用了30秒,Linux可以cat /proc/sys/net/ipv4/tcp_fin_timeout看到本机的这个值),然后即可回到CLOSED 可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。(这种情况应该就是四次挥手变成三次挥手的那种情况)
  9. CLOSING :这种状态在实际情况中应该很少见,属于一种比较罕见的例外状态。正常情况下,当一方发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING 状态表示一方发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是当双方几乎在同时close()一个SOCKET的话,就出现了双方同时发送FIN报文的情况,这是就会出现CLOSING 状态,表示双方都正在关闭SOCKET连接。
  10. CLOSE_WAIT :表示正在等待关闭。怎么理解呢?当对方close()一个SOCKET后发送FIN报文给自己,你的系统毫无疑问地将会回应一个ACK报文给对方,此时TCP连接则进入到CLOSE_WAIT状态。接下来呢,你需要检查自己是否还有数据要发送给对方,如果没有的话,那你也就可以close()这个SOCKET并发送FIN报文给对方,即关闭自己到对方这个方向的连接。有数据的话则看程序的策略,继续发送或丢弃。简单地说,当你处于CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。
  11. LAST_ACK :当被动关闭的一方在发送FIN报文后,等待对方的ACK报文的时候,就处于LAST_ACK 状态。当收到对方的ACK报文后,也就可以进入到CLOSED 可用状态了。

TCP三次握手的目的

三次握手的目的是同步连接双方的序列号和确认号并交换 TCP 窗口大小信息。旨在确定两个双向的初始序列号,TCP用序列号来编址传输的字节,由于是两个方向的连接,所以需要两个序列号,握手过程不传输任何字节,仅仅确定初始序列号

TCP三次握手原理

假设客户端为A,服务端为B。

握手 源端:状态 数据报文 目的端:状态
第一次 A:SYN_SENT SYN=1,ACK=0,Ack=无效,Seq=2019 B:空闲

在第一次握手过程中,发起连接请求报文,使得SYN=1,ACK=0,因为ACK=0,导致Ack无效。假设客户端A向服务端B发送请求,报文序号Seq=2019。

发送完毕后,客户端进入SYN_SENT状态,等待服务端接受(服务端此时不知道A发来消息,处于空闲状态)

握手 源端:状态 数据报文 目的端:状态
第二次 B:空闲->SYN_RCVD SYN=1,ACK=1,Ack=2020,Seq=2333 A:SYN_SENT

在第二次握手过程中,服务端接受客户端请求,使得SYN=1,ACK=1,激活Ack,Ack=2019+1=2020。假设服务端B向服务端B发送确认请求,报文序号Seq=2333。

发送完毕后,服务端马上从空闲状态进入SYN_RCVD状态。

握手 源端:状态 数据报文 目的端:状态
第三次 A:TABLISHED SYN=0,ACK=1,Ack=2334,Seq=2021 B:SYN_RCVD->TABLISHED

在第三次握手过程中,客户端接收到来自服务端的确认后,进行数据检测,判断Ack==Seq(第一次客户端自己的序号)+1,ACK==1。满足条件,置得ACK=1,Ack=Seq(第二次握手服务端发送过来的序号)+1,注意此时不再使用SYN了,因为SYN做同步用,能够第三次握手表明前面双方序号确认没问题。

发送完毕后,客户端进入TABLISHED(连接)状态,等待服务端接受,服务端接受后,判断Ack==Seq(第二次握手服务端自己的序号)+1,ACK==1,满足条件,服务端进入TABLISHED状态,连接建立完毕。

TCP三次握手

TCP四次挥手原理

实际上客户端和服务端任何一方都可以来进行CLOSE操作,因为TCP是全双工通信的。仍然指定A为客户端,B为服务端,且设定由客户端执行CLOSE操作请求。

挥手 源端:状态 数据报文 目的端:状态
第一次 A:TABLISHED->FIN_WAIT_1 SYN=1,ACK=0,Ack=无效,FIN=1,Seq=4096 B:TABLISHED

在第一次挥手过程中,客户端发起关闭请求报文,此时不需要同步和确认,所以置SYN=0,ACK=0,Ack=无效,发送FIN=1,假设客户端A向服务端B发送关闭请求,报文序号Seq=4096。

发送完毕后,客户端由TABLISHED连接状态进入FIN_WAIT_1(关闭等待1)状态,等待服务端接受(服务端此时不知道A发来消息,仍然处于连接状态)

挥手 源端:状态 数据报文 目的端:状态
第二次 B:TABLISHED->CLOSE_WAIT SYN=0,ACK=1,Ack=4097,Seq=2333 A:FIN_WAIT_1->FIN_WAIT_2

在第二次挥手过程中,服务端接受客户端请求,不需要请求同步使得SYN=0,该次回应需要确定序号使得ACK=1,激活Ack=4096+1=4097。假设服务端B向客户端A发送确认请求,报文序号Seq=2333。

发送完毕后,服务端马上从连接状态进入CLOSE_WAIT(等待确认关闭)状态。

挥手 源端:状态 数据报文 目的端:状态
第三次 B:CLOSE_WAIT->LAST_ACK SYN=0,ACK=1,Ack=4097,Seq=9999 A:FIN_WAIT_2

在第三次挥手过程中,服务端发送FIN=1,确认服务端可以关闭了,置ACK=1,激活Ack=4096+1=4097,假设服务端B向客户端A发送确认请求,报文序号Seq=9999。

挥手 源端:状态 数据报文 目的端:状态
第四次 A:FIN_WAIT_2->TIME_WAIT SYN=0,ACK=1,Ack=10000,Seq=4097 B:CLOSED

客户端接收到FIN后,进入TIME_WAIT状态,接着向服务端发送一个请求,服务端彻底关闭连接

发送完毕后,连接关闭完成。

TCP四次挥手

TCP面试问题

1. 为什么要TIME_WAIT等待呢?

为了防止这种情况:A接到B的释放连接请求后会发送一个确认信息,但是如果这个确认信息丢了,也就是B没有收到确认释放连接,那么B就会重发一个释放连接请求,这时候A还处于TIME_WAIT状态,所以会再次发送一个确认信息。

2. 为什么TIME_WAIT 状态还需要等2MSL秒之后才能返回到CLOSED 状态呢?

因为虽然双方都同意关闭连接了,而且握手的4个报文也都发送完毕,按理可以直接回到CLOSED 状态(就好比从SYN_SENT 状态到ESTABLISH 状态那样),但是我们必须假想网络是不可靠的,你无法保证你最后发送的ACK报文一定会被对方收到,就是说对方处于LAST_ACK 状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT 状态的作用就是用来重发可能丢失的ACK报文。

3. 为什么不能用两次握手进行连接

3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。

4. 如果已经建立了连接,但是客户端突然出现故障了怎么办

TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75分钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

TCP相比UDP为什么是可靠的

  • 确认和重传机制

    建立连接时三次握手同步双方的“序列号 + 确认号 + 窗口大小信息”,是确认重传、流控的基础传输过程中,如果Checksum校验失败、丢包或延时,发送端重传

  • 数据排序

    TCP有专门的序列号SN字段,可提供数据re-order重排序

  • 流量控制

    窗口和计时器的使用。TCP窗口中会指明双方能够发送接收的最大数据量

  • 拥塞控制

    TCP的拥塞控制由4个核心算法组成。

    1. “慢启动”(Slow Start)
    2. “拥塞避免”(Congestion avoidance)
    3. “快速重传 ”(Fast Retransmit)
    4. “快速恢复”(Fast Recovery)

参考

当我们说”TCP是可靠协议”时,我们真正表达的是什么 tcp为什么要三次握手,tcp为什么可靠 TCP的11种状态