面试必问!帮你一次搞定 TCP 三次握手!
关于 TCP(Transmission Control Protocol) 三次握手这个问题,基本上就是面试必问,是一个非常热门的问题。先当初我找实习的时候也被问到了,当那个时候只能大概说一下过程,一些标记位什么的还是不懂。
今天我们就大概讲一下,应付面试应该可以了。
标记位
我们这里先说要一下标记位和标记位的含义,这有助于后面理解整个握手的过程。
我们先来看一下 TCP 报文的格式吧。
TCP 报文我们要讲 TCP 握手的过程,就要先了解图中红色框出来的几个信息,下面我们一一解释。
序列号(Sequence number)
占4个字节,用来标记数据段的顺序,TCP 把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。
我们这里也补充一点网络的知识吧,我们都知道 TCP 是属于传输层,要保障数据准确完整的传输,而在网络传输中,一般都是要分包的,因为如果不分包,要是数据丢失了,要重新传的代价太大了,我们分成一小段一小段进行传输,这样即使有一小段丢失了,我们就只要重新传这一小段就可以了,可以节省很多流量。
TCP 在传输数据的时候,会顺便把数据放到重发队列里面,然后启动计时器,如果后面接收到了这个包的确认信息,就把这个数据包从队列里面删除掉,如果计时器超时了都还没有收到确认信息,就重新发送这个数据包,因为数据可能丢失了,对面没有收到。另外因为有序列号的存在,在接受到全部数据之后就可以按照顺序重新组装起来了,保障了数据的准确性和完整性。
说人话,就是有点类似 ID 一般,给这次连接定一个序列号。后面传输数据就从这个序号开始。
后面我们就用 seq 来表示 序列号。
ACK (Acknowledgement)
占1位,仅当 ACK=1 时,确认号字段才有效。ACK=0时,确认号无效。
说人话,ACK 就是相当于一个开关,确认号 就相当于灯泡的颜色,无论你这个灯泡是什么颜色,如果开关不打开,就什么都看不到,也就是说无效。ACK=1 时,这个开关就打开了,ACK=0时,这个开关就是关闭了。这样是不是好理解多了。
ack(Acknowledge number)
刚才我们说了,确认号收 ACK 控制,只有 ACK=1 时,确认号才有效。
那问题又来了,这个确认号是干什么用的呢?
确认号就是用来确认的,比如就好像你微信发了一个文件给我,我回复你一句“收到”是同样的道理,就是用来跟对方确认已经收到了消息。
后面我们就用 ack 来表示 确认号。
SYN(synchronous)
这个信号也是很关键,SYN 的意思就是说请求同步,也就是请求连接的意思。
三次握手
说完了几个关键的标记信息,我们就开始来说 TCP 建立的三次握手。
- 主机A 向 主机B 发送一个信号,【SYN=1,ACK=0,seq=x】,这个信号的意思就是说“主机B,我想跟你建立连接”。主机A 就会进入 SYN-SENT 状态。
- 主机B 收到 主机A 的请求之后,就会做出判断,如果同意的话,就会回一个消息 【SYN=1,ACK=1,ack=x+1,seq=y】,意思是说,你的请求我收到了,我也想跟你建立连接。主机B 的状态由 LISTEN 进入 SYN-RCVD 状态。
- 主机A 收到 主机B 的响应后,如果要建立连接就会回一个消息 【SYN=0,ACK=1,ack=y+1,seq=x+1】,表示,我知道了,我要发送数据了。主机A 进入 ESTABLISHED 状态。
到此,TCP 通道就会建立了,后面就通过这个 TCP 通道传输数据了。为了方便大家理解,我从网上找了一个图。
三次握手怎么样?听起来不会很复杂吧。
如果还不是很理解,我们就再场景化一下,想当年我实习面试的时候就是这么回答的,哈哈哈。
先模拟一个场景,A 要通过 微信 向 B 发一个文件。因为不知道对面有没有看微信,所以就要先问。
- 主机A 向 主机B 说,“嘿,我等一下要给你发个东西,你看到了没有?”。
- 主机B 看到之后,就回答说,“哦,好的,我知道了。你要发前先跟我说一声,我等着呢。“
- 主机A 看到了说,”OK,我这就发了“。
这样就不会那么抽象了吧,哈哈哈。可惜不会做图,不然就做两个图,以后再学学做图。
三次握手引申出的问题
为什么要三次握手,为什么不能是一次或者两次?
这个问题也是非常常见的问题。三次握手是确认双方都在线、都准备好发送和接受数据,而且连接性能最优的方式。
如果是一次握手根本就不可能,因为根本不可能确认对方在线,不断重发只会浪费性能。
如果是两次握手,那服务器收到客户端一个连接请求就会建立连接。但是如果服务器给客户端的响应丢失了,那客户端就根本不知道服务器接收到这个数据没有,就会一直等待,另一方面,服务器也在等客户端发送数据,现在两边就都在等待对方,那建立的这条 TCP 通道就白白浪费了。
同样的,如果客户端一开始发送的连接请求由于网络延迟的问题,一直没有到服务器,客户端就会废弃第一次的请求,重新发送一个新的连接请求,服务器接收到之后就会建立连接。这样看似没什么问题,但要是第一次后面又到达服务器了呢?由于只要两次握手,服务器接收到第一次请求之后,回一个消息就可以建立 TCP 连接了,但是客户端判定第一次的请求已经废弃了,也不会跟服务器建立 TCP 连接,也更不会发数据了,服务器就也白白浪费了这个 TCP 连接。
如果在第三次握手的时候,数据丢失、服务器没有收到怎么办?
如果在第三次握手的时候,数据丢失,服务器没有收到,但客户端认为 TCP 连接已经建立了,开始发数据会有问题吗?
有问题,这个时候客户端就会收到服务器的 RST 应答,客户端就知道出现问题了,需要重新连接。
三次握手的核心
其实三次握手的主要核心就是确认通信双方都在线能够,而且不会出现互相等待的情况。
如果只有一次握手,根本不能确认对方在线。
如果是两次握手,在理想情况下是能够确立的,但是如果网络出现问题,就会导致死锁的情况。浪费性能。
四次握手
既然三次握手都讲完了,我们就加个餐,说一下四次握手吧。
四次握手是用来断开 TCP 连接的。
为什么建立连接的时候只要三次,断开却要四次?
我们同样先来讲一下用到的一个关键标记信息。
FIN(Finish)
FIN标记位FIN 标记位,表示数据发送完毕(Finish),也就是请求断开连接。
断开连接 -- 四次握手
其实跟建立连接时的三次握手不同,主要因为,这个时候连接已经建立了,有一方主动请求断开连接,但是被动方可能还有一些数据正在发送,或者还有些数据没发送,不能马上中断,要等这部分数据发送完。
下面说一下四次握手的过程吧。
- 主机A 向 主机B 发送一个关闭连接请求。【FIN=1,seq=u】,状态由 ESTABLISHED 进入 FIN-WAIT-1 。
- 主机B 接收到关闭请求,会响应 【ACK=1,seq=v,ack=u+1】,告诉 主机A,你的请求是收到了,但是等一下,我还有东西没发完。状态由 ESTABLISHED 进入 CLOSE-WAIT 。主机A 收到之后,就会进入 FIN-WAIT-2 状态。
- 主机B 等发送完剩余的数据之后,发送 【FIN=1,ACK=1,ack=u+1,seq=w】,然后就进入 LAST-ACK 状态。这里 seq 为什么是变了呢?因为刚才把剩余的数据发送出去了,seq 增加了。
- 主机A 接受到消息之后,回复 【ACK=1,seq=u+1,ack=w+1】,然后就进入 TIME-WAIT 状态。等待 两倍最长报文寿命(2MSL)的时间之后,就进入 CLOSE 状态。主机B 接受到之后,就也进入 CLOSE 状态。
我也在网上找了一个图,方便大家理解。
四次握手整个流程大概就是这样了,如果觉得还不错,麻烦点个赞再走吧。
也可以转发给那些不太了解这块的同学,可以好好嘲讽一下学习一下,嘿嘿。
如果发现有问题,麻烦各位大佬在评论区指出。