什么是TCP
在了解三次握手四次挥手前必须先了解什么是tcp。
TCP是面向连接的,可靠的,基于字节流的传输层协议。
- 连接:所谓连接其实是保证可靠性和流量控制的状态信息的总和,包括sokict,滑动窗口和序列号。
- 可靠性:tcp通过序列号,重传机制,滑动窗口等一系列控制机制保证数据的无重复,无丢失,有序的被接受端处理。
- 字节流:tcp的数据是基于字节流,因此是无边界,数据是可以无限大的,tcp可以通过分片机制将数据有序发送到接收端。
TCP结构
TCP的头部在无“选项”字段的情况下是20个字节。包括:
- 2字节的源端口
- 2字节的目标端口
- 4字节序列号
- 4字节确认序列号
- 4位的首部长度
- 6位保留字段
- 6位标志位(SYN,ACK,RST,FIN,URG,PSH)
- 2字节窗口大小
- 2字节校验和
- 2字节紧急指针
这里需要说明的是“选项”这个字段是用来辅助解决可靠性问题的,正是因为这个字段的长度是不确定的,所以需要“首部长度”这个字段来表示TCP头部的长度。
TCP三次握手过程
什么是三次握手
TCP是基于连接的,所以TCP在使用前必须先建立连接,TCP建立连接的过程是基于三次握手的。
- 首先服务端的应用程序监听某个端口,也就是建立一个listened状态的Socket,服务端处于listen状态。
- 当客户端创建一个Socket,并调用connect函数连接服务端的时候,会向服务端发送一个SYN状态为1的tcp报文,并携带自己的随机序列号。客户端处于syn_send状态。
- 服务端接收到SYN报文后,会创建一个连接放入当前Socket的半连接队列,然后回复ACK+SYN报文并携带自己的随机序列号和确认序列号(客户端序列号+1)。服务端处于syn_recv状态。
- 客户端接受到服务端的ack后,经过一定处理,会给服务端回复一个ack报文,并携带确认序列号(服务端序列号+1)。此时客户端处于establisten状态。
- 服务端收到ack报文后,服务端会把半连接队列中的连接放入全连接队列。然后处于establisten状态。
至此,tcp连接建立完成,注意第三次握手是可以传输数据的。在这之前不能传输数据。
为什么是三次握手
一般大家都会认为三次握手是为了保证客户端和服务端双方都能确认自身和接收端建立单向连接和保证自身能够发送和接受成功数据。
这样答本身也没有错,但是太粗化了。
既然握手是为保证连接的建立,那就要先知道什么是TCP连接。
TCP连接是保证可靠性和流量控制的状态信息的总和,包括socket,序列号,滑动窗口。
在这里这个序列号至关重要,是保证消息无重复,无丢失,有序的关键,因此这里其实就是为了保证序列号的同步。
客户端给服务端发送一个初始序列号,服务端回复syn+ack,就是告诉客户端序列号已经收到了并且把服务端的初始序列号发送给客户端,客户端收到后也要回复给服务端表示序列号已经收到,这样就能保证双方都能确保序列号同步。
但是这还不是最重要的原因,最重要的原因是防止历史连接初始化再次连接。比如有这样一种情况,客户端发送syn包给服务端,但是网络阻塞了,服务端没有收到,所以服务端也不会回复,客户端收不到回复就会重新发送syn包,但是就在这时候服务端接收到了第一个syn包,并且回复客户端,这个时候客户端会进行比对校验这是不是自己最新发送的syn回复包,如果不是的话就会给服务端发送rst包,表示要求服务端中断这个连接。这也是三次握手的意义所在。
如果说没有第三次握手,那么在发生上面的这个情况后,服务端就会为每个syn请求创建连接,连接是需要占用内存的,就会耗费很多的资源。造成资源浪费,所以三次握手很有必要。
那么四次握手是否可以呢?
四次握手的话也是可以的,四次握手其实就是客户端发送syn包给服务端,服务端回复ack包,服务端发送syn包给客户端,客户端回复ack包,三次握手中的第二次握手回复的是syn+ack包,所有相当于合并了四次握手中的中间两次,所以三次握手最好。
四次挥手的过程
TCP是双向连接,所以两个方向上的连接都要断开。
- 断开前客户端和服务端都处于ESTABLISTENED状态。
- 客户端调用close方法尽心断开连接操作,客户端会发送fin包给服务端。客户端处于fin_wait1状态
- 服务端接收到fin包后,会回复一个ack。此时服务端处于closed_wait状态。
- 客户端收到服务端的ack后,表示已经断开了自己到服务端的连接,但是服务端到客户端的连接还没有断开,客户端需要等待服务端主动请求断开。此时客户端处于fin_wait2状态。
- 服务端之所以不会立刻给客户端发送fin包是因为服务端可能还存在要发送的数据,所以服务端需要把要处理的数据处理完在发送fin包给客户端,此时数据已经处理完,服务端主动给客户端发送fin包,此时服务端处于last_ack状态。
- 客户端收到fin包后,会回复ack给服务端,此时客户端处于time_wait状态。
- 服务端收到ack后将状态置为close.
- 客户端此时并不会直接进入close状态,而是会进入time_wait状态, 这个状态会持续2MSL时间。
在网络传输的世界里,有两个值是用来表示数据包失效的:
- MSL是报文在网络中的最大存活时间,超过这个时间就会被丢弃。
- TTL:在ip层的头部中有一个TTL字段保存所经过的路由数,没经过一个路由数就会减1,当为0的时候,数据就会被丢弃。
所以一般情况下MSL会大于TTL减为0所消耗的时间。
这里为什么是2倍的MSL呢?
因为当客户端接收到服务端的fin包后,会向服务端回复ack,但是客户端不知道这个ack是否发送成功了,所以客户端需要确认服务端接受成功后才能置为close状态,怎么确认呢,因为失败重传机制的存在,如果因为网络阻塞服务端没有收到ack,服务端会再次发送一次fin,一次ack包和再一次fin包就是2倍的MSL。MSL的计时是从收到fin包并且发送ack包开始的。
除了上面说的保证客户端的ack发送到服务端,并被正确接收,从而保证被关闭连接的一方可以正确关闭。
还能保证那些阻塞在网络中旧的连接,在端口又被复用的情况下,被接收到,这样就会发生数据错乱,而time_wait可以保证全部的网络中的连接被丢弃。
MSL默认是30秒。需要注意的是time_wait 的状态多了以后会占用内存资源和端口资源,所以不宜太多。
为什么要进行四次挥手?
tcp是双向连接,客户端发送fin包给服务端,服务端回复ack,只是客户端告诉服务端不再向服务端发送数据。
还需要服务端告诉客户端,服务端不再向客户端发送数据了,也就是服务端也要想客户端发送fin包,客户端也要给服务端回复ack包,这时候服务端和客户端才能进入close状态。
服务端在收到客户端发送的fin包并回复ack包后,服务端并不能马上向发送端发送fin包,因为此时可能还有连接在处理数据,必须等到数据处理完后才能向客户端发送fin包。
正因为这个原因,不能像三次握手那样把中间两次合并。