HTTP系列之 三次握手,四次挥手


一.关键词

1.报文头部:

1.1 ack :确认号码

  服务端收到客户端发过来的报文后需要给客户端回复一个ack数据包,回复报文的确认号ack的值等于服务端收到的报文的序列号seq的值+1。

  比如服务端当前收到的报文的序列号是ISN+2048(也就是第3段数据),那么它回复客户端的报文的确认号的值就是ISN+2048+1,其作用就是告诉客户端ISN+2048+1之前的所有数据都已经收到了。

  a.客户端发送一个报文后并不需要等服务端的ack回复就可以接着发下一条报文。

  b.服务端回复ack时,必须要确保确认号ack之前的数据全部已经收到了,比如上面的例子,如果序列号是ISN+2048的报文收到了,但是ISN+1024的报文还没收到,那就不能回复ISN+2048+1的ack。

  c.服务端在收到数据后不是立即给客户端发送ack的,一般会有200ms的延迟(系统有个定时器每隔200ms来检查是否需要发送ack包)。这么做是因为TCP数据包到达的顺序是不保证的,就比如上面必须要等ISN+1024的数据收到了才能回复ISN+2048+1的ack,这个时候就只用回复ISN+2048+1这一个ack就可以了,不需要回复ISN+1024+1的ack了,因为回复ISN+2048+1的ack就已经告诉客户端ISN+2048+1之前的数据已经全部收到了,这样做还可以减少网络流量。当然如果ISN+1024的数据丢包导致服务端一直没收到,那客户端也就一直收不到ack回复,客户端就会从上次收到的ack回复开始重发数据,包括服务端已经收到的ISN+2048数据也会重发。另外如果服务端刚好也有数据要发给客户端,那么就会在发送数据的TCP数据包里带上ack信息。

1.2seq :序列号,

  为了确保发送多条数据的顺序的正确性。初始序列号被系统初始化为一个随机值即 ISN,一个报文的序列号就是ISN+这个报文携带的数据的第一个字节的偏移量。每次握手都会占据一个序列号,所以此后每次握手,序列号+1

2.标志位

ACK —— 为1时表示确认号是有效的,携带ACK标志的报文段也称确认报文段(握手使用)

SYN —— 用于初始化一个连接的序列号,为1表示建立一个连接,携带SYN标志的报文段为同步报文段。SYN标志位只有在TCP建立连接时(也就是三次握手的时候)才会被置为。客户端请求建立连接时(第一次握手)的报文就携带SYN标志和初始化的序列号(也就是起始序列号ISN),SYN标志是提醒服务端记住客户端的起始序列号;服务端也会初始化自己的起始序列号并回复客户端一条报文(第二次握手),这条报文包含服务端的起始序列号、SYN标志位和ACK标志位,其中SYN标志位用来提醒客户端记住服务端的起始序列号

FIN ——为1时用来告知对方本端要关闭连接了。

URG:为1时表示紧急指针有效。

PSH:为1时是提示接收端应用程序应该立即从TCP接受缓冲区中读走数据,为后续接收的数据让出空间

RST:为1时表示通知对方关闭连接或重新建立连接。一旦发送了复位报文段,发送端所有排队等待发送的数据都将被丢弃,而且发送完RST报文后TCP连接就关闭了,所以接收端收到RST报文后也就没有必要发送ACK包来确认了。

二.三次握手

第一次握手:(SYN=1, ACK=0, seq=x):

Client发送SYN标志位1的包到Server,以及初始序号x(保存在包头的序列号seq字段,简称ISN),和ACK标志位为0,并进入SYN_SEND状态,等待Server确认。

第二次握手:(SYN=1, ACK=1, seq=y, ack=x+1):

服务端收到客户端发过来的报文后,发现SYN=1(即为连接请求)。Server发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。Server确认ISN序列号y,放到seq域里,同时将确认序号(ack)设置为Client的 ISN 加1,即x+1。 发送完毕后,Server进入 SYN_RCVD 状态。

第三次握手:(ACK=1,seq=x+1,ack=y+1)

Client再次发送确认包(ACK),ACK标志位为1,并且把Server发来ISN的序号字段+1,放在确定字段中发送给对方。

发送完毕后,Client和Server建立连接,TCP 握手结束。

客户端收到服务端的回复后发现ACK=1 且 ack=x+1, 于是知道服务端已经收到了序列号为x的那段报文;同时 SYN=1,知道了服务端同意了这次连接,于是就将服务端的序列号y给存下来。然后客户端再回复一段报文给服务端,报文包含ACK标志位(ACK=1)、ack=y+1服务端序列号+1)、seq=x+1第一次握手时发送报文是占据一个序列号的,需要注意的是不携带数据的ACK报文是不占据序列号的,所以后面第一次正式发送数据时seq还是x+1)。当服务端收到报文后发现ACK=1并且ack=y+1,就知道客户端收到序列号为y的报文了,就这样客户端和服务端通过TCP建立了连接。  

三.四次挥手

 四次挥手 实际就是Client端和Server端关闭TCP连接的发送四个包的过程。

第一次挥手:(FIN=1,seq=u)

Client 想要关闭连接,Client 会发送一个FIN标志位置为1,当前序列号为u(ISN+发送内容长度+1)的包,表示需要关闭连接了。Client进入 FIN_WAIT_1 状态。

需要注意的是客户端发出FIN报文段后只是不能发数据了,但是还可以正常收数据;另外FIN报文段即使不携带数据也要占据一个序列号。

第二次挥手:(ACK=1,seq=v,ack=u+1)

Server收到Client发的FIN报文后给客户端回复确认报文,确认报文包含ACK标志位(ACK=1)、序列号seq为v(服务端ISN+发送内容)及确认序号ack为收到的序列号u+1的包,表明自己接受到了Client关闭连接的请求,但还未准备好关闭连接。Server进入 CLOSE_WAIT 状态,Client进入 FIN_WAIT_2 状态。

服务端处于关闭等待状态,而不是立马给客户端发FIN报文,这个状态还要持续一段时间,因为服务端可能还有数据没发完。

第三次挥手:(FIN=1,ACK=1,seq=w,ack=u+1)

当Server将剩余数据发送完之后,会发送一个自己的FIN包,序列号为u+1。Server进入 LAST_ACK 状态,等待来自Client的最后一个ACK。

服务端将最后数据(比如50个字节)发送完毕后就向客户端发出连接释放报文,报文包含FIN和ACK标志位(FIN=1,ACK=1)、确认号和第二次挥手一样ack=u+1、序列号seq=(服务端ISN+发送内容)+50)。 

第四次挥手:(ACK=1,seq=u+1,ack=w+1)

Client接收到来自Server端的关闭请求之后,发送最后一个ACK确认包(ACK=1)、确认号ack=w+1、序列号seq=u+1。Client进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。Server接收到这个确认包之后,关闭连接,进入CLOSED状态。 注意Client发出确认报文后不是立马释放TCP连接,而是要等待2MSL(最长报文段寿命的2倍时长)之后,没有收到Server的ACK,就确认Server进入CLOSED状态,自己也关闭进入CLOSED状态。 而Server一旦收到Client发出的确认报文就会立马释放TCP连接,所以Server结束TCP连接的时间要比客户端早一些。

四.常见面试题

1 为什么TCP连接的时候是3次?2次不可以吗?

在《计算机网络》一书中其中有提到,三次握手的目的是“为了防止已经失效的连接请求报文段突然又传到服务端,因而产生错误”,这种情况是:

一端(client)A发出去的第一个连接请求报文并没有丢失,而是因为某些未知的原因在某个网络节点上发生滞留,导致延迟到连接释放以后的某个时间才到达另一端(server)B。本来这是一个早已失效的报文段,但是B收到此失效的报文之后,会误认为是A再次发出的一个新的连接请求,于是B端就向A又发出确认报文,表示同意建立连接。如果不采用“三次握手”,那么只要B端发出确认报文就会认为新的连接已经建立了,但是A端并没有发出建立连接的请求,因此不会去向B端发送数据,B端没有收到数据就会一直等待,这样B端就会白白浪费掉很多资源。如果采用“三次握手”的话就不会出现这种情况,B端收到一个过时失效的报文段之后,向A端发出确认,此时A并没有要求建立连接,所以就不会向B端发送确认,这个时候B端也能够知道连接没有建立。

2 为什么TCP连接的时候是3次,关闭的时候却是4次?

因为只有在客户端和服务端都没有数据要发送的时候才能断开TCP。而客户端发出FIN报文时只能保证客户端没有数据发了,服务端还有没有数据发客户端是不知道的。而服务端收到客户端的FIN报文后只能先回复客户端一个确认报文来告诉客户端我服务端已经收到你的FIN报文了,但我服务端还有一些数据没发完,等这些数据发完了服务端才能给客户端发FIN报文(所以不能一次性将确认报文和FIN报文发给客户端,就是这里多出来了一次)。

3 为什么客户端发出第四次挥手的确认报文后要等2MSL的时间才能释放TCP连接?

这里同样是要考虑丢包的问题,如果第四次挥手的报文丢失,服务端没收到确认ack报文就会重发第三次挥手的报文,这样报文一去一回最长时间就是2MSL,所以需要等这么长时间来确认服务端确实已经收到了。

4 如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP设有一个保活计时器,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

 五.了解

1.SYN攻击

什么是 SYN 攻击(SYN Flood)?

在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.

SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

SYN 攻击是一种典型的 DoS/DDoS 攻击

  • 如何检测 SYN 攻击?

    检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

  • 如何防御 SYN 攻击?

    SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种:

    • 缩短超时(SYN Timeout)时间
    • 增加最大半连接数
    • 过滤网关防护
    • SYN cookies技术

2.TCP KeepAlive

TCP 的连接,实际上是一种纯软件层面的概念,在物理层面并没有“连接”这种概念。TCP 通信双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会。在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些 TCP 连接并未来得及正常释放,在软件层面上,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。

TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接