TCP常见的定时器三次握手与四次挥手
Posted 滴巴戈
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP常见的定时器三次握手与四次挥手相关的知识,希望对你有一定的参考价值。
1.TCP常见的定时器
在TCP协议中有的时候需要定期或者按照某个算法对某个事件进行触发,那么这个时候,TCP协议是使用定时器进行实现的。在TCP中,会有七种定时器:
重传定时器(retransmission timer)
坚持定时器(persist timer)
保活定时器(keepalive timer)
TIME_WAIT定时器 (TIME_WAIT timer, 也叫2MSL timer)
FIN_WAIT_2定时器(FIN_WAIT_2 timer)
延迟应答定时器(delayed ACK timer)
建立连接定时器(connection-establishment timer)
等待时间RTT:发送一个数据包到收到对应的ACK,所花费的时间。即一个给定连接的往返时间。由于网络流量变化,这个时间会相应地发生改变,TCP需要跟踪这些变化。RTT由三部分组成:链路的传播时间,末端系统的处理时间,路由器缓存中的排队和处理时间。
其中,前两个部分的值对于一个TCP连接相对固定,路由器缓存中的排队和处理时间会随着整个网络拥塞程度的变化而变化。所以RTT的变化在一定程度上反应了网络的拥塞程度。
RTT的值应该动态计算。公式是:RTT=previous RTT*alpha + (1-alpha)*current RTT。这是一个平滑的RTT又称置为SRTT, alpha是一个平滑因子,取值为0.8或者0.9,也可以说新的RTT是以前的RTT值的90%加上当前RTT值的10%.
Karn算法:对重传报文,在计算新的RTT时,不考虑重传报文的RTT。因为无法推理出:发送端所收到的确认是对上一次报文段的确认还是对重传报文段的确认。干脆不计入。
(1)重传计时器:
重传定时器:为了控制丢失或丢弃的报文段,即控制报文段确认的等待时间。当TCP发送报文段时,就创建这个特定报文段的重传计时器,可能发生两种情况:若在计时器超时之前收到对报文段的确认ACK,则撤销计时器;若在收到对特定报文段的确认之前计时器超时,则重新发送队列中需要重传的报文段,并把计时器复位;规则:
- 当TCP发送了位于发送队列最前端的报文段后就启动这个RTO计时器;
- 如果队列为空则停止计时器,否则重启计时器;
- 当计时器超时后,TCP会重传发送队列最前端的报文段;
- 当一个或者多个报文段被累计确认后,这个或者这些报文段会被清除出队列
RTO: 发送数据包,启动重传定时器,到重传定时器被重置所花费的时间,称为RTO。RTT会由于网络流量的变化,而相应地发生改变,所以TCP需要跟踪这些变化并动态调整超时时间RTO。可简记为重传时间RTO=2*RTT;
基于RTT,计算出对应的RTO
RTO = min[UBOUND,max[LBOUND,(BETA*SRTT)]
其中UBOUND是最大值,一般情况下为120s,LBOUND是最小重传值,一般情况下为1s,Beta取值为1.3~2.0. [RFC793]
Jacobaon/Karels 算法
第一次RTO计算方法, 假设RTT = R
1. SRTT = R
2. RTTVAR = R/2
3. RTO = SRTT + max(G, K*RTTVAR) , K = 4
后续的RTO计算,假设当前的RTT为R\'
RTTVAR = (1 - beta)*RTTVAR + beta*|SRTT - R\'| *计算平滑RTT和真实RTT的差距,切记这个地方的SRTT是上一次的SRTT*
SRTT = (1 - alpha)*SRTT + alpha*R\' * 计算平滑RTT*
RTO = SRTT + max(G, K*RTTVAR)
alpha = 1/8 beta = 1/4, 值得指出的是这个算法在目前的Linux协议栈中应用,多么伟大的一件事情。
(2)坚持定时器
坚持定时器是在一方滑动窗口为0之后,另外一方停止传输数据,进入坚持定时器的轮询,直到滑动窗口不再为0。主要是解决0窗口可能导致的问题。
场景:刚开始接收端向发送端发送一个零窗口报文段。不久后,如果接收端的缓存区有空间可以接收数据,则会向发送端发送非零窗口大小的报文段(即窗口更新),但报文段在传输过程中丢失,结果是发送端没有收到该非零窗口的报文段而一直处于等待非零窗口的报文端通知中,而由于接收端已经发送此报文段且并不知道该报文段丢失,则接收端会一直处于等待接收数据状态,如果没有任何措施的话,这个死锁的局面会一直延续下去。
解决:TCP 为每一个连接设有一个坚持定时器(也叫持续计数器)。当发送端收到零窗口的确认时就启动坚持计时器,当坚持计时器到期时就发送一个探测报文段,这个报文段只有一个字节的数据,且有序号,但序号永远不需要确认,在计算对其他部分数据的确认时这个序号也被忽略。探测报文段提醒接收端,确认已丢失,须重传。计时器的截止期即为重传时间的值,若无响应,则再次发送探测报文段,并将坚持计时器的值加倍和并复位,如此直到这个值增大到阈值为止(通常为 60 秒)。在此之后,发送端每隔 60s 就发送一个报文段,直到窗口重新打开为止。
说说术语,首先是滑动窗口,可以简单理解为缓冲区剩余空间大小。不管是写缓冲还是读缓冲,一旦一方通告了自己的滑动窗口大小,另外一方就会根据滑动窗口大小传递窗口大小的数据了。但是,当被通告,一方的滑动窗口大小为0的时候,另外一方就会启动坚持定时器,基本也是使用TCP指数退避方法,第一次1.5秒,第二次1.5x2秒,第三次1.5x4...
其次是糊涂窗口综合症。这个症状是滑动窗口引起的。病因是发送方和接收方在一个很小的滑动窗口的时候就开始数据传输,传输结束之后,读写的消费速度也并没有那么快,导致下次传输的时候,滑动窗口还是那么小。然后现象就是每次传输的数据都非常小。就好比每次开出去的火车载货量只有一节车厢,其实我们是希望能攒够n节车厢才开始传输。
糊涂窗口综合症有解决办法,还不止一种,在接收方或者发送方都可以解决。大致就是如果接收方解决,那么接收方在接收窗口小于一定大小的时候,对所有的接收请求都返回窗口为0的包,来触发另外一方的坚持定时器。同样发送方也是,在可以发送的数据大于一定窗口的时候才发送。
(3)保活定时器
这个就是我们经常说的tcp的keepalive了。实际使用场景是在应用层没有数据进行传输的时候,一定时间(tcp_keepalive_time,默认每2个小时)发送一次保持心跳的包,如果发送成功,则继续保持端口活跃,如果没有正常返回,则在指定次数内(tcp_keepalive_probes,默认是9次),指定间隔(tcp_keepalive_intvl,默认是75s)发送心跳包。如果最后都没有获得正常的ACK,那么才算连接失败。
当然,tcp是否需要提供keepalive机制,是有争议的,我们可以为每个tcp连接设置是否启用keepalive和启用keepalive的各个指标设置。
(4)TIME_WAIT定时器
TIME_WAIT是主动关闭连接的一端最后进入的状态, 而不是直接变成CLOSED的状态, 为什么呢?第一个原因是万一被动关闭的一端在超时时间内没有收到最后一个ACK, 则会重发最后的FIN,2MSL(报文段最大生存时间)等待时间保证了重发的FIN会被主动关闭的一段收到且重新发送最后一个ACK;另外一个原因是在2MSL等待时间时,任何迟到的报文段会被接收并丢弃,防止老的TCP连接的包在新的TCP连接里面出现。不可避免的,在这个2MSL等待时间内,不会建立同样(源IP, 源端口,目的IP,目的端口)的连接。
(5)FIN_WAIT_2定时器
主动关闭的一端调用完close以后(即发FIN给被动关闭的一端, 并且收到其对FIN的确认ACK)则进入FIN_WAIT_2状态。如果这个时候因为网络突然断掉、被动关闭的一段宕机等原因,导致主动关闭的一端不能收到被动关闭的一端发来的FIN,主动关闭的一段总不能一直傻等着,占着资源不撒手吧?这个时候就需要FIN_WAIT_2定时器出马了, 如果在该定时器超时的时候,还是没收到被动关闭一端发来的FIN,那么不好意思, 不等了, 直接释放这个链接。FIN_WAIT_2定时器的时间可以从/proc/sys/net/ipv4/tcp_fin_timeout中查看和设置。
(6)建立连接定时器
顾名思义,这个定时器是在建立连接的时候使用的, 我们知道, TCP建立连接需要3次握手, 如下图所示:
建立连接的过程中,在发送SYN时, 会启动一个定时器(默认应该是3秒),如果SYN包丢失了, 那么3秒以后会重新发送SYN包的(当然还会启动一个新的定时器(设置成6秒超时),当然也不会一直没完没了的发SYN包, 在/proc/sys/net/ipv4/tcp_syn_retries 可以设置到底要重新发送几次SYN包。
(7)延迟应答定时器
延迟应答也被称为捎带ACK,这个定时器是在延迟应答的时候使用的。为什么要延迟应答呢?延迟应答是为了提高网络传输的效率。
举例说明,比如服务端收到客户端的数据后,不是立刻回ACK给客户端,而是等一段时间(一般最大200ms),这样如果服务端要是有数据需要发给客户端,那么这个ACK就和服务端的数据一起发给客户端了,这样比立即回给客户端一个ACK节省了一个数据包。
参考资料
《TCP/IP协议详解》
《高效TCP/IP编程》
2.三次握手与四次挥手
(1)什么是三次握手
“三次握手”即对每次发送的数据量进行跟踪与协商使数据段的发送和接收同步,根据所接收到的数据量而确认数据发送、接收完毕后何时撤消联系,并建立虚连接。
下面详细解释,如图为“三次握手”的过程:
建⽴连接的过程:
1)第一次握手:客户端发出段1,SYN位表⽰连接请求。序号是1000,这个序号在⽹络通讯中⽤作临时的地址,每发⼀个数据字节,这个序号要加1,这样在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况,另外,规定SYN位和FIN位也要占⼀个序号,这次虽然没发数据,但是由于发了SYN位,因此下次再发送应该⽤序号1001。 mss表⽰最⼤段尺⼨, 如果⼀个段太⼤,封装成帧后超过了链路层的最⼤帧长度,就必须在IP 层分⽚,为了避免这种情况,客户端声明⾃⼰的最⼤段尺⼨,建议服务器端发来的段不要超过这个长度。
客户端发送完数据包之后进入SYN_SENT状态,等待服务器确认。
2)第二次握手:服务器发出段2,也带有SYN位,同时置ACK位表⽰确认,确认序号是1001,表⽰“我接收到序号1000及其以前所有的段,请你下次发送序号为1001的段”,也就是应答了客户端的连接请求,同时也给客户端发出⼀个连接请求,同时声明最⼤尺⼨为1024。
此时服务器进入SYN_RECV状态
3)第三次握手:客户端发出段3,对服务器的连接请求进⾏应答,确认序号是8001。
此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
在这个例⼦中,⾸先客户端主动发起连接、发送请求,然后服务器端响应请求,随后建立连接成功。两条竖线表⽰通讯的两端,从上到下表⽰时间的先后顺序,注意,数据从⼀端传到 ⽹络的另⼀端也需要时间,所以图中的箭头都是斜的。双⽅发送的段按时间顺序编号为1-10, 各段中的主要信息在箭头上标出,例如段2的箭头上标着SYN, 8000(0), ACK 1001, <mss 1024>, 表⽰该段中 的SYN位置1,32位序号是8000,该段不携带有效载荷(数据字节数为0),ACK位置1,32位确认 序号是1001,带有⼀个mss选项值为1024。
在这个过程中,客户端和服务器分别给对⽅发了连接请求,也应答了对⽅的连接请求,其中服务器的请求和应答在⼀个段中发出,因此⼀共有三个段⽤于建⽴连接,称为\'\'\'三⽅握⼿。在建⽴连接的同时,双⽅协商了⼀些信息,例如双⽅发送序号的初始值、最⼤段 尺⼨等。
TCP三次握手与四次挥手