预备知识
在讲解tcp三次握手和四次挥手前,先来大致看看tcp包的格式(主要看头部就行了)
这里我们看最上面的黄色部分就好了。
- 序号:显然,序号代表的就是目前这个数据包的序号,大概就是第几个包之类的,这里我们要记住它的英文符号:
seq
,等一下要用 - 确认号:确认号是用来确认接收到了上一次的数据包的一个东东,比如上一次客户端发给服务器的包的seq是10000,那么如果服务端要告诉客户端它确认收到了这个包的话,确认号就可以设置为10001,这样一来客户端就知道服务器收到了上一个数据包了,确认号的英文缩写:
ack
,记住这个,等一下也要用
然后再来看看图中间的那几个大写英文单词缩写,他们其实是数据包中的标识位(flag),虽然占用的空间不大,但却在数据传输中起到很大的作用,这里我仅介绍其中在三次握手四次挥手用到的标志位。补充一点:这些标志位都只能取0或1
- ACK:这个标志位代表着数据包中的ack(小写ack,不是大写)是否有效,取1时ack才有效
- SYN:这个标志位代表着要发送一个建立连接的请求,取1时表示要建立连接
- FIN:这个标志位代表着要发送一个断开连接的请求,取1时表示要断开连接
好了,到这里就是我们要了解三次握手与四次挥手的预备知识,不过我还是建议那些对计网五层协议栈没怎么了解过的同学先去学习五层协议栈,不然你都不知道tcp是干嘛用的,这里可以去看我的另一篇文章:计网五层协议栈,接下来进入正文。
三次握手
我相信很多小伙伴都看过网络上其他关于三次握手四次挥手的解读的文章,配一张图对吧,每次发送什么,返回什么,被几个英文字母搞得稀里糊涂的,这里我会以一种通俗的语言去讲解三次握手的流程。
首先,客户端与服务器之间是全双工的(即客户端要与服务器建立通信,而服务器也要与客户端建立通信)。
好,现在客户端要与服务器建立通信了,于是客户端扔给了服务器一个包,告诉服务器他想要建立通讯,前面讲过,SYN是关于建立连接请求的一个标志位,所以这个包的内容一定是:
SYN = 1,seq = x 解释:SYN = 1就可以告诉服务器说想要建立一个连接,然后seq其实是多少无所谓,这里只代表当前包的序号,但是后面也是要用的
接着,服务器要告诉客户端它收到了客户端的连接请求,所以它就要返回一个确认号对吧,确认号前面也讲过,是与上一次的seq有关的,所以确认号需要设置为x+1,同时,要确认ack有效,一定与ACK标志位有关,所以ACK要设置为1,接着,不仅客户端要与服务器建立连接对吧,服务器也要与客户端建立连接,所以这次返回的数据包头部是这样的:
SYN = 1,seq = y,ack = x + 1,ACK = 1 解释:懂了没?是不是开始有点晕了,没事,自己回去看我上面对这几个英文缩写的解释
然后,客户端收到服务器返回的这个数据包后,也要对服务器的连接请求进行确认对不对,所以确认号跟上一次的seq有关,确认标志位也要置1,返回的数据包是这样的:
ack = y + 1,ACK = 1
当服务器收到客户端发来的这个包后,三次握手就已经完成了,最后用一种比较有趣的方式总结三次握手的流程
- 客户端:hi!服务器,我要跟你建立连接
- 服务器:好的客户端,我也想要和你建立连接
- 客户端:好的服务器,我们现在可以建立连接了
四次挥手
相信读懂了前面三次握手的流程再来看四次挥手其实是很简单的,这里我就直接讲了。
首先还是一样的,客户端想要断开连接,所以FIN要置1,数据包长这样:
FIN = 1,seq = x
接着服务器要跟客户端说确认收到了这个断开连接的请求,然后数据包长这样:
ack = x + 1,ACK = 1
好了,其实已经有人发现了这一步与三次握手不一样,可能有小伙伴会问,为什么服务端没有向客户端立即发送断开连接的请求?其实在这里,服务端可能还不想和客户端断开请求对吧,可能还有数据在传输,也可能服务器对客户端恋恋不舍,不想这么快离开,所以这里服务器会先等一下,等服务端准备好断开后,再发送断开请求。
过了n个单位时间后,服务器想清楚了,要和客户端一刀两断,于是它毅然的向客户端发送了断开连接的请求,数据包长这样:
FIN = 1,seq = y
然后客户端收到了这个分手包后,就要进行确认,数据包长这样:
ack = seq + 1,ACK = 1
最终完成分手。这里也用一种比较有趣的方式来总结一下四次分手的过程:
- 客户端:服务器我要和你分手
- 服务器:好的,不过先让我想一会,而且我要把我这里一些东西给你
- 过了很久....
- 服务端:行吧,分就分吧,无所谓了
- 客户端:行,互删吧!
面试题
最后是一些关于三次握手四次挥手的面试题问答
问:为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
问:为什么不能用两次握手建立连接
3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。