四次分手
主动关闭连接一端(active close),<br>才有TIME_WAIT状态<br>
半关闭状态/half-close
为啥挥手需要四次?
FIN报文,只表示发送端不再发送数据了,但还能接收数据
服务器收到客户端的FIN报文时,先回一个ACK应答报文,<br>而服务器端还可能有数据需要处理和发送,等服务端不再<br>发送数据是,才发送FIN报文给客户端,表示现在同意关闭连接。<br>
因为客户端和服务器端的ACK和FIN需要分开发送,所以需要四次
为什么TIME_WAIT等待时间是2MSL?
MSL:Maxmum Segment Lifetime报文最大生存时间。<br>任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。<br>
IP头有一个TTL字段,表示IP数据包可以经过的最大路由数,每经过<br>一个路由器此值减一,当TTL=0时,此数据包被丢弃。<br><br>MSL是时间,TTL是经过路由跳数。所以理论上MSL应该大于等于TTL.
极端情况下,网络中可能存在来自发送方的数据包,当这些发送方的<br>数据包被接收方处理后又被对方发送响应,所以一来一回要等待2倍的时间。<br>
如果TIME_WAIT时间内,因为客户端的ACK还没有传输到服务端,<br>客户端又接收到了服务端重发的FIN报文,那么2MSL时间将重新计时。<br>
Linux系统TIME_WAIT的时间为固定的60秒<br>#define TCP_TIMEWAIT_LEN (60*HZ)
为什么需要TIME_WAIT状态?
防止旧连接的数据包
足以让两个方向的数据包都被丢弃,<br>使得原来连接的数据包在网络中都<br>自认消失,再出现的数据包一定都<br>是新建立连接产生的。<br>
保证连接正确关闭
等待足够的时间以确保最后的ACK能让被动关闭方接收,从而帮助其正常关闭
如果服务器端没有收到四次握手中的最后一个ACK报文,则会重发 FIN 关闭连接报文并等待新的 ACK 报文
客户端在TIME_WAIT状态等待2MSL后,就可以保证双方的连接都可以正常的关闭
TIME_WAIT过多的危害
内存资源占用
对端口资源的占用,一个TCP连接至少消耗一个本地端口<br>可以通过以下参数设定:net.ipv4.ip_local_port_range
如果占满了所有端口,则会导致无法创建新连接。
TIME_WAIT优化
方法一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps
可以复用处于TIME_WAIT 的 socket 为新的连接所用
如果引入时间戳,则2MSL问题就不复存在了。<br>因为重复的数据包会因为时间戳过期被自然丢弃。
方法二:net.ipv4.tcp_max_tw_buckets<br>默认值为18000
当系统中处于TIME_WAIT的连接一旦超过这个值时,<br>系统就会将所有的TIME_WAIT 连接状态重置。<br>
不推荐
方法三:程序中使用 SO_LINGER
struct linger so_linger;<br>so_linger.l_onoff = 1;<br>so_linger.l_linger = 0;<br>setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));<br><br>如果l_onoff为非 0, 且l_linger值为 0,那么调用close后,会立该发送<br>一个RST标志给对端,该 TCP 连接将跳过四次挥手,也就跳过了<br>TIME_WAIT状态,直接关闭。<br>
不推荐
如果已经建立了连接,<br>但是客户端突然发生故障,怎么办?<br>
原理:如果没有任何连接相关的活动,TCP keepalive每隔一个时间间隔,<br>发送给一个探测报文,如果联系几个探测报文都没有得到响应,则认为当<br>前的TCP连接已经死亡,系统内核将错误信息通知给上层应用程序。<br>
内核参数,默认值:<br>net.ipv4.tcp_keepalive_time=7200<br>net.ipv4.tcp_keepalive_intvl=75<br>net.ipv4.tcp_keepalive_probes=9
<b>tcp_keepalive + (tcp_keepalive_intvl * tcp_keepalive_probes)</b><br>Linux系统经过2小时11分15秒<br>
需要考虑的情况
正常工作,TCP keepalive被重置
对端程序崩溃并重启,对端可以响应,但由于没有改连接的有效信息,<br>会产生RST报文,这样TCP连接被重置<br>
对端崩溃,或不可到达。达到keepalive的次数后,TCP会报告该TCP连接已经死亡
三次握手
为啥需要三次握手?
目的:初始化<b>Socket, 序列号</b>和<b>窗口大小</b>并建立连接
三次握手才可以<br>阻止重复历史连接的初始化<br>造成混乱。
历史连接情况图例
如果是两次握手,不能判断当前连接是否是历史连接。<br>三次握手的时候,客户端在准备发出第三次报文时,有足够的上下文信息来判断当前连接是否是历史连接:<br>1. 如果是历史连接,则发送RST报文,中止历史连接;<br>2. 如果不是历史连接,则发生ACK报文,通信双方成功建立连接。<br>
三次握手才可以同步双方的初始序列号
序列号是可靠传输的关键因素。<br>握手时,一来一回,才能确保双方的初始序列号能被可靠的同步。
三次握手才可以避免资源浪费
如果只是两次握手,当客户端发送SYN请求在网络中阻塞,<br>客户端没有收到ACK,就会重发SYN,由于没有第三次握<br>手,服务器不清楚客户端是否收到了自己发送的建立连接<br>的ACK确认报文,所以每个SYN就只能主动建立一个连接,<br><b>建立多个冗余的无效链接,造成资源浪费</b>。<br>