TCP三次握手和四次挥手过程如下
名词解释:
字段 含义 ACK 确认号,一般为1。 SYN 请求建立连接,设置为1,在其序列号字段进行初始化设置。 URG 紧急指针是否有效,为1,标识某一位需要被优先处理。 PSH 提示接收端立即从TCP缓冲区把数据读走。 RST 对方要求重新建立连接,复位。 FIN 希望断开连接。
步骤解释:
握手
第一次握手:客户端请求建立连接,发送序号为seq=x,进入syn-sent 状态,等待服务端响应。
第二次握手:服务端收到syn后,需要确认,ack=x+1,也会发送syn和序号seq=y,ACK+SYN,进入syn-recv状态。
第三次握手:客户端收到响应后,发送ack确认,ack=y+1,seq=x+1 递增,进入established,连接成功,完成三次握手。
挥手
第一次挥手:客户端主动发起释放连接请求,FIN=1,并停止发送数据,但是还会消耗一个seq,seq=x(x为最后一个发送的数据序号+1),并进入fin-wait1 状态。
第二次挥手:服务端收到连接释放请求后,进行ACK确认,ack=x+1,并携带自己的序号seq=y,此时服务端进入close-wait 状态,tcp通知应用层,客户端向服务端发送连接释放了,这时候处于半关闭状态,客户端没有数据需要发送,但是如果服务端还需要发送数据,客户端仍需要接受。
客户端收到服务端的确认后,进入fin-wait2状态,等待服务端发送释放连接,在此之前还需要接受服务端的数据。
第三次挥手:服务端将最后数据发送完毕后,就向客户端发送释放连接,ACK=1,ack=x+1,seq=z,此时服务端进入lask-ack状态,等待客户端的确认。
第四次挥手:客户端收到服务端连接释放请求后,必须发送ACK,ack=z+1,发送seq递增,此时客户端进入time-wait 等待状态,TCP此时还没有释放,必须等待2msl(最长报文寿命)时间后,客户端撤销TCB 后,进入closed状态。可见,服务端早于客户端结束TCP连接。
1、为什么连接是三次握手,关闭的时候是四次挥手?
因为在连接的时候,服务端第一次收到客户端的连接请求的时候,服务端需要进行确认,确认的时候可以发送数据(ACK+SYN),所以就合并成一次响应了。但是关闭连接的时候,服务端可能不会立马发送完数据,只有等到数据全部发送完以后,才会回复客户端FIN。所以不能合并连接。
2、为什么time-wait 需要经过2msl才进入closed状态。
客户端最后一次发送的ACK可能在网络中会丢失,time-wait 状态就是为了可能需要重发ACK报文的,假如ACK丢失后,服务端会重试,不断的发送FIN 报文,client不能立马关闭连接,需要等待2msl(就是报文一来一回的最大时间),如果服务端重传了FIN 报文,2msl会被重新计算。如果client在2msl时间内没再收到服务端的fin,就会判断ACK已经被服务端接受成功,此时结束TCP连接。
3、为什么不能进行2次握手连接
3次握手重要的功能是需要确认双方都有发送和接受的功能,如果只采用2次握手,服务端不能判断客户端释放能接受数据,另外如果采用2次握手的话会导致死锁,在第二次握手阶段,可能出现网络波动丢包的情况,如果存在,客户端认为还没有建立连接成功,会忽略服务端发送的任何数据报文,只等待确认报文,而服务端在发出去的数据分组超时后,会重发的发送数据分组,这样就会形成死循环。
4、如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP 会有个保活计时器,客户端如果出现故障,服务端不可能一直等下去浪费资源,服务端每收到客户端一次请求后会重置这个计时器,计时器时间通常为2个小时,若超过2小时后,服务端会发送一个探测报文,以后每隔75秒钟发送一次,若连续发送的10个报文都没响应,服务端会关闭连接。
5、time-wait 过多的危害
网络不好的情况下,主动方无time-wait等待,关闭前一个连接后,主动方与被动方又建立了新连接,这个时候被动方超时重传,或延时过来的fin报文,会影响新的tcp连接。即使没有建立新连接,当接收到被动方重传的fin报文或者延迟的fin报文后,会给被动方返回一个RST包,可能会影响被动方的其他连接。
6、time-wait 过多出现的场景
客户端和服务端在同一台机器上,每次循环connect服务端后,发送完数据立马关闭连接。
import socket
host = '127.0.0.1'
ip = 8888
while True:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((host, ip))
sock.sendall('hahahah')
sock.close()
在HTTP1.1协议中,有个 Connection 头,Connection有两个值,close和keep-alive
,这个头就相当于客户端告诉服务端,服务端你执行完成请求之后,是关闭连接还是保持连接,保持连接就意味着在保持连接期间只能由客户端主动断开连接。keep-alive设置的值就代表了服务端保持连接保持多久。HTTP默认的Connection值为close,那么就意味着关闭请求的一方几乎都会是由服务端这边发起的。那么这个服务端产生TIME_WAIT过多的情况就很正常了
7、time-wait过多解决方案
修改内核参数,扩大time-wait 数量
修改etc/sysctl.conf 文件,
net.ipv4.tcp_max_tw_buckets 是系统保持 time-wait 最大数量,超过此数量,系统会清除多余的time-wait 连接,系统日志会出现 TCP: time wait bucket table overflow。
由于硬件限制,该值可以设置net.ipv4.tcp_max_tw_buckets=262144 ,
不过在高并发下,系统会以产生连接的速度销毁正常的time-wait连接,此时可能会出现连接异常或新的连接建立失败。
启用快速回收
这里会涉及到 net.ipv4.tcp_tw_recycle 和 net.ipv4.tcp_timestamps
两个参数,net.ipv4.tcp_timestamps 本质是记录数据包发送的时间,tcp作为可靠传输,一个重要的机制是超时重传,如何涉及一个准确的RTO 对tcp性能很重要。当tcp_timestamps 和tcp_tw_recycle 两个选项同时开启情况下,开启per-host的PAWS机制 从而实现快速回收处于TIME-WAIT 状态的tcp连接。
PAWS — Protect Againest Wrapped Sequence numbers 目的是解决在高宽带下,tcp序号可能会重复使用带来的问题,PAWS 通用依赖于tcp_timestamps,在一个tcp中按序收到的tcp包的tcp_timestamps都是线性增长的,如果对per-host 的使用paws机制,则会解决time-wait中考虑的上一个流的数据包在下一个流中被当做有效数据包,这样就没必要等待2msl时间来结束time-wait状态了,只要等到足够的RTO ,解决好需要重传最后一个ACK的情况就好了,所以linux实现了这样一个机制:当tcp_tw_recycle 和tcp_timestamps 当时开启情况下 PAWS 也会开启。快速回收能达到700ms。但是这里会引申出另外一个问题,NAT 网络下会出现部分主机无法正常建立连接。
经过同一个NAT 转化后,由于不同的client的数据会携带各自不同的tcp_timestamps,进而无法保证到达服务端的数据包的时间戳会严格线性递增。可能会丢弃部分不合格的数据包。这样会造成连接成功率下降。所以 tcp_timestamp 只能关闭该参数,但是又违背了tcp设计的初衷。难解。
开启重用机制
开启重用用机制net.ipv4.tcp_tw_reuse,允许time-wait sockect 重新用于新的连接。默认为0 关闭状态。重用的条件是收到最后一个包后超过1s。官网手册有一段警告:It should not be changed without advice/request of technical experts. 该机制只对客户端有效。
使用长连接替换短连接
类似于http中的keep-alive 实现长连接,但是不安全。