关于TCP的握手与挥手

关于TCP的握手与挥手

前言

由于自己每次都是唱的比懂的好听,光知道唱"三次握手四次挥手",再往里细问SYN标志就只能阿巴阿巴阿巴,为了解决自己的知识储备问题,顺便继续深入了解TCP的握手和挥手,这里郑重写下此文档,以便查阅和参考

请注意ACK和ack是不一样的,ACK是标志位而ack是字段,ack表示期望接收的下一个字节的序列号。

三次握手

关于TCP,我们知道其是处于传输层的,关于客户端和服务端都有着各自的IP地址,由于客户端和服务端都有着各自的服务运行在机器上,所以两者在进行信息交互的时候自然需要一个协议来规范两者的数据传输,因为数据不能随意的进行传输,如果这么做肯定就乱套了。为了规范传输过程,将数据完整的传送到目的地址,TCP就此产生了。

在TCP传输的请求报文中,含有SYN、ACK、FIN、SEQ等标识,通常情况下,这些标识的位置为0的时候表示的是关闭,1的时候是开启。

下面我们来说一下完整的TCP三次握手传输过程

​ 假设此时客户端此时要向服务端发出请求,那么客户端就会开始准备,首先创建一个传输控制模块TCB,并打包TCP请求,客户端首先会将SYN设置为1,表示这是握手报文,并随机设置一个初始序列号seq=x,随后会将这一堆数据进行封装,发给服务端。此时,客户端进入了SYN-SENT同步已发送状态。

​ 随后,服务端收到这个包之后,检查到SYN为1,说明客户端正在进行TCP握手,服务端此时就会着手进行第二次握手的准备,产生一个标志位ACK,并且将ACK的数值设置为1,表示成功接收。随后将ack设置为客户端发出的seq的数值x+1,即ack=x+1,以便服务端向客户端表示其发出的数据已经被成功接收,并且期望下一次的数据从x+1处开始;随后服务端也同样会随机生成一个seq=y,将SYN标识为1后封装数据包发回给客户端。此时,TCP服务器进程进入了 SYN-RCVD 同步收到状态。

​ 客户端收到服务端发回的数据包之后,首先检查SYN状态,发现SYN标识为1,说明是服务端发回的二次握手包,随后继续检查服务端发回的信息。随后开始着手准备第三次握手包的内容,此时客户端同样会产生应答字段ACK,令ACK=1。并且设置ack为服务端发来的seq的数值y+1,以便客户端向服务端表示其发出的数据已经被成功接收,并且期望下一次的数据从y+1处开始。上述操作完毕之后会封装数据包并发送给服务端,客户端直接进入ESTABLISHED 已建立连接状态,服务端接收之后检查数据包内容无误之后进入ESTABLISHED状态,此时就可以开始传输数据了,三次握手完成,TCP连接建立。

三次握手的作用

第一次握手包,证明的是客户端发送信息能力完好;第二次握手包证明了服务端的接收信息和发送信息的能力完好;第三次握手包证明了客户端的接收信息的能力完好。

三次握手的意义

TCP三次握手巧妙的检验了客户端与服务端的数据发送和接收能力,能够稳定保障数据在点对点传输中的完整性和无误性。

有些时候可能直观觉得两次握手就足够了,但是这样不可避免的会有网络连接上的错误,这里举一个现实的例子。假设你要访问www.baidu.com,你发送出了第一次握手包,但是非常不幸,此时由于各种小原因,发生了网络堵塞,你的连接请求被堆压了,你看到两秒都没有显示出百度的页面,转而尝试刷新了一下网页,又产生了一个握手包。此时如果网络通了,你的两个握手包就都被送到了服务器处,由于是二次握手,此时服务器向客户端发送完二次握手包后就可以直接打开服务的端口了,但是注意,由于没有客户端传回确认的过程,此时服务端完全有可能开两个端口对你电脑的一个web页面,此时就极易造成页面显示等等错误,并且造成服务器资源的浪费。这还单单是对浏览器而言,由于TCP的泛用性,这种错误如果产生在其他地方,损失将是重大的。但是如果使用三次握手,客户端只要分辨一下,选择其中一个对www.baidu.com的二次握手包进行三次握手包的应答,那么这个问题就可以规避掉了。(阻止历史连接产生的错误,减少服务器开销)

三次握手流量抓包示意图如下

四次挥手

数据传输完毕之后,服务器和客户端双方都可以释放连接。最开始的时候,客户端和服务器都是ESTABLISHED状态,然后客户端主动发起关闭请求,服务端被动关闭。

TCP四次挥手传输过程

​ 在挥手关闭传输时,依旧是使用标识符来确认的。首先是客户端来发送请求,此时客户端会设置FIN标识为1,ACK标识为1,向服务端表示要断开连接;此时的seq就不是随机产生的了,而是延续之前客户端在进行TCP数据包发送时的下一个序号,此处假设为seq=u。完成上述操作之后客户端会封装数据发给服务端,此时,客户端进入**FIN-WAIT-1(终止等待1)**状态。<此处的ACK是对对挥手包发送之前的最后一个TCP包的确认>

​ 服务端在接收到一次挥手包后,检查FIN为1,说明是断开连接请求。随后服务端开始准备给客户端的确认报文。首先服务端会生成一个seq,这个seq同样不是随机的,也是继续延续服务端在向客户端发送的TCP数据包的下一个序号,此处假设seq=v;随后会设置ack为u+1,表示自己成功接收到了客户端的请求。完成上述操作之后数据会被封装发给客户端,服务端进入CLOSE-WAIT 关闭等待状态(也就是说服务端回复客户端之后不会立即关闭连接请求,而是会继续等待客户端的信息,这一步是为了确保在TCP断开之前,所有的数据已经传输完毕,注意此处不设置FIN)。

​ 客户端收到服务器发来的数据包确认无误后,会直接进入FIN-WAIT-2(终止等待2) 状态,待服务端向客户端传输完毕当前pipe的TCP数据后,服务端会主动再向客户端发出释放连接的报文,告诉客户端可以关闭这个TCP传输pipe了。这时的服务端和之前一样,设置seq为下一序号,此处假设seq=f(一般来说此时的seq=f=v+1,因为三次挥手会紧跟二次挥手之后),并设置FIN为1,ack设置为和第二次挥手一致,同为ack=u+1。三次挥手包(连接释放报文)封装完毕之后服务端就会发送给客户端,随后服务器就会进入**LAST-ACK(最后确认)**状态,等待客户端的确认。

​ 客户端接收到服务端发来的连接释放报文之后,会确定其中的标识信息,无误之后,会开始着手封装四次挥手包,设置ack=f+1,以向服务器标识自己成功接收到了服务器的挥手数据包,设置seq为上一个的递增序号,这里就不重复了,一样的。封装完毕之后客户端会将此数据包发给服务端,随后客户端就进入了TIME-WAIT(时间等待) 状态,但此时TCP连接还未终止,必须要经过2MSL(最长报文寿命)时间,客户端超时撤销相应的TCB后,客户端才会进入CLOSED 关闭状态;对服务器来说,当服务端接收到确认报文后,会立即进入CLOSED关闭状态,到这里TCP连接就断开了,四次挥手完成

四次挥手流量抓包示意图如下

第一张图为挥手前的最后一个TCP数据包,此时我们可以直观的看到seq在第一次挥手是如何被设置的

小总结:

三次握手时

l 第一次握手包标识SYN,客户端向服务端表示自己要进行握手(SYN)

l 第二次握手包标识SYN和ACK,服务端向客户端表示自己也要进行握手(SYN)了,并且确认(ACK)成功接收到了客户端的握手请求

l 第三次握手包标识ACK,客户端向服务端表示自己确认(ACK)收到了服务端的请求

四次挥手时

l 第一次挥手包标识FIN和ACK,客户端向服务端表示自己要进行挥手(FIN),并且确认(ACK)已经接收到挥手前的最后一个TCP包。

l 第二次挥手包标识ACK,服务端向客户端表示自己成功确认(ACK)了客户端的挥手包,但仍有可能继续发送数据。

l 第三次挥手包标识FIN和ACK,服务端向客户端表示由于数据发送完毕,自己已经准备好关闭(FIN)数据发送通道,并且再次确认(ACK)已经收到了第一次由客户端发来的数据包。<再次发送ACK标识可以避免由于第二次挥手的数据包阻塞导致的客户端重发挥手,并且根据TCP的设计,FIN报文本身需要被确认,所以这里服务端连续的两次回发都需要携带ACK>

l 第四次挥手包标识ACK,客户端向服务端表示自己确认(ACK)收到了服务端的挥手包

此处顺便穿插说一下wireshark抓包如何判断第一个挥手包前最后一个TCP是谁

这里其实一句话就能说明白,看线即可,这里给图

可以看到左侧有实线有虚线,实线之间就是具有"强关联"的数据包,也就是说实线穿下来的这几个数据包就是相互之间具有联系的。也就是说,这里和1461包有关系的上一个包就是1451。