详解TCP三次握手与四次挥手

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/swty3356667/article/details/84112030

一、TCP三次握手和socket详解

1.TCP连接

第一次:cli发送SYN包(SYN = j)到ser,并且进入SYN_SEND状态,等待服务器确认;

第二次:ser收到SYN包,必须确认客户的SYN(ACK = j+1),同事自己也发送一个SYN包(SYN = k),即SYN+ACK,此时ser进入SYN_RECV状态;

第三次:cli收到ser的SYN+ACK包,向ser发送确认包ACK(ACK = k+1),此包发送完毕,ser和cli进入ESTABLISHED状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕之后,cli与ser才开始传送数据。

2.三次握手状态详解:

3.图解三次握手:

从图中可以看出,当客户端调用connect时,触发了连接请求,向服务器发送了SYN包,这时connect进入阻塞状态;服务器监听到连接请求,即收到了SYN J包,调用accept函数接收请求 向客户端发送SYN K,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K,ACK J+1之后,这时connect返回,并且对SYN K进行确认;服务器收到ACK K+1,accept返回,至此三次握手完毕,连接建立。客户端的connect在三次握手的第二次返回,而服务器端的accept在三次握手的第三次返回

套接字之间的连接过程分为三个步骤:服务器监听、客户端请求、连接确认

(1)服务器监听:是服务器端套接字并不指定具体的客户端套接字,而是一直处于等待的状态,实时监控网络状态。

(2)客户端请求:是指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字必须首先描述它要连接的服务器的套接字,指出服务器端套接字的地址和端口号,然后就向服务器端套接字提出连接请求。

(3)连接确认:是指当服务器端套接字监听到或者接收到客户端套接字的连接请求,它就响应该请求,建立一个新线程,把服务器端套接字的描述发给客户端,一旦客户端确认此描述,连接就建立好了。注意:此时,服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

二、图解TCP四次挥手

第一次挥手:客户端进程首先调用close主动关闭连接,这时客户端向服务器发送一个FIN报文段,此时,客户端进入FIN_WAIT_1状态,这表示客户端已无数据要发送给服务器;

第二次挥手:服务器收到了客户端发送的FIN报文段,向客户端回复一个ACK报文段,ACK+1,客户端进入FIN_WAIT_2状态,服务器告诉客户端,我同意你的关闭请求;

第三次挥手:服务器向客户端发送FIN报文段,请求关闭连接,同时服务器进入LAST_ACK状态;

第四次挥手:客户端收到服务器发送的FIN报文段,向服务器发送ACK报文段,然后客户端进入TIME_WAIT状态,服务器收到客户端的ACK报文段之后,就关闭连接,此时客户端等待2MSL后依旧没有收到回复,则证明服务器端已正常关闭,那好,客户端也可以关闭连接了。

三、疑难问题

1、FIN_WAIT_1和FIN_WAIT_2的解析

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看到。


FIN_WAIT_2:上面已经详解了这样的状态,实际上FIN_WAIT_2状态下的SOCKET。表示半连接,也即有一方要求close连接,但另外还告诉对方,我临时还有点数据须要传送给你,稍后再关闭连接。
TIME_WAIT: 表示收到了对方的FIN报文。并发送出了ACK报文,就等2MSL后就可以回到CLOSED可用状态了。假设FIN_WAIT_1状态下。收到了对方同一时候带FIN标志和ACK标志的报文时,能够直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。

2、为什么握手是三次,挥手是四次?

这是由于服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后。它能够把ACK和SYN(ACK起应答作用。而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它只表示对方没有数据发送给你了。但未必你所有的数据都所有发送给对方了。所以你能够未必会立即会关闭SOCKET,也即你可能还须要发送一些数据给对方之后,再发送FIN报文给对方来表示你允许如今能够关闭连接了。所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

3、为什么TIME_WAIT状态还须要等2MSL后才干返回到CLOSED状态?

MSL即报文最大生存时间, MSL是不论什么报文段被丢弃前在网络内的最长时间。2MSL也就是这个时间的2倍,当TCP连接完毕四个报文段的交换时,主动关闭的一方将继续等待一定时间(2-4分钟),即使两端的应用程序结束。

为什么须要这个2MSL呢?
(1)尽管两方都允许关闭连接了,并且握手的4个报文也都协调和发送完成,按理能够直接回到CLOSED状态;可是由于我们必需要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会由于超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文。
(2)报文可能会被混淆。意思是说。其它时候的连接可能会被当作本次的连接。当某个连接的一端处于TIME_WAIT状态时。该连接将不能再被使用。其实,对于我们比較有现实意义的是,这个port将不能再被使用。某个port处于TIME_WAIT状态(其实应该是这个连接)时,这意味着这个TCP连接并没有断开(全然断开),那么。假设你bind这个port,就会失败。对于server而言,假设server突然crash掉了,那么它将无法在2MSL内又一次启动,由于bind会失败。解决问题的一个方法就是设置socket的SO_REUSEADDR选项。这个选项意味着你能够重用一个地址。
当建立一个TCP连接时。server端会继续用原有port监听,同一时候用这个port与client通信。

而client默认情况下会使用一个随机port与server端的监听port通信。

有时候,为了server端的安全性,我们须要对client进行验证,即限定某个IP某个特定port的client。client能够使用bind来使用特定的port。对于server端,当设置了SO_REUSEADDR选项时。它能够在2MSL内启动并listen成功。可是对于client。当使用bind并设置SO_REUSEADDR时,假设在2MSL内启动,bind会成功。

当TCP连接发生一些物理上的意外情况时,比如网线断开,linux上的TCP实现会依旧觉得该连接有效。

4、为什么不能两次握手?

3次握手完毕两个重要的功能。既要两方做好发送数据的准备工作(两方都知道彼此已准备好),也要同意两方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。

如今把三次握手改成仅须要两次握手。死锁是可能发生的。

Eg:假定cli给ser发送一个连接请求分组,ser收到了这个分组,并发送了确认应答分组。依照两次握手的协定,ser觉得连接已经成功地建立了,能够開始发送数据分组。但是,cli在ser的应答分组在传输中被丢失的情况下,将不知道ser是否已准备好。不知道ser建立什么样的序列号。cli甚至怀疑ser是否收到自己的连接请求分组。在这样的情况下,cli觉得连接还未建立成功,将忽略ser发来的不论什么数据分组,仅仅等待连接确认应答分组。而S在发出的分组超时后,反复发送相同的分组。这样就形成了死锁

5、time_wait状态如何产生?
首先调用close()发起主动关闭的一方,在发送最后一个ACK之后会进入time_wait的状态,也就说该发送方会保持2MSL时间之后才会回到初始状态。MSL值得是数据包在网络中的最大生存时间。产生这种结果使得这个TCP连接在2MSL连接等待期间,定义这个连接的四元组(客户端IP地址和端口,服务端IP地址和端口号)不能被使用。

6、time_wait状态产生的原因与作用

(1)为实现TCP全双工连接的可靠释放

由TCP状态变迁图可知,假设发起主动关闭的一方(client)最后发送的ACK在网络中丢失,由于TCP协议的重传机制,执行被动关闭的一方(server)将会重发其FIN,在该FIN到达client之前,client必须维护这条连接状态,也就说这条TCP连接所对应的资源(client方的local_ip,local_port)不能被立即释放或重新分配,直到另一方重发的FIN达到之后,client重发ACK后,经过2MSL时间周期没有再收到另一方的FIN之后,该TCP连接才能恢复初始的CLOSED状态。如果主动关闭一方不维护这样一个TIME_WAIT状态,那么当被动关闭一方重发的FIN到达时,主动关闭一方的TCP传输层会用RST包响应对方,这会被对方认为是有错误发生,然而这事实上只是正常的关闭连接过程,并非异常。

(2)为使旧的数据包在网络因过期而消失

为说明这个问题,我们先假设TCP协议中不存在TIME_WAIT状态的限制,再假设当前有一条TCP连接:(local_ip, local_port, remote_ip,remote_port),因某些原因,我们先关闭,接着很快以相同的四元组建立一条新连接。本文前面介绍过,TCP连接由四元组唯一标识,因此,在我们假设的情况中,TCP协议栈是无法区分前后两条TCP连接的不同的,在它看来,这根本就是同一条连接,中间先释放再建立的过程对其来说是“感知”不到的。这样就可能发生这样的情况:前一条TCP连接由local peer发送的数据到达remote peer后,会被该remot peer的TCP传输层当做当前TCP连接的正常数据接收并向上传递至应用层(而事实上,在我们假设的场景下,这些旧数据到达remote peer前,旧连接已断开且一条由相同四元组构成的新TCP连接已建立,因此,这些旧数据是不应该被向上传递至应用层的),从而引起数据错乱进而导致各种无法预知的诡异现象。作为一种可靠的传输协议,TCP必须在协议层面考虑并避免这种情况的发生,这正是TIME_WAIT状态存在的第2个原因。

 

7、大量TIME_WAIT状态造成的后果

在生产过程中,如果服务器使用短连接,那么完成一次请求后会主动断开连接,就会造成大量time_wait状态。因此我们常常在系统中会采用长连接,减少建立连接的消耗,同时也减少TIME_WAIT的产生,但实际上即使使用长连接配置不当时,当TIME_WAIT的生产速度远大于其消耗速度时,系统仍然会累计大量的TIME_WAIT状态的连接。TIME_WAIT状态连接过多就会造成一些问题。如果客户端的TIME_WAIT连接过多,同时它还在不断产生,将会导致客户端端口耗尽,新的端口分配不出来,出现错误。如果服务器端的TIME_WAIT连接过多,可能会导致客户端的请求连接失败

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

为什么我们要关注这个高并发短连接呢?有两个方面需要注意:
1. 高并发可以让服务器在短时间范围内同时占用大量端口,而端口有个0~65535的范围,并不是很多,刨除系统和其他服务要用的,剩下的就更少了。
2. 在这个场景中,短连接表示业务处理+传输数据的时间 远远小于 TIMEWAIT超时的时间的连接

  这里有个相对长短的概念,比如取一个web页面,1秒钟的http短连接处理完业务,在关闭连接之后,这个业务用过的端口会停留在TIMEWAIT状态几分钟,而这几分钟,其他HTTP请求来临的时候是无法占用此端口的(占着茅坑不拉翔)。单用这个业务计算服务器的利用率会发现,服务器干正经事的时间和端口(资源)被挂着无法被使用的时间的比例是 1:几百,服务器资源严重浪费。(说个题外话,从这个意义出发来考虑服务器性能调优的话,长连接业务的服务就不需要考虑TIMEWAIT状态。同时,假如你对服务器业务场景非常熟悉,你会发现,在实际业务场景中,一般长连接对应的业务的并发量并不会很高
 综合这两个方面,持续的到达一定量的高并发短连接,会使服务器因端口资源不足而拒绝为一部分客户服务。同时,这些端口都是服务器临时分配,无法用SO_REUSEADDR选项解决这个问题。

8、查询TCP连接数

9、怎样解决TIME_WAIT过多的情况

1)修改系统配置
  需要修改的tcp_max_tw_bucketstcp_tw_recycletcp_tw_reuse这三个配置项。
         1)将tcp_max_tw_buckets调大,从本文第一部分可知,其默认值为18w(不同内核可能有所不同,需以机器实际配置为准),根据文档,我们可以适当调大,至于上限是多少,文档没有给出说明,我也不清楚。个人认为这种方法只能对TIME_WAIT过多的问题起到缓解作用,随着访问压力的持续,该出现的问题迟早还是会出现,治标不治本。
         2)开启tcp_tw_recycle选项:在shell终端输入命令”echo 1 > /proc/sys/net/ipv4/tcp_tw_recycle”可以开启该配置。
   需要明确的是:其实TIME_WAIT状态的socket是否被快速回收是由tcp_tw_recycletcp_timestamps两个配置项共同决定的,只不过由于tcp_timestamps默认就是开启的,故大多数文章只提到设置tcp_tw_recycle1
   还需要特别注意的是:clientserver之间有如NAT这类网络转换设备时,开启tcp_tw_recycle选项可能会导致serverdrop(直接发送RST)来自clientSYN包。

         3)开启tcp_tw_reuse选项:echo1 > /proc/sys/net/ipv4/tcp_tw_reuse。该选项也是与tcp_timestamps共同起作用的,另外socket reuse也是有条件的。查了很多资料,与在用到NATFireWall的网络环境下开启tcp_tw_recycle后可能带来副作用相比,貌似没有发现tcp_tw_reuse引起的网络问题。
2修改应用程序
         1)将TCP短连接改造为长连接。通常情况下,如果发起连接的目标也是自己可控制的服务器时,它们自己的TCP通信最好采用长连接,避免大量TCP短连接每次建立/释放产生的各种开销;如果建立连接的目标是不受自己控制的机器时,能否使用长连接就需要考虑对方机器是否支持长连接方式了。
        2)通过getsockopt/setsockoptapi设置socketSO_LINGER选项。

3)需要补充说明的问题
   我们说TIME_WAIT过多可能引起无法对外建立新连接,其实有一个例外但比较常见的情况:S模块作为WebServer部署在服务器上,绑定本地某个端口;客户端与S间为短连接,每次交互完成后由S主动断开连接。这样,当客户端并发访问次数很高时,S模块所在的机器可能会有大量处于TIME_WAIT状态的TCP连接。但由于服务器模块绑定了端口,故在这种情况下,并不会引起由于TIME_WAIT过多导致无法建立新连接的问题。也就是说,本文讨论的情况,通常只会在每次由操作系统分配随机端口的程序运行的机器上出现(每次分配随机端口,导致后面无端口可用)。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

猜你喜欢

转载自blog.csdn.net/swty3356667/article/details/84112030