一、三次握手
1.wireshark 抓包
2.TCP报文手部
注意标志位:
1).同步 SYN = 1 表示这是一个连接请求或连接接受报文。
2).只有当 ACK = 1 时确认号字段才有效。当 ACK = 0 时,确认号无效。
3).FIN = 1 表明此报文段的发送端的数据已发送完毕,并要求释放运输连接。
3.连接示意
二、四次挥手
1.知所以然
为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)
放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部
发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意
现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
更明了的回答:由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一
个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行
关闭的一方将执行主动关闭,而另一方执行被动关闭。
2.图示
第一阶段
- 首先client会发送一个FIN包给server(同时还有ack和seq包),这是要告诉server,我已经没有数据要发给你了,此时client处于FIN_WAIT_1状态。接收到FIN包的server处于CLOSE_WAIT的状态。
- server发回一个ACK(值为client传过来的seq+1)和seq(值为client传过来的ack的值)给client。client收到server发过来的包后确认关闭连接,此时client处于FIN_WAIT_2。
第二阶段
- server在接收到client的FIN后,得知client要断开tcp连接了,于是在发送完ack和seq给client后,自己发送一个FIN包给client(也带有ack和seq包),告诉client我也要断开连接了,此时server处于LAST_ACK状态。
- client接收到server的FIN信息后,会回复server一个ack包,并且会进入TIME_WAIT状态,持续2个MSL(Max Segment Lifetime)。而server接收到client ack后便关闭连接。client在指定时间过后仍然没有接收到server的数据,确认server已经没有数据过来,也关闭了连接。
(1)、CLOSE_WAIT的解释。
在以上事例,我们知道server在接收到FIN后,发送ACK之前会进入CLOSE_WAIT,如果长期处于这个状态,或者说服务器出现大量CLOSE_WAIT,说明ACK包一直没有发出,这时候就应该检查代码了。
(2)、TIME_WAIT注意事项
从事例我们知道,主动关闭连接的一方会经历TIME_WAIT状态,在该状态下的socket是不会被回收的。而如果是服务器端主动关闭连接,则可能会面临处于大量TIME_WAIT的情况(因为连接很多嘛),会严重影响服务器的处理能力。
怎么解决呢,那就减少服务器端TIME_WAIT的时间咯。
(3)2MSL(Maximum Segment Lifetime)存在的理由
TCP的TIME_WAIT状态也称为2MSL等待状态
(4)2MSL状态为什么设计在主动关闭这一方
(1)发最后ack的是主动关闭一方
(2)只要有一方保持TIME_WAIT状态,就能起到避免incarnation connection在2MSL内的重新建立,不需要两方都有
三、TCP连接出现大量TIME_WAIT的解决办法
主动关闭TCP/IP连接,会通过TIME_WAIT的状态保留一段时间,时间过了才会释放这个端口,当端口接受的频繁请求数量过多的时候,
就会产生大量的TIME_WAIT状态的连接,这些连接占着端口,会消耗大量的资源。
这个TIME_WAIT的作用是什么?
原因有二:
一、保证TCP协议的全双工连接能够可靠关闭
如果Client直接CLOSED了,那么由于IP协议的不可靠性或者是其它网络原因,导致Server没有收到Client最后回复的ACK。那么Server就会在超时之后继续发送FIN。 此时由于Client已经CLOSED了,就找不到与重发的FIN对应的连接,最后Server就会收到RST而不是ACK,Server就会以为是连接错误把问题报告给高层。 这样的情况虽然不会造成数据丢失,但是却导致TCP协议不符合可靠连接的要求。 所以,Client不是直接进入CLOSED,而是要保持TIME_WAIT,当再次收到FIN的时候,能够保证对方收到ACK,最后正确的关闭连接。
二、保证这次连接的重复数据段从网络中消失
如果Client直接CLOSED,然后又再向Server发起一个新连接,我们不能保证这个新连接与刚关闭的连接的端口号是不同的。也就是说有可能新连接和老连接的端口号是相同的。
一般来说不会发生什么问题,但是还是有特殊情况出现:假设新连接和已经关闭的老连接端口号是一样的,如果前一次连接的某些数据仍然滞留在网络中,这些延迟数据在建立新连接之后才到达Server,由于新连接和老连接的端口号是一样的,又因为TCP协议判断不同连接的依据是socket pair。
于是,TCP协议就认为那个延迟的数据是属于新连接的,这样就和真正的新连接的数据包发生混淆了。
vi /etc/sysctl.conf
#当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击
net.ipv4.tcp_syncookies = 1 #允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0
net.ipv4.tcp_tw_reuse = 1 #开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭
net.ipv4.tcp_tw_recycle = 1 # 默认60
net.ipv4.tcp_fin_timeout = 30 /sbin/sysctl -p
此外,如果你的连接数本身就很多,我们可以再优化一下TCP/IP的可使用端口范围,进一步提升服务器的并发能力。
net.ipv4.tcp_keepalive_time = 1200
表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为20分钟。
net.ipv4.ip_local_port_range = 10000 65000
表示用于向外连接的端口范围。缺省情况下很小:32768到61000,改为10000到65000。设的太低,否则可能会占用掉正常的端口
net.ipv4.tcp_max_syn_backlog = 8192
表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。
net.ipv4.tcp_max_tw_buckets = 5000
表示系统同时保持TIME_WAIT的最大数量,如果超过这个数字,TIME_WAIT将立刻被清除并打印警告信息。默认为180000,改为5000。对于Apache、Nginx等服务器,上几行的参数可以很好地减少TIME_WAIT套接字数量。
参考:TCP连接的建立和释放