TCP三次握手,四次挥手异常情况(坑)
1、三次握手
(文中client,server均是相对而言)
(1)、client第一个syn包丢失,没有收到server的ack,则client进行持续重传syn包。总尝试时间为75秒。参与文献《TCP/IP详解 卷1:协议》p178
(2)、server收到了client的syn,并发出了syn+ack包,syn+ack包丢失。
client方面,因为没收server的。将执行情况(1);
server方面,超时时间内没有收到client的ack包(或者数据包),会持续发送syn+ack包;
(3)、当Client端收到Server的SYN+ACK应答后,其状态变为ESTABLISHED,并发送ACK包给Server;
如果此时ACK在网络中丢失,那么Server端该TCP连接的状态为SYN_RECV,并且依次等待3秒、6秒、12秒后重新发送SYN+ACK包,以便Client重新发送ACK包,以便Client重新发送ACK包。
Server重发SYN+ACK包的次数,可以通过设置/proc/sys/net/ipv4/tcp_synack_retries修改,默认值为5。
如果重发指定次数后,仍然未收到ACK应答,那么一段时间后,Server自动关闭这个连接。
如果此时client向server发送数据包,server能正常接收数据。并认为连接已正常。参考:https://blog.csdn.net/zerooffdate/article/details/79359726
应用层编写socket代码时,三次握手发生在client的connect,所以为了避免长时间(75秒)无响应连接,应设置为非阻塞socket,同时用select检测设置合适的超时时间。
2、四次挥手
CLIENT SERVER
(1)、client发的FIN包丢了,对于client,因为没收对应的ACK包,应当一直重传(像普通包一样),直至到达上限次数,直接关闭连接;对于server,它应该无任何感知;
(2)、server回client的ACK包丢了,对于client,将执行(1),对于server将像丢普通的ack一样,再次收到FIN后,再发一个ACK包;
(3)、如果client收到ACK后,server直接跑路。client将永远停留在这个状态(半打开状态,就像client关闭了输出一样)。linux有tcp_fin_timeout这个参数,设置一个超时时间 cat /proc/sys/net/ipv4/tcp_fin_timeout 查看,默认60s,可否修改看linux具体版本; windows 注册表有TcpTimedWaitDelay,win10默认值30s;
(4)、server发的FIN包丢了,对于server,像丢普通的包一样,重传。若此时client早已跑路且与其他人建立的连接,client应会不认识这个FIN包,直接回个RST包给server。
如若client没跑路,且没收到server的FIN包,如(3)描述;
(5)、client回复ACK后,按道理来说,可以跑路了,但防止回复的ACK包丢失(丢失后,server因为没收FIN的ACK,所以会再发一个FIN),将等待2MSL(最大报文存活时间)(RFC793定义了MSL为2分钟,Linux设置成了30s)为什么要这有TIME_WAIT?为什么不直接给转成CLOSED状态呢?主要有两个原因:1)TIME_WAIT确保有足够的时间让对端收到了ACK,如果被动关闭的那方没有收到Ack,就会触发被动端重发Fin,一来一去正好2个MSL,2)有足够的时间让这个连接不会跟后面的连接混在一起(你要知道,有些自做主张的路由器会缓存IP数据包,如果连接被重用了,那么这些延迟收到的包就有可能会跟新连接混在一起),这期间如若再收到server的FIN,则再回复ACK;
题外话:
可以看到时三次握手第二步时,server收到client的SYN包并回复SYN+ACK包后,server会把这条连接放入“半连接队列”。这边有个问题,假设这个client是恶意的,client只发SYN包,收到SYN+ACK后不回复,那在server方面,会一直存有这条“半连接”,client数量达到一定程序,serve就炸了。这就是所谓的SYN FLOOD攻击;那怎么防止这种情况呢?有下面几种方法:(来自RFC 4987)
- 过滤
- 增加积压
- 减少SYN-RECEIVED定时
- 复用古老的半开通TCP
- SYN缓存
- SYN Cookie
- 混合方法
- 防火墙和代理