TCP的三次握手和四次挥手

来源:互联网 发布:巴黎 地图 知乎 编辑:程序博客网 时间:2024/06/07 21:02

TCP协议

TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内[1] 另一个重要的传输协议。
应用层向TCP层发送用于网间传输的、用8位字节表示的数据流,然后TCP把数据流分区成适当长度的报文段(通常受该计算机连接的网络的数据链路层的最大传输单元(MTU)的限制)。之后TCP把结果包传给IP层,由它来通过网络将包传送给接收端实体的TCP层。
TCP为了保证不发生丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收。然后接收端实体对已成功收到的包发回一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。TCP用一个校验和函数来检验数据是否有错误;在发送和接收时都要计算校验和。

在学习TCP协议的时候,我们总是得到TCP较UDP协议比较可靠,那么具体的可靠原因是什么呢?

①TCP是面向连接的,并且是全双工的

②TCP是基于请求应答机制的

③TCP能够识别出丢包并重传

④TCP可以保证数据的按序到达

⑤TCP维护各种各样的定时器用来满足不同的工作需求

⑥TCP是基于滑动窗口的流量控制

⑦TCP必定要实现网络拥塞避免算法

TCP的报文段格式

在简单了解了一下TCP协议之后呢,我们去了解一下TCP段的格式


其中的标志位(6位)的意思分别是:

URG表示16位紧急指针(Urgent Pointer)字段有意义

ACK表示确认序号(Acknowledgment Number)字段有意义

PSH表示Push功能

RST表示复位TCP连接

SYN表示SYN报文(在建立TCP连接的时候使用)

FIN表示没有数据需要发送了(在关闭TCP连接的时候使用)

对于URG和PSH的比较可以参考这里——>URG和PSH的比较

另外16位紧急指针只有URG标志位被设置时该字段才有意义,表示紧急数据相对序列号(Sequence Number字段的值)的偏移。

TCP的三次握手

下面我们具体解释一下三次握手的过程:

所谓三次握手,其实就是TCP建立连接的过程,三次表示需要客户端和服务端总共发送3个包以确认连接的建立。


握手之前,两边现处于没有任何连接状态;当启动之后,服务器先创建传输控制块准备接受客户端的连接请求,并进入LISTEN监听状态。

第一次握手:Client将标志位SYN置为1,随机产生一个值seq(序号)=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。

第二次握手:Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。

第三次握手:Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。

其中的几个状态分别代表:

LISTEN - 侦听来自远方TCP端口的连接请求;

SYN-SENT -在发送连接请求后等待匹配的连接请求; 

SYN-RCVD - 在收到和发送一个连接请求后等待对连接请求的确认; 

ESTABLISHED- 代表一个打开的连接,数据可以传送给用户;

CLOSED - 没有任何连接状态;

为什么是三次握手,如果是两次握手会怎样?

三次握手的目的是防止已经失效的连接请求又传到了服务端。

假如这样:客户端向服务端发送一个请求连接报文,但是这个报文在网络中阻塞了,并没有传到服务端。所以服务端也无法向客户端发送确认报文,在重传计时器的计时到达之后,客户端再次向服务端发送请求连接报文,这个报文服务端收到了,并且向客户端做出应答,建立连接,传输数据。数据传输完后,关闭连接。问题来了,就在服务端关闭连接之后,客户端第一次发送的请求连接报文到了(这个报文是已经失效的),服务端以为客户端要再次创建一个新连接,于是再向客户端发送确认报文。

至于两次握手则根本不能保证连接的可靠。

假如这样:客户端给服务端发送了一个请求连接,然后服务端回了一个确认报文,但是这个确认报文丢失了,那么此时服务端认为连接已经建立了并且一直等待客户端发送数据,但是客户端认为服务端并没有收到我的请求报文,我会等待这个确认报文,那么这样就造成了服务端在这里一直等待,那么就会浪费资源了。

TCP的四次挥手

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

CP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。


首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。

第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。

第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。

第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。

上面的是一方主动关闭,利=另一方被动关闭的情况,但是实际中还会出现同时发起主动关闭的情况:


其中的几个状态分别代表:

FIN-WAIT-1 - 等待远程TCP的连接中断请求,或先前的连接中断请求的确认

FIN-WAIT-2 - 从远程TCP等待连接中断请求

CLOSE-WAIT - 等待从本地用户发来的连接中断请求

CLOSING -等待远程TCP对连接中断的确认

LAST-ACK - 等待原来发向远程TCP的连接中断请求的确认

TIME-WAIT -等待足够的时间以确保远程TCP接收到连接中断请求的确认  

建立连接只需要三次握手,那么为何关闭连接需要四次挥手呢?

这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。

TIME_WAIT状态的调研

TIME_WAIT表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带 FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

TIMEWAIT状态本身和应用层的客户端或者服务器是没有关系的。仅仅是主动关闭的一方,在使用FIN|ACK|FIN|ACK四分组正常关闭TCP连接的时候会出现这个TIMEWAIT。服务器在处理客户端请求的时候,如果你的程序设计为服务器主动关闭,那么你才有可能需要关注这个TIMEWAIT状态过多的问题。如果你的服务器设计为被动关闭,那么你首先要关注的是CLOSE_WAIT。

TCP要保证在所有可能的情况下使得所有的数据都能够被正确送达。当你关闭一个socket时,主动关闭一端的socket将进入TIME_WAIT状态,而被动关闭一方则转入CLOSED状态,这的确能够保证所有的数据都被传输。当一个socket关闭的时候,是通过两端四次握手完成的,当一端调用close()时,就说明本端没有数据要发送了。这好似看来在握手完成以后,socket就都可以处于初始的CLOSED状态了,其实不然。原因是这样安排状态有两个问题 首先,我们没有任何机制保证最后的一个ACK能够正常传输,第二,网络上仍然有可能有残余的数据包(wandering duplicates),我们也必须能够正常处理。

TIMEWAIT就是为了解决这两个问题而生的。

1.假设最后一个ACK丢失了,被动关闭一方会重发它的FIN。主动关闭一方必须维持一个有效状态信息(TIMEWAIT状态下维持),以便能够重发ACK。如果主动关闭的socket不维持这种状态而进入CLOSED状态,那么主动关闭的socket在处于CLOSED状态时,接收到FIN后将会响应一个RST。被动关闭一方接收到RST后会认为出错了。如果TCP协议想要正常完成必要的操作而终止双方的数据流传输,就必须完全正确的传输四次握手的四个节,不能有任何的丢失。这就是为什么socket在关闭后,仍然处于TIME_WAIT状态的第一个原因,因为他要等待以便重发ACK。

2.假设目前连接的通信双方都已经调用了close(),双方同时进入CLOSED的终结状态,而没有走TIME_WAIT状态。会出现如下问题,现在有一个新的连接被建立起来,使用的IP地址与端口与先前的完全相同,后建立的连接是原先连接的一个完全复用。还假定原先的连接中有数据报残存于网络之中,这样新的连接收到的数据报中有可能是先前连接的数据报。为了防止这一点,TCP不允许新连接复用TIME_WAIT状态下的socket。处于TIME_WAIT状态的socket在等待两倍的MSL时间以后(之所以是两倍的MSL,是由于MSL是一个数据报在网络中单向发出到认定丢失的时间,一个数据报有可能在发送途中或是其响应过程中成为残余数据报,确认一个数据报及其响应的丢弃的需要两倍的MSL),将会转变为CLOSED状态。这就意味着,一个成功建立的连接,必然使得先前网络中残余的数据报都丢失了。

大量TIMEWAIT出现,并且需要解决的场景

在高并发短连接的TCP服务器上,当服务器处理完请求后立刻按照主动正常关闭连接。这个场景下,会出现大量socket处于TIMEWAIT状态。如果客户端的并发量持续很高,此时部分客户端就会显示连接不上。

我来解释下这个场景。主动正常关闭TCP连接,都会出现TIMEWAIT。为什么我们要关注这个高并发短连接呢?有两个方面需要注意:

1. 高并发可以让服务器在短时间范围内同时占用大量端口,而端口有个0~65535的范围,并不是很多,刨除系统和其他服务要用的,剩下的就更少了。

2. 在这个场景中,短连接表示“业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间”的连接。这里有个相对长短的概念,比如,取一个web页面,1秒钟的http短连接处理完业务,在关闭连接之后,这个业务用过的端口会停留在TIMEWAIT状态几分钟,而这几分钟,其他HTTP请求来临的时候是无法占用此端口的。单用这个业务计算服务器的利用率会发现,服务器干正经事的时间和端口(资源)被挂着无法被使用的时间的比例是 1:几百,服务器资源严重浪费。

综合这两个方面,持续的到达一定量的高并发短连接,会使服务器因端口资源不足而拒绝为一部分客户服务。

虽然它存在着这种问题,但是我们还是需要要抱着一个友好的态度来看待它,因为它尽它的能力保证了服务器的健壮性。

原创粉丝点击