前言
tcp三次握手和四次挥手是面试题的热门考点,它们分别对应tcp的连接和释放过程,今天我们先来认识一下tcp三次握手过程,以及是否可以使用“两报文握手”建立连接?。
1、tcp是什么?
tcp是面向连接的协议,它基于运输连接来传送tcp报文段,tcp运输连接的建立和释放,是每一次面向连接的通信中必不可少的过程。
tcp运输连接有以下三个阶段:
- 建立tcp连接,也就是通过三报文握手来建立tcp连接。
- 数据传送,也就是基于已建立的tcp连接进行可靠的数据传输。
- 释放连接,也就是在数据传输结束后,还要通过四报文挥手来释放tcp连接。
tcp的运输连接管理就是使运输连接的建立和释放都能正常的进行。
2、tcp首部格式
源端口: 占16比特,写入源端口号,用来 标识发送该tcp报文段的应用进程。
目的端口: 占16比特,写入目的端口号,用来标识接收该tcp报文段的应用进程。
序号: 占32比特,取值范围[0,2^32-1],序号增加到最后一个后,下一个序号就又回到0。指出本tcp报文段数据载荷的第一个字节的序号。
确认号: 占32比特,取值范围[0,2^32-1],确认号增加到最后一个后,下一个确认号就又回到0。指出期望收到对方下一个tcp报文段的数据载荷的第一个字节的序号,同时也是对之前收到的所有数据的确认。若确认号=n,则表明到序号n-1为止的所有数据都已正确接收,期望接收序号为n的数据。
确认标志位ack: 取值为1时确认号字段才有效;取值为0时确认号字段无效。tcp规定,在连接建立后所有传送的tcp报文段都必须把ack置1。
数据偏移: 占4比特,并以4字节为单位。用来指出tcp报文段的数据载荷部分的起始处距离tcp报文段的起始处有多远。这个字段实际上是指出了tcp报文段的首部长度。
窗口: 占16比特,以字节为单位。指出发送本报文段的一方的接收窗。
同步标志位syn: 在tcp连接建立时用来同步序号。终止标志位fin: 用来释放tcp连接。复位标志位rst: 用来复位tcp连接。
推送标志位psh: 接收方的tcp收到该标志位为1的报文段会尽快上交应用进程,而不必等到接收缓存都填满后再向上交付。
校验和: 占16比特,检查范围包括tcp报文段的首部和数据载荷两部分。在计算校验和时,要在tcp报文段的前面加上12字节的伪首部。
紧急指针: 占16比特,以字节为单位,用来指明紧急数据的长度。
填充: 由于选项的长度可变,因此使用填充来 确保报文段首部能被4整除,(因为数据偏移字段,也就是首部长度字段,是以4字节为单位的)。
3、tcp的连接建立
tcp 建立连接的过程叫做握手,握手需要在客户和服务器之间交换三个tcp 报文段,称之为三报文握手,采用三报文握手主要是为了防止已失效的连接请求报文段突然又传送到了,因而产生错误。
tcp的连接建立要解决以下三个问题:
-
1、使tcp双方能够确知对方的存在 。
-
2、使tcp双方能够协商一些参数( 最大窗口值是否使用窗口扩大选项和时间戳选项,以及服务质量等)。
-
3、使tcp双方能够对运输实体资源(例如缓存大小连接表中的项目等)进行分配。
4、三次握手图文详解
这是两台要基于tcp进行通信的主机:
-
主动发起tcp连接建立称为tcp客户(client)。
-
被动等待tcp连接建立的应用进程称为tcp服务器(server)。
我们可以将tcp建立连接的过程比喻为”握手“,“握手”需要在tcp客户端和服务器之间交换三个tcp报文段。
最初两端的tcp进程都处于关闭状态。
一开始,tcp服务器进程首先创建传输控制块,用来存储tcp连接中的一些重要信息。 例如tcp连接表、指向发送和接收缓存的指针、指向重传队列的指针,当前的发送和接收序号等。之后就准备接受tcp客户进程的连接请求, 此时tcp服务器进程就要进入监听状态等待tcp客户进程的连接请求。
tcp客户进程也是首先创建传输控制块,然后再打算建立。 tcp服务器进程是被动等待来自tcp客户端进程的连接请求,因此称为被动打开连接。
tcp连接时向tcp服务器进程发送tcp连接请求报文段,并进入同步已发送状态。
-
tcp 连接请求报文段首部中的同步位syn被设置为1,,表明这是一个tcp连接请求报文段。
-
序号字段seq被设置了一个初始值x作为tcp客户进程所选择的初始序号。
由于tcp连接建立是由tcp客户进程主动发起的,因此称为主动打开连接。 请注意tcp规定syn被设置为1的报文段不能携带数据但要消耗掉一个序号。
tcp服务器进程收到tcp连接请求报文段后,如果同意建立连接,则向tcp客户进程发送tcp连接请求确认报文段,并进入同步已接收状态。
- 该报文段首部中的同步位syn和确认位ack 都设置为1,表明这是一个tcp连接请求。
- 序号字段seq被设置了一个初始值y,作为tcp服务器进程所选择的初始序号。
- 确认号字段ack的值被设置成了x 1,这是对tcp客户进程所选择的初始序号seq的确认。
请注意这个报文段也不能携带数据,因为它是syn被设置为一的报文段但同样要消耗掉一个序号。
tcp客户进程收到tcp连接请求确认报文段后,还要向tcp服务器进程发送一个普通的tcp 确认报文段并进入连接已建立状态。
- 该报文段首部中的确认位ack被设置为1,表明这是一个普通的tcp确认报文段 。
- 序号字段seq 被设置为x 1,这是因为tcp客户进程发送的第一个tcp报文段的序号为x,并且不携带数据,因此第二个报文段的序号为x 1。
- 确认号字段ack被设置为y 1,这是对tcp服务器进程所选择的初始序号的确认。
请注意tcp规定,普通的tcp确认报文段可以携带数据。但如果不携带数据则不消耗序号,在这种情况下所发送的下一个数据报文段的序号仍是x 1。
tcp服务器进程收到该确认报文段后也进入连接已建立状态,现在tcp双方都进入了连接已建立状态,他们可以基于已建立好的tcp连接进行可靠的数据传输了。
5、三次握手文字总结
三次握手是 tcp 连接的建立过程。在握手之前,主动打开连接的客户端结束 close 阶段,被动打开的服务器也结束 close 阶段,并进入 listen 阶段。随后进入三次握手阶段:
① 首先客户端向服务器发送一个 syn 包,并等待服务器确认,其中:
- 标志位为 syn,表示请求建立连接;
- 序号为 seq = x(x 一般取随机数);
- 随后客户端进入 syn-sent 阶段。
② 服务器接收到客户端发来的 syn 包后,对该包进行确认后结束 listen 阶段,并返回一段 tcp 报文,其中:
- 标志位为 syn 和 ack,表示确认客户端的报文 seq 序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接;
- 序号为 seq = y;
- 确认号为 ack = x 1,表示收到客户端的序号 seq 并将其值加 1 作为自己确认号 ack 的值,随后服务器端进入 syn-recv 阶段。
③ 客户端接收到发送的 syn ack 包后,明确了从客户端到服务器的数据传输是正常的,从而结束 syn-sent 阶段。并返回最后一段报文。其中:
- 标志位为 ack,表示确认收到服务器端同意连接的信号;
- 序号为 seq = x 1,表示收到服务器端的确认号 ack,并将其值作为自己的序号值;
- 确认号为 ack= y 1,表示收到服务器端序号 seq,并将其值加 1 作为自己的确认号 ack 的值。
- 随后客户端进入 established。
当服务器端收到来自客户端确认收到服务器数据的报文后,得知从服务器到客户端的数据传输是正常的,从而结束 syn-recv 阶段,进入 established 阶段,从而完成三次握手。
5、是否可以使用“两报文握手”建立连接?
为什么tcp客户进程最后还要发送一个普通的tcp确认报文段?
考虑这样一种情况,tcp客户进程发出一个tcp连接请求报文段,但该报文段在某些网络节点长时间滞留了,这必然会造成该报文段的超时重传。
假设重传的报文段被tcp服务器进程正常接收,tcp服务器进程给tcp客户进程发送一个tcp连接请求确认报文段,并进入连接已建立状态。
请注意,由于我们改为两报文握手,因此tcp服务器进程发送完tcp连接请求确认报文段后,进入的是连接已建立状态,而不像三报文握手那样进入同步已接收状态,tcp服务器进程并等待tcp客户进程发来针对tcp连接请求确认报文段的普通确认报文段。tcp客户进程收到tcp连接请求确认报文段后进入tcp连接已建立状态,但不会给tcp服务器进程发送针对该报文段的普通确认报文段。
现在,tcp双方都处于连接已建立状态,他们可以相互传输数据,之后可以通过四报文挥手来释放连接,tcp双方都进入了关闭状态。
一段时间后,之前滞留在网络中的那个失效的tcp连接请求报文段到达了tcp服务器进程,tcp 服务器进程会误认为这是tcp客户进程又发起了一个新的tcp连接请求,于是给tcp客户进程发送tcp连接请求确认报文段并进入连接已建立状态。
该报文段到达tcp客户进程,由于tcp客户进程并没有发起新的tcp连接请求,并且处于关闭状态,因此不会理会该报文段。
但tcp服务器进程已进入了连接已建立状态,他认为新的tcp连接已建立好了,并一直等待tcp客户进程发来数据。这将白白浪费tcp服务器进程所在主机的很多资源。
综上所述,采用三报文握手,而不是两报文握手来建立tcp连接,是为了防止已失效的连接请求报文段突然又传送到了tcp服务器进程因而导致错误。
6、两次握手文字总结
三次握手的主要目的是确认自己和对方的发送和接收都是正常的,从而保证了双方能够进行可靠通信。若采用两次握手,当第二次握手后就建立连接的话,此时客户端知道服务器能够正常接收到自己发送的数据,而服务器并不知道客户端是否能够收到自己发送的数据。
我们知道网络往往是非理想状态的(存在丢包和延迟),当客户端发起创建连接的请求时,如果服务器直接创建了这个连接并返回包含 syn、ack 和 seq 等内容的数据包给客户端,这个数据包因为网络传输的原因丢失了,丢失之后客户端就一直接收不到返回的数据包。由于客户端可能设置了一个超时时间,一段时间后就关闭了连接建立的请求,再重新发起新的请求,而服务器端是不知道的,如果没有第三次握手告诉服务器客户端能否收到服务器传输的数据的话,服务器端的端口就会一直开着,等到客户端因超时重新发出请求时,服务器就会重新开启一个端口连接。长此以往, 这样的端口越来越多,就会造成服务器开销的浪费。