TCP是可靠的传输,要保证可靠那么就有一个基础原则—《有请求就必须要有应答》,这也是tcp协议3次握手4次挥手的前置原因。

一、TCP数据头格式结构

源端口号和目标端口号是不可少的,没有这个也就没办法确认数据应该发给哪个应用

序号(Sequence Number):用于解决乱序问题,客户端接收到数据要重新编排合并,有了序号就可以正常排序(因为网络并不可靠,也有可能后发的数据提前到达,所以并不能按接收到数据的时间进行合并)

确认序号(Acknowledgment Number):基础原则,有来就有回,用于确认接收到的数据,保证丢包后可以重传。

首部长度(Header Length):用来标识tcp数据区的开始位置,接收端按此值对数据进行拆分,tcp首部长度一般为20 Bytes(5)。

标志位(Flags):包含URG紧急指针、ACK确认号、PSH操作标志、RST连接复位、SYN同步序列、FIN终止连接

URG:紧急指针是告知接收方某些“紧急数据”已经放在普通的数据流中,具体的处理由相应程序进行处理

PSH:操作标志其实就是发送数据操作(与UDP数据包发送的区别就在此),一般要等到缓冲区满了,才会把数据发送出去,当psh为1的时候 ,就会立即发送出去了

RST:连接复位,用来处理异常的连接关闭,当发送方发送RST时,不等缓冲区的包都发出去(可以丢失缓冲区,直接发送RST信号),而接收端收到RST包时(也不用发送ACK包给发送方确认)

ACK|SYN|FIN:这也是TCP的3次握手、4次挥手的关键标识,先看看下面的图。

二、TCP3次握手过程

(1)客户端和服务端都处于 CLOSED 状态。服务端主动监听某个端口,处于 LISTEN 状态。

(2)第一次握手:客户端发起连接 (标志SYN=1、序号(sn)seq=随机数x),之后处于 SYN-SENT 状态。

(3)第二次握手:服务端收到发起的连接,设置标志ACK=1,确认序号(an)seq=客户端序号x+1,同时设置标志SYN=1,序号(sn)seq=随机数y,对客户端进行回复,发送之后处于 SYN-RCVD 状态(注意:服务端的ack回复与syn连接请求是同一个包发送出去的)

(4)第三次握手:客户端收到 ACK (就能确认服务端已经接收到自己的SYN请求了),同时还有服务端的SYN(既然有SYN,那么就需要ACK进行回复),于是就会对服务端SYN进行回复,设置标志ACK=1,确认序号(an)seq =y+1发送回复包。

(5)服务端收到自己SYN对应的ACK 之后,处于 ESTABLISHED 状态,至此一个3次握手连接成功,应用层就可以进行发信息了,如下图(3次握手完成后,接下来的一个数据包是应用层的http协议)

这是正常的情况下的3次握手流程,TCP握手除了建立连接,还有一个主要的问题,就是确认TCP的序号(这也是TCP有问必有答的核心原因)

三、TCP握手的原因及细节问题

1、发送syn请求建立连接,如果ip不对、对方压根不在线,或者网络丢包,发送出去的数据包一直都没收到回复,那么是不是就能确定连接不通,也就没必要再发送数据报文了。

2、发送了syn请求,同时设置了序号seq=x,超时没收到ack回复,于是再重试又发送了seq=n再次确认,好死不死的,在发送了seq=n的时候,seq=x的回复收到了,那么此时,是不是直接就可以进入下一个状态,不用再管seq=n的回复了,答案是的,因为已经有答复了,我能从x的序号进行发送数据操作。

3、序号为什么是随机号,而不是从1开始,因为每个序号都 为1的话,无法确认是第一次发送syn,还是重试时发送的syn,用了随机号就能区别开了,这个随机号可以看成32位的计数器,每4微秒加1,产生相同的序号最快也需要4个小时,4个小时的旧的syn请求也因IP头的TTL生存时间原因早就死翘翘了

4、TCP为什么是3次握手,而不是2次或者是4次呢?

如果是2次握手,那么就要放弃最后一步的客户端对服务端的ACK回复吧,这样的话,对于服务端来说,就没有收到ACK回复,也就没办法确定自己的发送序号,无法保证数据正确传输,这显然是不行的。

再看4次握手,正常来说两台机器有问有答,正常来说是4次才对,事实上在握手的第二步,服务端将ACK回复与自己的SYN在一个数据包进行发送出去的。

A、为什么不分开?分开的话怎么确认这个syn就是上次ack的连接回复呢?那么客户端是要理解为新的tcp连接还是旧的ack相应 的syn?另外因网络太复杂(丢包、迟缓都有可能)如果syn比ack先达到了客户端呢,是不是就认为是新的请求?当服务端接收的syn是否还要再回复ack再发syn?(这样一直重复下去还要不要发数据了呢?)

B、既然是第一次连接除了tcp头也没其它的数据,分开发送,还有很多问题要解决,那么直接把ack的与syn合并一起发送会不会更好一些?不但可以减少一次数据包请求,还可以减少复杂性,也能避免ack丢失或比syn慢到的情况,合并在一起,你要丢就一起丢没关系,我再一起重发就好

综合以上的情况,2次握手是没办法让两台设备确认到发送序号,不能保证数据有序传输的,而4次握手是因为第二次的ack回复与syn请求合并一起就变成了3次,拆分成4次反而还要考虑更多的问题

四、TCP4次挥手过程

(1)客户端和服务端都处于 ESTABLISHED 状态。

(2)第一次挥手:当任意一方需要进行断开时,就可以发送标志FIN=1,seq=p(最后发送的序号sn),然后进入FIN_WAIT_1状态(优先发送断开连接请求的就是客户端)。

(3)第二次挥手:当服务端接收到请求后,会回复标志ACK=1,(确认序号an) seq= p+1,然后进入CLOSE_WAIT状态,服务端此时会通知应用层对方准备关闭连接了,同时将自己还未完成发送的数据进行发送。

(4)当客户端接收到ACK回复后,会进入进入FIN_WAIT_2状态,然后会进入一个延时等待结束时间(注意:虽然收到ack回复了,但并不能立即关闭连接,可能还有数据要接收的,所以要有一个延时等待,1是接收剩余数据,2是等待对方的FIN断开标志,此状态下也会停止了向服务端发送数据,但接收还是正常的)

(5)第三次挥手:依然是服务端发送的,当服务端的数据发送完成,就会发送标志FIN=1,seq=q,ack=p+1(即上次回复ACK时的seq),此次主要是告诉客户端,我也准备好结束连接了,然后进入 LAST_ACK状态。

(5)第四次挥手:客户端接收到服务端的FIN结束请求后,会回复标志ACK=1,seq=q+1序号,然后进入TIME_WAIT状态还要有2MSL等待时间(注意:这也是一个延时等待),只有超过2MSL(2个报文最长生存时间)之后,才能关闭连接变成CLOSED状态

(6)服务端接收到ACK回复后,就可以关闭连接,直接变成CLOSED状态。

五、四次挥手的细节问题

从断开的过程来看,挥手用了4次连接,为什么会是这样?

第二步:服务端回复ACK时不像连接那样把FIN一起发送了呢?这是因为收到了对方的FIN请求,但我可能还有数据未完成发送,需要所有数据都完成发送后,才能发起FIN请求 。

第二步:客户端收到了ACK后,为什么要等待?而不是直接关闭呢?其实呢,在这一步客户端已经关闭了向对方发数据了,但端口还是开放着。不直接关闭是对方可能还有数据未完成发送,要等待对方剩余数据,还要等待对方的FIN请求。另外直接关闭了,也可能空出来的端口有可能被新的应用占用了,这个时间服务端是不知道的,那么B端那些未完成发送或因网络迟缓迟到的数据包就会发送到这端口,那么就会产生了混乱,所以需要有足够的时间等待,且不能直接关闭。只有到了足够的时间才会结束(tcp协议对这个并没有处理,但Linux系统有tcp_fin_timeout 这个参数,设置一个超时时间)。

第四步:客户端回复对方的ACK也需要等待2MSL的时间也是如上的原因,必须超过MSL(Maximum Segment Lifetime报文最大生存时间)再释放,另外一个异常是服务端超过2MSL才还没收到ACK,那么就会再重发FIN,客户端接收到这个重发的FIN后,会直接发送RST标志,表示已经超时了,之后的我都不认了。