由于网上关于TCP三次握手四次挥手的内容已经很丰富,所以我们今天趣味讲解一下TCP三次握手和四次挥手。
三次握手:
首先先上图:
![TCP三次握手]
由于我给客户端和服务端都上了色,所以暂且就叫他们小蓝(Client)和小绿(Server)就好了。
由于一次意外,两人都双目失明,看不见了。他们只能通过声音辨认对方。
小蓝首先叫了一声小绿(syn)
小绿听见听见小蓝在叫自己,便答应了一声自己在(ack),但小绿怕小蓝叫的是其他的小绿,便问小蓝是在叫自己吗(syn),小蓝听到后知道了是小绿,便进入(established)状态。
最后小蓝也答应了一声是自己(ack),小绿听到后进入(established)状态。
这其中还有两个中间态:syn_sent和syn_rcvd,这两个状态叫【半打开】状态,也就是两人呼唤对方。Syn_sent是客户端的半打开状态,sun_rcvd是服务端的半打开状态。
Syn_sent : syn package has been sent
Syn_rcvd : syn package has been received
官方语言:
TCP服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
TCP客户进程也是先创建传输控制块TCB,然后向服务器发出连接请求报文,这是报文首部中的同部位SYN=1,同时选择一个初始序列号
seq=x ,此时,TCP客户端进程进入了
SYN-SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。TCP服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该
ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号
seq=y,此时,TCP服务器进程进入了SYN-RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。TCP客户进程收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
当服务器收到客户端的确认后也进入ESTABLISHED状态,此后双方就可以开始通信了。
四次挥手:
![TCP四次挥手]
两人离别的时候,
小蓝说“我要走了”(FIN)
小绿回应“那好吧”(ACK)
此时小绿说了一些话,但发现小蓝真的不说话了,就说“我也走了”(FIN)
小蓝回应了一句“好的”(ACK)
官方语言:
客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
状态time_wait是一个非常特殊的状态,它是主动关闭的一方在回复完对方后进入的一个长期状态,这个状态标准的持续时间是4分钟,4分钟后才会进入到closed状态,释放套接字资源。不过在具体实现上这个时间是可以调整的。
这个后果就是持续4分钟的time_wait状态,不能释放套接字资源(端口),就好比守寡期,这段时间内套接字资源(端口)不得回收利用。
它的作用是重传最后一个ack报文,确保对方可以收到。因为如果对方没有收到ack的话,会重传fin报文,处于time_wait状态的套接字会立即向对方重发ack报文。
同时在这段时间内,该链接在对话期间于网际路由上产生的残留报文(因为路径过于崎岖,数据报文走的时间太长,重传的报文都收到了,原始报文还在路上)传过来时,都会被立即丢弃掉。4分钟的时间足以使得这些残留报文彻底消逝。不然当新的端口被重复利用时,这些残留报文可能会干扰新的链接。
4分钟就是2个MSL,每个MSL是2分钟。MSL就是maximium segment
lifetime——最长报文寿命。这个时间是由官方RFC协议规定的。至于为什么是2个MSL而不是1个MSL呢?
第一,保证客户端发送的最后一个ACK报文能够到达服务器,因为这个ACK报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个2MSL时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。
第二,防止“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。
最后一个问题,为什么建立连接是三次握手,关闭连接确是四次挥手呢?
建立连接的时候,
服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,服务器收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送,从而导致多了一次。
四次挥手也并不总是四次挥手,中间的两个动作有时候是可以合并一起进行的,这个时候就成了三次挥手,主动关闭方就会从fin_wait_1状态直接进入到time_wait状态,跳过了fin_wait_2状态。
扩展:TCP数据传输
数据传输过程即是小蓝和小绿对话的过程。
只不过两人隔着河,且都看不见,所以当一方说话后(data)需要对方听到了回应一声(ACK)。
如果小蓝喊了一句,半天没听到小绿回复,小蓝就认为自己的话被大风吹走了,小绿没听见,所以需要重新喊话,这就是【tcp重传】。
也有可能是小绿听到了小蓝的话,但是小绿向小蓝的回复被大风吹走了,以至于小蓝没听见小绿的回复。小蓝并不能判断究竟是自己的话被大风吹走了还是小绿的回复被大风吹走了,小蓝也不用管,重传一下就是。
既然会重传,小绿就有可能同一句话听见了两次,这就是【去重】。【重传】和【去重】工作操作系统的网络内核模块都已经帮我们处理好了。
小蓝可以向小绿喊话,同样小绿也可以向小蓝喊话,因为tcp链接是「双工的」,双方都可以主动发起数据传输。不过无论是哪方喊话,都需要收到对方的确认才能认为对方收到了自己的喊话。
小蓝可能是个八卦,一说连说了八句话,这时候小绿可以不用一句一句回复,而是连续听了这八句话之后,一起向对方回复说前面你说的八句话我都听见了,这就是批量ack。但是小蓝也不能一次性说了太多话,小绿的脑子短时间可能无法消化太多,两人之间需要有协商好的合适的发送和接受速率,这个就是【TCP窗口大小】。
网络环境的数据交互同人类之间的对话还要复杂一些,它存在数据包乱序的现象。同一个来源发出来的不同数据包在「网际路由」上可能会走过不同的路径,最终达到同一个地方时,顺序就不一样了。操作系统的网络内核模块会负责对数据包进行排序,到用户层时顺序就已经完全一致了。
好了,今天就介绍到这里了,说的比较简单,深的等以后写,每天进步一点点,希望世界充满正能量!