三次握手与四次挥手
TCP的三次握手和四次挥手
三次握手
三次握手其实就是建立一个TCP连接时,需要客户端和服务器总共发送3个包,确认双方的接收能力和发送能力是否正常、指定自己的初始化序列号为后面的可靠性传送做准备。
ACK:确认序号有效;SYN:发起一个新连接。
刚开始客户端处于Closed的状态,服务器处于Listen状态。
- 第一次握手:首先客户端向服务器发送一段TCP报文,其中:标记位SYN表示请求建立新连接;序号seq=X,X一般为1;随后客户端进入SYN-SEND阶段。
- 第二次握手:服务器接收到来自客户端的TCP报文后,结束Listen阶段,并返回一段TCP报文,其中:标志位为SYN和ACK,表示确认客户端的报文seq序号有效,服务器能正常接收客户端发送的数据,并同意创建新连接;序号seq=Y;确认号Ack=X+1,表示收到客户端的序号seq并将其值加1作为自己确认号ACK的值;随后服务器进入SYN-RCVD阶段。
- 第三次握手:客户端收到来自服务器的确认收到数据的TCP报文之后,明确了从客户端到服务器的数据传输是正常的,结束SYN-SENT阶段,并返回最后一段TCP报文。其中:标志位为ACK,表示确认收到服务器发来的同意连接信号;序号seq=X+1,表示收到服务器端的确认号ACK,并将其值作为自己的序号;确认号为ACK=Y+1,表示收到服务器端的序号seq,并将其值加1作为自己的确认号ACK的值;随后客户端进入ESTABLISHED阶段。
在客户端与服务器传输的TCP报文中,双方的确认号ACK和
序号seq的值,都是在彼此ACK和seq值的基础上进行计算的,这样保证了TCP报文传输的连贯性。一旦出现某一方发出的TCP报文丢失,便无法继续握手,以此确保了三次握手的顺利完成。
为什么进行三次握手
为了防止服务器开启一些无用的连接增加服务器开销以及防止已失效的连接请求报文突然又传送到了服务器,因而产生错误。
四次挥手
FIN:释放一个连接
四次挥手就是TCP连接的释放。连接的释放必须是一方主动释放,一方被动释放。以下是以客户端主动发起释放连接的图解:
挥手之前主动释放连接的客户端结束ESTABLISHED阶段,随后开始“四次挥手”:
- 首先客户端想要释放连接,向服务器端发送一段TCP报文,并且停止在客户端到服务器端方向上发送数据,但是客户端仍能接收从服务器端传输过来的数据。这里不发送的是正常连接时传输的数据,而不是一切数据,所以客户端仍能发送ACK确认报文。
- 服务器收到客户端发出的TCP报文后,确认了客户端想要释放连接,返回一段TCP报文。随后服务器开始准备释放服务器到客户端方向上的连接。
前两次挥手既让服务器知道了客户端想要释放连接,也让客户端知道了服务器端已经接收到了连接释放的请求,于是,可以确认关闭客户端到服务器端方向上的连接了。
- 服务器端在发送确认报文后,停止在服务器端到客户端方向上的发送数据,但是服务器端仍能够接收到从客户端传输过来的数据。
- 客户端收到从服务器端发出的确认TCP报文,确认了服务器端已做好释放连接的准备,随后向服务器发送一段报文,并等待2MSL。
后两次挥手既让客户端知道了服务器端准备好释放连接,也让服务器知道了客户端已经收到自己准备好释放连接的确认报文段了。于是,可以确认关闭服务器端到客户端的连接,由此完成了四次挥手。
为什么握手是三次,挥手是四次
之所以是三次握手是因为在第二次握手时服务器端发送给客户端的TCP报文是请求连接和确认报文一起发送给客户端的。请求连接表示服务器端同意建立连接,确认报文表示告诉客户端,服务器收到了它的请求。
而四次挥手是因为释放连接报文和确认接收报文分别传输的。因为在收到客户端发来的释放连接请求时,服务器可能还有必要的数据需要处理,所以只能先发送确认报文,等准备好释放连接后再发送释放连接报文。
为什么客户端需要等待2MSL
为了确认服务器端是否收到客户端发出的ACK确认报文。
当客户端发出最后的ACK确认报文后,并不能确定服务器端是否收到,所以客户端会设置一个2MSL(MSL:一段报文在传输过程中的最大生命周期,2MSL即服务器端发出报文以及客户端发出确认报文的总时长)的计时器。在客户端发出最后一个确认报文段后,服务器端在1MSL内没有收到客户端发来的确认报文,就再次向客户端发送报文。如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。
什么是SYN洪泛攻击,如何防范?
SYN洪泛攻击属于DOS攻击的一种,它的原理是:
- 在三次握手中,服务器发送
SYN/ACK
包之后,收到客户端发送的ACK
之前的TCP连接称为半连接,此时服务器处于SYN_RECV
状态,如果收到客户端的ACK,则TCP连接成功;如果未收到,则会不断重发请求直至成功。 SYN
的攻击者伪造大量IP地址,向服务器不断发送SYN
包,服务器返回SYN/ACK
包,并等待客户的信任。由于源地址是不存在的,服务器需要不断地重新发送SYN/ACK
包,直至超时。- 这会影响正常的
SYN
包的发送,导致网络拥塞,甚至超时。
防范:
- SYN cookies技术
- 增大最大半连接数,缩短超时时间
- 过滤网关
三次握手的第三次丢包会发生什么?
- 服务器端:超过一定时间未收到客户端发来的确认包,会重传
SYN/ACK
包、若多次重传后还未收到确认包,则会关闭该连接。 - 客户端:客户端在发送
ACK
包后会认为该连接建立成功,随后发送数据进行通信,服务器段会返回一个RST
包告诉客户端这个连接异常已被关闭,这样客户端就知道三次握手失败了。
如果已经建立了连接,但客户端出现了故障怎么办?
通过定时器与超时重传机制,尝试获取确认,直到最后自动断开连接。
TCP 设有一个计时器。服务器每收到一次客户端的数据,都会重新复位这个计时器,时间通常是设置为 2 小时。若 2 小时还没有收到客户端的任何数据,服务器就开始重试:每隔 75 分钟发送一个探测报文段,若发送 10 个探测报文后客户端依然没有回应,那么服务器就认为连接已经断开了。
四次挥手
FIN:释放一个连接
四次挥手就是TCP连接的释放。连接的释放必须是一方主动释放,一方被动释放。以下是以客户端主动发起释放连接的图解:
挥手之前主动释放连接的客户端结束ESTABLISHED阶段,随后开始“四次挥手”:
- 首先客户端想要释放连接,向服务器端发送一段TCP报文,标志位FIN=1,表示请求释放连接,序号seq=U,随后客户端进入半关闭状态,并且停止在客户端到服务器端方向上发送数据,但是客户端仍能接收从服务器端传输过来的数据。这里不发送的是正常连接时传输的数据,而不是一切数据,所以客户端仍能发送ACK确认报文。
- 服务器收到客户端发出的TCP报文后,确认了客户端想要释放连接,返回一段TCP报文,标志位为ACK=1,表示接收到客户端发来的释放连接请求,序号为seq=V,ack=U+1。随后服务器开始准备释放服务器到客户端方向上的连接。
前两次挥手既让服务器知道了客户端想要释放连接,也让客户端知道了服务器端已经接收到了连接释放的请求,于是,可以确认关闭客户端到服务器端方向上的连接了。
- 服务器端在发送确认报文后,等待做好了释放服务器到客户端方向上的连接准备后,再次向客户端发送一段TCP报文,标志位为FIN=1,ACK=1,表示已经准备好释放连接了,序号seq=W,ack=U+1,停止在服务器端到客户端方向上的发送数据,但是服务器端仍能够接收到从客户端传输过来的数据。
- 客户端收到从服务器端发出的确认TCP报文,确认了服务器端已做好释放连接的准备,随后向服务器发送一段报文,标志位ACK=1,表示已经接收到服务器准备好释放连接的信号,序号seq=U+1,ack=W+1,并等待2MSL。
后两次挥手既让客户端知道了服务器端准备好释放连接,也让服务器知道了客户端已经收到自己准备好释放连接的确认报文段了。于是,可以确认关闭服务器端到客户端的连接,由此完成了四次挥手。
为什么握手是三次,挥手是四次
之所以是三次握手是因为在第二次握手时服务器端发送给客户端的TCP报文是请求连接和确认报文一起发送给客户端的。请求连接表示服务器端同意建立连接,确认报文表示告诉客户端,服务器收到了它的请求。
而四次挥手是因为释放连接报文和确认接收报文分别传输的。因为在收到客户端发来的释放连接请求时,服务器可能还有必要的数据需要处理,所以只能先发送确认报文,等准备好释放连接后再发送释放连接报文。
为什么客户端需要等待2MSL
为了确认服务器端是否收到客户端发出的ACK确认报文。
当客户端发出最后的ACK确认报文后,并不能确定服务器端是否收到,所以客户端会设置一个2MSL(MSL:一段报文在传输过程中的最大生命周期,2MSL即服务器端发出报文以及客户端发出确认报文的总时长)的计时器。在客户端发出最后一个确认报文段后,服务器端在1MSL内没有收到客户端发来的确认报文,就再次向客户端发送报文。如果客户端在2MSL内,再次收到了来自服务器端的FIN报文,说明服务器端由于各种原因没有接收到客户端发出的ACK确认报文。客户端再次向服务器端发出ACK确认报文,计时器重置,重新开始2MSL的计时;客户端在2MSL内没有再次收到来自服务器端的FIN报文,说明服务器端正常接收了ACK确认报文,客户端可以进入CLOSED阶段,完成“四次挥手”。
TIME-WAIT 状态过多会产生什么后果?
从服务器来讲,短时间内关闭了大量的Client连接,就会造成服务器上出现大量的TIME_WAIT连接,严重消耗着服务器的资源,此时部分客户端就会显示连接不上。
从客户端来讲,客户端TIME_WAIT过多,就会导致端口资源被占用,因为端口就65536个,被占满就会导致无法创建新的连接。
TIME_WAIT 是服务器端的状态?还是客户端的状态?
TIME_WAIT 是主动断开连接的一方会进入的状态,一般情况下,都是客户端所处的状态;服务器端一般设置不主动关闭连接。
TIME_WAIT 需要等待 2MSL,在大量短连接的情况下,TIME_WAIT会太多,这也会消耗很多系统资源。对于服务器来说,在 HTTP 协议里指定 KeepAlive(浏览器重用一个 TCP 连接来处理多个 HTTP 请求),由浏览器来主动断开连接,可以一定程度上减少服务器的这个问题。
TCP粘包
在默认情况下,TCP连接会启用延时传送算法,在数据发送之前缓存他们,如果短时间内有多个数据发送,会缓冲到一起一次发送,这样可以减少IO消耗提高性能。如果是发送文件的话,不用处理粘包问题;如果发送的是数据的话,就需要处理粘包问题。
粘包有以下几种情况:如果连续调用两次sand分别发送两端数据data1、data2,那么:
- 先接收到data1,再接收到data2
- 先接收到data1的部分数据,然后接收到data1的剩余部分和data2
- 先接收到data1和data2的部分数据、然后接收到data2的剩余部分
- 一次性接收到了data1和data2
如何解决粘包问题
- 多次发送之前间隔一个等待时间:只需要等上一段间隔时间结束再进行下一次send,适用于交互频率较低的场景;如果交互频率频繁,传输效率较低。
- 不使用延迟传送算法(Nagle):适用于每次发送的数据比较大,但文件不是很大的情况,
- 进行封包/拆包:给每个数据包的之前或之后放一些有特征的数据,就收到数据后按特征进行分割。
为什么UDP不会粘包
- TCP是面向流的协议,UDP是面向消息的协议。UDP段是一条消息,必须以消息为单位接收数据,不能以字节为单位接收数据。
- 每个UDP包中都有消息头,这样对于接收端来说就很容易区别UDP包了。
TCP的keep-alive
keep-alive就是定义一个时间段,如果在这个时间段内没有报文传输,那么服务器就会每隔一个时间间隔发送一个探测报文,如果连续几个探测报文没有收到响应,那么就会认为当前的TCP链接已经断开,服务器就会断开连接。
TCP保活机制
为什么需要保活机制
TCP建立连接后,在一段时间内双方没有发送任何数据,那么:
- 怎么判断对方是否还处于连接状态。这是因为,TCP的非正常断开的连接系统并不能侦测到(比如网线断掉)。
- 长时间没有任何数据发送,连接可能被中断。网络连接会经过路由器、防火墙等设备,这些设备可能会断掉长时间没有活动的连接。
TCP保活机制的实现
保活机制由一个定时器实现的,当计时器被激发,一端将发送一个保活探测报文,另一端收到报文会返回一个ACK报文作出响应。
过程描述:
在开启了keep-alive后,连接的一段会向另一端发送一个探测报文,如果收到响应则重置计时器,如果没有收到响应,则经过一段时间间隔后再次发送探测报文,当达到一定次数还没有收到响应后,则认为连接不可到达,则断开连接。
保活机制的弊端
- 会占用不必要的带宽
- 出现短暂的网络错误时,保活机制会把正常的TCP连接断开
拔掉网线后几秒,再插回去,原本的TCP连接还会存在嘛?
客户端拔掉网线后,并不会直接影响到TCP连接的状态。所以,拔掉网线后,TCP链接还会存在。
- 在传输数据的情况下:
- 在客户端拔掉网线后,如果服务器发送了数据报文,那么服务器在没有收到确认报文就会进行重传,如果重传次数没有达到最大值之前,客户端将网线,那么双方的TCP连接还是正常存在的
- 如果重传次数达到了最大值,那么服务器就会断开TCP连接,等到客户端插回网线并重新发送数据时,服务器会返回一个RST报文,表示TCP连接已经断开了,客户端收到这个报文后就知道了TCP连接已经断开了
- 在不传输数据的情况下:
- 如果双方没有开启keep-alive机制,那么在客户端拔掉网线后没有插回,那么客户端和服务器的TCP连接状态还是会一直存在
- 如果开启了keep-alive机制,那么客户端拔掉网线后,如果客户端一直不插回网线,那么服务器就会认为客户端已经断开了连接,那么服务器也会断开连接。如果在TCP探测期间客户端插回了网线,那么双方原本的TCP连接还是能正常存在。
除了客户端拔掉网线后,还有客户端宕机
和杀死进程
两种场景
- 宕机。客户端宕机和拔掉网线是一样无法被服务器感知的,所以如果在没有数据传输并没有开启TCP的keep-alive机制的情况下,服务器的TCP连接会一直处于ESTABLISHED连接状态,知道服务器重启进程。
- 杀死进程。杀死客户端进程后,客户端会向服务器进行四次挥手。