【HTTP】HTTP三次握手与四次挥手
1 HTTP的握手和挥手
HTTP是基于TCP,所以谈到HTTP的握手挥手,即建立TCP连接的握手挥手
1.1 前置知识
- 同步序列编号SYN(Synchronize Sequence Numbers)。是TCP/IP建立连接时使用的握手信号。SYN报文内容中会包含一个初始化序列号(ISN)。
- 初始化序列号(ISN)是在建立TCP连接时,用于标识TCP报文段中数据的起始字节的一个数字。在三次握手的第一个步骤中,客户端发送的SYN报文会携带一个初始化序列号ISN,这是一个随机数。服务器在回应的SYN-ACK报文中会确认这个序列号,并携带自己的ISN。ISN在后续的通信中扮演着重要的角色,因为后续的序列号都是基于ISN计算出来的。如果序列号不匹配,报文将被认为是非法的,会被丢弃。
- ACK (Acknowledge character)即是确认字符,在数据通信中,接收站发给发送站的一种传输类控制字符。表示发来的数据已确认接收无误。
1.2 HTTP的三次握手
握手的主要目的是同步双方(客户端、服务端)的初始化序列号(ISN),确认双方的接收和发送能力正常,以及分配资源,准备数据传送。通过三次握手,可以确保双方的通信信道已经建立,为数据传送做好准备。
- 第一次握手:建立连接时,客户端发送SYN包(syn=x)到服务器,并指明客户端的初始化序列号 ISN(client_isn),并进入 SYN_SENT 状态,等待服务器确认
- 第二次握手:服务器收到SYN包,必须确认客户的SYN,同时自己也发送一个SYN包(syn=y)到客户端,并指明服务端的初始化序列号 ISN(server_isn),即SYN+ACK包(ack=x+1),此时服务器进入 SYN_RECV 状态;
- 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=y+1),此包发送完毕,客户端和服务器进入 ESTABLISHED (TCP连接成功)状态,完成三次握手。
1.3 HTTP的四次挥手
- 第一次挥手:客户端发送一个FIN,用来关闭客户端到服务端的数据传送,客户端进入 FIN_WAIT_1 状态;
- 第二次挥手:服务端收到FIN后,发送一个ACK给客户端,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),服务端进入 CLOSE_WAIT 状态;
- 第三次挥手:服务端发送一个FIN,用来关闭服务端到客户端的数据传送,服务端进入 LAST_ACK 状态;
- 第四次挥手:客户端收到FIN后,客户端t进入 TIME_WAIT 状态,接着发送一个ACK给Server,确认序号为收到序号+1,服务端进入CLOSED状态,完成四次挥手。
1.4 提问
1.4.1 为什么是三次握手?不是两次或者四次?
- 第一次握手:客户端发送网络包,服务端收到了。
- 服务端视角:客户端的发送能力、服务端的接收能力是正常的。
- 第二次握手:服务端发包,客户端收到了。
- 客户端视角:服务端的接收、发送能力,客户端的接收、发送能力是正常的。不过此时服务器并不能确认客户端的接收能力是否正常。
- 第三次握手:客户端发包,服务端收到了。
- 服务端视角:客户端的接收、发送能力正常,服务器自己的发送、接收能力也正常。
因此,至少需要三次握手才能确认双方的接收与发送能力是否正常。
为什么二次握手不行?根据上面的推断,二次握手后,服务端对客户端的接收能力是不能确认正常的,此时如果服务端直接开始传输数据则不能保证TCP的正常连接。试想如果是用两次握手,则会出现下面这种情况:
如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。、
三次握手才可以阻止重复连接,前两次握手都是保证一方已经建立好连接的准备。形象解释:客户端需要到服务端家去串门(访问),第一次是客户端敲服务端门有人在家吗?第二次是服务端回答有人在家的你是某某吗?第三次客户端回答我是某某然后打开门进屋坐客。
1.4.2 ISN(Initial Sequence Number)是固定的吗?
当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。
三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。
1.4.3 什么是半连接队列?什么是全连接队列?
服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列。
当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。
这里在补充一点关于SYN-ACK 重传次数的问题:
服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s......
1.4.4 三次握手过程中可以携带数据吗?
其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据
为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。
也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。
1.4.5 挥手为什么需要四次?
因为当服务端收到客户端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当服务端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉客户端,"你发的FIN报文我收到了"。只有等到我服务端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四次挥手。