LwIP协议栈源码详解——TCP/IP协议的实现 TCP定时器

这节讨论TCP的定时处理函数。在前面的讨论中,我们看到了与TCP的各种定时器,包括重传定时器、持续定时器和保活定时器,此外TCP中还有几个定时器我们还未涉及。这里总的来看看TCP中的各个定时器。TCP为每条连接总共建立了七个定时器,依次为:

1 )“连接建立(connection establishment)”定时器在发送SYN报文段建立一条新连接时启动。如果在75秒内没有收到响应,连接建立将中止。

2 )“重传(retransmission)”定时器在TCP发送某个数据段时设定。如果该定时器超时而对端的确认还未到达,TCP将重传该数据段。重传定时器的值 (即TCP等待对端确认的时间)是动态计算的,与RTT的估计值密切相关,且还取决于该报文段已被重传的次数。

3 )“延迟ACK(delayed ACK)”定时器在TCP收到必须被确认但无需马上发出确认的数据时设定。如果在200ms内,有数据要在该连接上发送,延迟的ACK响应就可随着数据一起发送回对端,称为捎带确认。如果200ms后,该确认未能被捎带出去,则定时器超时,此时需要发送一个立即确认。

4 )“持续 (persist)”定时器在连接对端通告接收窗口为0,阻止TCP继续发送数据时设定。由于连接对端发送的窗口通告不可靠(只有数据才会被确认,ACK不会被确认),允许TCP继续发送数据的后续窗口更新有可能丢失。因此,如果TCP有数据要发送,但对端通告接收窗口为0,则持续定时器启动,超时后向对端发送 1字节的数据,判定对端接收窗口是否已打开。

5 )“保活(keep alive)”定时器在TCP控制块的so_options 字段设置了SOF_KEEPALIVE选项时生效。如果连接的连续空闲时间超过2小时,则保活定时器超时,此时应向对端发送连接探测报文段,强迫对端响应。如果收到了期待的响应, TCP可确定对端主机工作正常,在该连接再次空闲超过 2小时之前,TCP不会再进行保活测试。如果收到的是RST复位响应, TCP可确定对端主机已重启。如果连续若干次保活测试都未收到响应, TCP就假定对端主机已崩溃,但它无法区分是主机故障还是连接故障。

6) FIN_WAIT_2定时器,当某个连接从FIN_WAIT_1状态变迁到FIN_WAIT_2状态并且不能再接收任何新数据时,FIN_WAIT_2定时器启动,设为10分钟。定时器超时后,重新设为75秒,第二次超时后连接被关闭。加入这个定时器的目的是为了避免如果对端一直不发送 FIN,某个连接会永远滞留在FIN _ WAIT_ 2状态(假设TCP不选用半打开功能)。

7) TIME_WAIT定时器,一般也称为2MSL定时器。2MSL指两倍的MSL,即最大报文段生存时间。当连接转移到TIME_WAIT状态,即连接主动关闭时,定时器启动。状态转换图那一节中已经详细说明了需要2MSL等待状态的原因。连接进入TIME_WAIT状态时,定时器设定为1分钟,超时后,TCP控制块被删除,端口号可重新使用。

前面的7个定时器中,重传定时器使用rtime字段计数,持续定时器使用persist_cnt字段计数,其他五个定时器除延迟ACK定时器外都使用rtime字段计数,从上面的描述中可以看出,这四个定时器是TCP处于四种不同的状态时使用的,因此四个定时器完全独立的使用rtime字段而不会互相影响。延迟ACK定时器使用系统250ms周期性定时来完成的。

LWIP中包括两个定时器函数:一个函数每 250 ms调用一次(快速定时器);另一个函数每500ms调用一次(慢速定时器)。延迟ACK定时器与其他6个定时器有所不同:如果某个连接上设定了延迟ACK定时器,那么下一次250ms定时器超时后,延迟的ACK必须被发送(实际的ACK延迟时间在0~250ms之间)。其他的6个定时器每500 ms增加1,当计数值超过某些阈值时,则相应的动作被触发。

先看简单的快速定时器处理函数:

void tcp_fasttmr(void)

{

struct tcp_pcb *pcb;

for(pcb = tcp_active_pcbs; pcb != NULL; pcb = pcb->next) { //遍历整个active链表

if (pcb->refused_data != NULL) { // 如果某个控制块还没有数据未接收

err_t err;

TCP_EVENT_RECV(pcb, pcb->refused_data, ERR_OK, err); //调用上层函数接收数据

if (err == ERR_OK) {

pcb->refused_data = NULL; // 成功接收则复位指针

}

}

if (pcb->flags & TF_ACK_DELAY) {//若控制块开启了延迟ACK定时器

tcp_ack_now(pcb); // 发送一个立即确认

pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW); //清除标志位

}

}// if

}// for

从上面可以看出,快速定时器处理函数主要做了两方面的工作,一是向上层递交上层一直未接收的数据,二是发送该连接上的立即确认数据段。与快速定时器相比,慢速定时器处理函数就显得相当的庞大了,这里我们只列出和各个定时器相关的部分,而对其他部分采用伪代码的方式加以描述,当然,这里所谓的其他部分就是我们在前面已经讲解过的部分了。

void tcp_slowtmr(void)

{

++tcp_ticks;

pcb = tcp_active_pcbs;

while (pcb != NULL) {

pcb_remove = 0;

该控制块的零窗口探查处理;

该控制块的超时重传处理;

if (pcb->state == FIN_WAIT_2) { // FIN_WAIT_2定时器超时

if ((u32_t)(tcp_ticks - pcb->tmr) >

TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

}

该控制块的超时保活处理;

#if TCP_QUEUE_OOSEQ

if (pcb->ooseq != NULL && // 丢弃在ooseq队列中长时间未被处理的数据

(u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {

tcp_segs_free(pcb->ooseq);

pcb->ooseq = NULL;

}

#endif

if (pcb->state == SYN_RCVD) { // SYN_RCVD状态超时

if ((u32_t)(tcp_ticks - pcb->tmr) >

TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

}

if (pcb->state == LAST_ACK) { // LAST_ACK定时器超时

if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

}

若有超时则删除控制块;

无超时则周期性的外发数据包(poll);

取得active链表上的下一个控制块;

}// while

pcb = tcp_tw_pcbs; //处理TIME-WAIT链表

while (pcb != NULL) {

pcb_remove = 0; // TIME-WAIT定时器超时

if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {

++pcb_remove;

}

若超时则删除控制块;

取得TIME-WAIT链表上的下一个控制块;

}// while

}//函数尾

可以看出各个定时器的实现都是利用全局变量tcp_ticks与tmr字段的差值来实现的。当TCP进入某个状态时,就会将相应tmr字段设置为当前的全局时钟tcp_ticks的值,所以上面的差值可以有效表示出TCP处于某个状态的时间。各个定时器超时后的处理也很相似,即将变量pcb_remove加1,pcb_remove变量是超时处理中最核心的变量了,当针对某个PCB控制块作完超时判断之后,函数通过判断pcb_remove的值来处理TCP控制块,当pcb_remove值大于1时,则表示该控制块上有超时事件发生,该控制块或被删除或被挂起。注意伪代码中的重传定时器超时并不会影响pcb_remove的值。如果细心,你还可以看到,上面的代码多了两个超时事件,即SYN_RCVD状态超时和ooseq队列数据超时,当然,这两个超时事件并不影响协议栈功能的实现。

最后来看看系统为每个超时时间设置的超时时间,从上面的代码中可以看出,它们是在各个宏定义里面实现的。

#define TCP_TMR_INTERVAL 250  //250ms

#define TCP_FAST_INTERVAL TCP_TMR_INTERVAL  // 快速定时器

#define TCP_SLOW_INTERVAL (2*TCP_TMR_INTERVAL) // 慢速定时器

#define TCP_FIN_WAIT_TIMEOUT  20000  // FIN_WAIT_2状态超时时间

#define TCP_SYN_RCVD_TIMEOUT  20000  // SYN_RCVD状态超时时间

#define TCP_OOSEQ_TIMEOUT 6U  //ooseq队列中数据等待的rto周期数

#define TCP_MSL  60000U   // MSL

到这里,我们的PCB控制块中的各个字段基本都已涉及到了,除了polltmr和pollinterval。这两个字段用于周期性调用函数tcp_output,用以发送控制块上残留的未发送数据段。

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值