TCP协议详解-定时器
紧接上篇
3. TCP定时器
3.1 连接建立(connection establishment)定时器
这个定时器由TCP_KEEP计数器实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | /* * Keep-alive timer went off; send something * or drop connection if idle for too long. */ case TCPT_KEEP: tcpstat.tcps_keeptimeo++; if (tp->t_state < TCPS_ESTABLISHED) goto dropit; ...... dropit: tcpstat.tcps_keepdrops++; tp = tcp_drop(tp, ETIMEDOUT); break; ...... |
如代码所示,如果tcp的state<ESTABLISHED,表明其处于连接建立状态。定时器超时后,调用dropit终止连接。大多数伯克利系统将建立一个连接的最长时间设置为75s。连接建立定时器配合重传定时器一起使用,重传定时器会隔一段时间重传SYN,如下图所示:
图中可以看出,对于一个新连接,重传定时器初始化为6s,后续值为24s和48s。重传定时器在0s,6s和30s处传送SYN报文。在75s处,连接定时器超时,调用tcp_drop()终止连接。
3.2 保活(keepalive)定时器
TCP_KEEP同时也实现了保活定时器。代码如下(4.4BSD-Lite2):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | case TCPT_KEEP: tcpstat.tcps_keeptimeo++; if (tp->t_state < TCPS_ESTABLISHED) goto dropit; if (tp->t_inpcb->inp_socket->so_options & SO_KEEPALIVE && tp->t_state <= TCPS_CLOSE_WAIT) { if (tp->t_idle >= tcp_keepidle + tcp_maxidle) goto dropit; /* * Send a packet designed to force a response * if the peer is up and reachable: * either an ACK if the connection is still alive, * or an RST if the peer has closed the connection * due to timeout or reboot. * Using sequence number tp->snd_una-1 * causes the transmitted zero-length segment * to lie outside the receive window; * by the protocol spec, this requires the * correspondent TCP to respond. */ tcpstat.tcps_keepprobe++; |
保活定时器的作用和应用层的心跳类似。检测对端tcp连接是否存在。其发送的序号为tcp->snd_una-1,这样就发送了一个0字节的报文段,从而来检测对端tcp连接的情况。所有的保活定时器在连接2小时候空闲后超时。然后以75s为间隔连续发送9个探测报文段。如果皆无响应,则丢弃此连接。所以一共是2小时+75s * 9后(大约为2个小时11分钟),关闭连接。如下图所示:
3.3 重传(retransmission)定时器
重传定时器的取值依赖于连接上测算得到的RTT(RTT测算方法不在本次讨论范围内)。
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | case TCPT_REXMT: if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) { tp->t_rxtshift = TCP_MAXRXTSHIFT; tcpstat.tcps_timeoutdrop++; tp = tcp_drop(tp, tp->t_softerror ? tp->t_softerror : ETIMEDOUT); break; } tcpstat.tcps_rexmttimeo++; rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift]; TCPT_RANGESET(tp->t_rxtcur, rexmt, tp->t_rttmin, TCPTV_REXMTMAX); tp->t_timer[TCPT_REXMT] = tp->t_rxtcur; /* * If losing, let the lower level know and try for * a better route. Also, if we backed off this far, * our srtt estimate is probably bogus. Clobber it * so we'll take the next rtt measurement as our srtt; * move the current srtt into rttvar to keep the current * retransmit times until then. */ if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) { in_losing(tp->t_inpcb); tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT); tp->t_srtt = 0; } tp->snd_nxt = tp->snd_una; ...... (void) tcp_output(tp); break; |
重传移位计数器(t_rxtshift)在每次重传时递增,如果大于TCP_MAXRXTSHIFT(12),连接将被丢弃。
代码中的TCP_REXMTVAL宏是为了实现指数退避,即重传超时时间逐渐变为1.5、3、6、12、24、48、64、64、64、64、64、64、64(s)。
如果报文段重传次数>3次(TCP_MAXRXTSHIFT(12)/4),则会释放缓存中的路由以便重新寻找新的路由。
最后强迫重传最早的未确认的数据:
1 2 | tp->snd_nxt = tp->snd_una;//下一个序号snd_nxt被置为最早的未确认过的报文 (void) tcp_output(tp);//强制发送 |
重传过程如下图所示:
3.4 持续(persist)定时器
持续定时器超时后,由于对端已通告接收窗口为0,则强制发送1字节的数据以探测对端窗口。 代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | /* * Persistance timer into zero window. * Force a byte to be output, if possible. */ case TCPT_PERSIST: tcpstat.tcps_persisttimeo++; /* * Hack: if the peer is dead/unreachable, we do not * time out if the window is closed. After a full * backoff, drop the connection if the idle time * (no responses to probes) reaches the maximum * backoff that we would use if retransmitting. */ if (tp->t_rxtshift == TCP_MAXRXTSHIFT && (tp->t_idle >= tcp_maxpersistidle || tp->t_idle >= TCP_REXMTVAL(tp) * tcp_totbackoff)) { tcpstat.tcps_persistdrop++; tp = tcp_drop(tp, ETIMEDOUT); break; } tcp_setpersist(tp); tp->t_force = 1; (void) tcp_output(tp); tp->t_force = 0; break; |
其也在重传移位计数器>TCP_MAXRXTSHIFT后drop连接。
下图为持续定时器取值时间表(假设连接的重传时限为1.5s):
图中可以看出坚持定时器也采用了指数退避策略。
3.5 FIN_WAIT_2定时器
TCP的TCP2_2MSL定时计数器实现了两种定时器:FIN_WAIT_2定时器和2MSL定时器。
FIN_WAIT_2定时器。当tcp_input从FIN_WAIT_1状态变迁为FIN_WAIT_2状态(调用了close,而不是shutdown),FIN_WAIT_2定时器设定为10分钟(tcp_maxidle)。这样可以防止连接永远停留在FIN_WAIT_2状态。
代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 | /* * 2 MSL timeout in shutdown went off. If we're closed but * still waiting for peer to close and connection has been idle * too long, or if 2MSL time is up from TIME_WAIT, delete connection * control block. Otherwise, check again in a bit. */ case TCPT_2MSL: if (tp->t_state != TCPS_TIME_WAIT && tp->t_idle <= tcp_maxidle) tp->t_timer[TCPT_2MSL] = tcp_keepintvl; else tp = tcp_close(tp); break; |
3.6 2MSL定时器
TCP的TCP2_2MSL定时计数器同时也实现了2MSL定时器,代码如上一节所示。MSL是指的报文段最大生存时间,设定一个2MSL的等待状态是为了不让网络中幸存的包(可能缓存在路由器中、经过多个路由器导致时间滞后的)重新发送给已经关闭的连接。
4. 总结
TCP作为一个通用的网络协议,其作者们为了可靠性做出了巨大的努力,从而导致TCP内部存在各种机制,异常复杂,本文通过对TCP窗口和定时器所采用的各种算法进行了介绍,从而让大家能够进一步理解TCP协议。