lwip源码解析之 TCP协议 定时器 tcp_slowtmr();和tcp_fasttmr();

TCP协议中许多地方是需要使用到定时功能的,如定时重传功能,保活keepalive功能,坚持定时器功能,这些定时功能会在lwip中的两个定时器函数中实现。

一,定时器时钟

二,快速定时任务

void tcp_fasttmr(void)比较简单,它的功能主要是每250ms处理延时发送的ack报文和fin报文,同时通知上层应用处理数据。

void
tcp_fasttmr(void)
{
    
    
  struct tcp_pcb *pcb;

  ++tcp_timer_ctr;

tcp_fasttmr_start:
  pcb = tcp_active_pcbs;  //在active中遍历

  while (pcb != NULL) {
    
    
    if (pcb->last_timer != tcp_timer_ctr) {
    
    
      struct tcp_pcb *next;
      pcb->last_timer = tcp_timer_ctr;
      //发送延时的ack
      if (pcb->flags & TF_ACK_DELAY) {
    
    
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: delayed ACK\n"));
        tcp_ack_now(pcb);
        tcp_output(pcb);
        pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);
      }
      //发送延时的fin
      if (pcb->flags & TF_CLOSEPEND) {
    
    
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_fasttmr: pending FIN\n"));
        pcb->flags &= ~(TF_CLOSEPEND);
        tcp_close_shutdown_fin(pcb);
      }

      next = pcb->next;

      //若当前tcp有未被上层应用接收的数据
      if (pcb->refused_data != NULL) {
    
    
        tcp_active_pcbs_changed = 0;
        tcp_process_refused_data(pcb);  //通过回调函数使上层处理数据
        if (tcp_active_pcbs_changed) {
    
    
          goto tcp_fasttmr_start;
        }
      }
      pcb = next; //下一个
    } else {
    
    
      pcb = pcb->next;
    }
  }
}

三,低速定时任务

void tcp_slowtmr(void)每500ms调用,该函数完成了超时重传,tcp保活功能,并会遍历activetimewait链表的PCB,删除那些超时或者出错的PCB,同时将PCB中unsent队列中的数据发送出去。一般使用tcp_write();写入数据后,数据不会马上发送,而是在定时任务中发送。

1,超时重传

重点的代码注释如下:

    //请求连接次数超出限制
    if (pcb->state == SYN_SENT && pcb->nrtx >= TCP_SYNMAXRTX) {
    
    
      ++pcb_remove; //移除增加
      LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));
    }
    //数据重发次数超出限制
    else if (pcb->nrtx >= TCP_MAXRTX) {
    
    
      ++pcb_remove;
      LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));
    } else {
    
    

      //如果坚持定时器已经开启
      if (pcb->persist_backoff > 0) {
    
    

        //获取坚持定时器触发值
        u8_t backoff_cnt = tcp_persist_backoff[pcb->persist_backoff-1];
        //坚持定时器不超过触发值,则加1
        if (pcb->persist_cnt < backoff_cnt) {
    
    
          pcb->persist_cnt++;
        }
        //坚持定时器触发,发送窗口探查
        if (pcb->persist_cnt >= backoff_cnt) {
    
    
          if (tcp_zero_window_probe(pcb) == ERR_OK) {
    
    
            pcb->persist_cnt = 0; //发送成功,清除计数值
            if (pcb->persist_backoff < sizeof(tcp_persist_backoff)) {
    
    
              pcb->persist_backoff++; //发送探查次数+1
            }
          }
        }
      } else {
    
      //无开启坚持定时器
        //如果开启了超时重传定时器,则加1
        if (pcb->rtime >= 0) {
    
    
          ++pcb->rtime;
        }
        //有未确认报文且重传时间到,要重传了
        if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {
    
    
          LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %"S16_F
                                      " pcb->rto %"S16_F"\n",
                                      pcb->rtime, pcb->rto));

          ESP_STATS_TCP_PCB(pcb);

          if (pcb->state != SYN_SENT) {
    
    
            u8_t backoff_idx = LWIP_MIN(pcb->nrtx, sizeof(tcp_backoff)-1);  //获得重传次数,但重传次数不会超过7
            //动态设置rto,每次超时后,rto时间会增加
            pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[backoff_idx];  
          }

          pcb->rtime = 0; //重置超时重传定时器

          //TODO 出现重传,说明报文丢失了,可能是网络出现阻塞,减小拥塞窗口
          eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
          pcb->ssthresh = eff_wnd >> 1; //ssthresh减少到拥塞窗口的一半

          //若ssthresh比最大报文长度的两倍还小,后者的数值(限制了ssthresh的最小值)
          if (pcb->ssthresh < (tcpwnd_size_t)(pcb->mss << 1)) {
    
    
            pcb->ssthresh = (pcb->mss << 1);
          }
          pcb->cwnd = pcb->mss; //拥塞窗口设置成最大报文长度,一个报文长度
          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"TCPWNDSIZE_F
                                       " ssthresh %"TCPWNDSIZE_F"\n",
                                       pcb->cwnd, pcb->ssthresh));

          //重传报文
          tcp_rexmit_rto(pcb);
        }
      }
    }

    if (pcb->state == FIN_WAIT_2) {
    
    
  
      //TODO 处于FIN_WAIT_2的时间太长(由于对方长时间无反应)则删除
      if (pcb->flags & TF_RXCLOSED) {
    
    
        /* PCB was fully closed (either through close() or SHUT_RDWR):
           normal FIN-WAIT timeout handling. */
        if ((u32_t)(tcp_ticks - pcb->tmr) >
            TCP_FIN_WAIT_TIMEOUT / TCP_SLOW_INTERVAL) {
    
    
          ++pcb_remove;
          LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in FIN-WAIT-2\n"));
        }
      }
    }

2,保活keepalive

服务端需要检查客户端是否还能通信,若两小时内无通信,客户端发送探查报文,若客户端ack,则更新保活计时器,否则,每隔75s发送一个探查报文,若发送超过9个报文,则认为客户端已挂掉

    /* Check if KEEPALIVE should be sent */
    //服务端需要检查客户端是否还能通信,若两小时内无通信,客户端发送探查报文,若客户端ack,则更新保活计时器
    //否则,每隔75s发送一个探查报文,若发送超过9个报文,则认为客户端已挂掉
    if (ip_get_option(pcb, SOF_KEEPALIVE) &&
       ((pcb->state == ESTABLISHED) ||
        (pcb->state == CLOSE_WAIT))) {
    
    
      
      //发送9个以上探查报文,对方仍无反应,应该关闭tcp
      if ((u32_t)(tcp_ticks - pcb->tmr) >
         (pcb->keep_idle + TCP_KEEP_DUR(pcb)) / TCP_SLOW_INTERVAL)
      {
    
    
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: KEEPALIVE timeout. Aborting connection to "));
        ip_addr_debug_print(TCP_DEBUG, &pcb->remote_ip);
        LWIP_DEBUGF(TCP_DEBUG, ("\n"));

        ++pcb_remove;
        ++pcb_reset;  //复位对方
      } else if ((u32_t)(tcp_ticks - pcb->tmr) >
                (pcb->keep_idle + pcb->keep_cnt_sent * TCP_KEEP_INTVL(pcb))
                / TCP_SLOW_INTERVAL)  //发送探查报文
      {
    
    

        err = tcp_keepalive(pcb); 
        if (err == ERR_OK) {
    
    
          pcb->keep_cnt_sent++; //探查次数加一
        }
      }
    }

3,删除超时PCB


    //若pcb的osseq队列中无序的数据超过一定时长会被丢弃
    if (pcb->ooseq != NULL &&
        (u32_t)tcp_ticks - pcb->tmr >= pcb->rto * TCP_OOSEQ_TIMEOUT) {
    
    
      tcp_segs_free(pcb->ooseq);
      pcb->ooseq = NULL;
      LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: dropping OOSEQ queued data\n"));
    }
    
    //一直等不到服务端回答的tcp要被移除
    if (pcb->state == SYN_RCVD) {
    
    
      if ((u32_t)(tcp_ticks - pcb->tmr) >
          TCP_SYN_RCVD_TIMEOUT / TCP_SLOW_INTERVAL) {
    
    
        ++pcb_remove;
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in SYN-RCVD\n"));
      }
    }

     //在last_ack超过一定时间也被删除
    if (pcb->state == LAST_ACK) {
    
    
      if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
    
    
        ++pcb_remove;
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: removing pcb stuck in LAST-ACK\n"));
      }
    }

以上的代码中若当前PCB需要被删除,则pcb_remove不为0,具体的删除代码如下:

    if (pcb_remove) {
    
    	//不为0则需要删除pcb
      struct tcp_pcb *pcb2;

      tcp_err_fn err_fn = pcb->errf;  //错误回调函数

      void *err_arg;
      enum tcp_state last_state;
      tcp_pcb_purge(pcb); //释放pcb部分成员,pcb结构体不会被释放
      
      if (prev != NULL) {
    
    
        LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_active_pcbs", pcb != tcp_active_pcbs);
        prev->next = pcb->next;
      } else {
    
    
        /* This PCB was the first. */
        LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_active_pcbs", tcp_active_pcbs == pcb);
        tcp_active_pcbs = pcb->next;
      }

      //需要发送重置报文
      if (pcb_reset) {
    
    
        tcp_rst(pcb->snd_nxt, pcb->rcv_nxt, &pcb->local_ip, &pcb->remote_ip,
                 pcb->local_port, pcb->remote_port);
      }

      err_arg = pcb->callback_arg;
      last_state = pcb->state;
     
      pcb2 = pcb;
      pcb = pcb->next;  //获取下一个pcb
      memp_free(MEMP_TCP_PCB, pcb2);  //释放pcb结构体

      tcp_active_pcbs_changed = 0;
      TCP_EVENT_ERR(last_state, err_fn, err_arg, ERR_ABRT); //调用错误回调函数
      if (tcp_active_pcbs_changed) {
    
    
        goto tcp_slowtmr_start;
      }
    } else {
    
    
      //不需要删除pcb
      prev = pcb;
      pcb = pcb->next;  //获取下一个pcb

      //TODO 定时周期到,调用回调函数
      ++prev->polltmr;
      if (prev->polltmr >= prev->pollinterval) {
    
    
        prev->polltmr = 0;
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: polling application\n"));
        tcp_active_pcbs_changed = 0;
        TCP_EVENT_POLL(prev, err);  //!回调周期性函数
        if (tcp_active_pcbs_changed) {
    
    
          goto tcp_slowtmr_start;
        }
        
        if (err == ERR_OK) {
    
    
          tcp_output(prev); //输出unsent报文
        }
      }
    }
  }

以上都是处理active链表的pcb,接下来处理timewait的pcb,苍天饶过谁?

  /*--------------------------------遍历所有timewait的pcb-----------------------*/
  /* Steps through all of the TIME-WAIT PCBs. */
  prev = NULL;
  pcb = tcp_tw_pcbs;
  while (pcb != NULL) {
    
    
    LWIP_ASSERT("tcp_slowtmr: TIME-WAIT pcb->state == TIME-WAIT", pcb->state == TIME_WAIT);
    pcb_remove = 0;
    //是否达到2MSL,是则删除
    if ((u32_t)(tcp_ticks - pcb->tmr) > 2 * TCP_MSL / TCP_SLOW_INTERVAL) {
    
    
      ++pcb_remove;
    }

    if (pcb_remove) {
    
    
      struct tcp_pcb *pcb2;
      tcp_pcb_purge(pcb);
      /* Remove PCB from tcp_tw_pcbs list. */
      if (prev != NULL) {
    
    
        LWIP_ASSERT("tcp_slowtmr: middle tcp != tcp_tw_pcbs", pcb != tcp_tw_pcbs);
        prev->next = pcb->next;
      } else {
    
    
        /* This PCB was the first. */
        LWIP_ASSERT("tcp_slowtmr: first pcb == tcp_tw_pcbs", tcp_tw_pcbs == pcb);
        tcp_tw_pcbs = pcb->next;
      }
      pcb2 = pcb;
      pcb = pcb->next;
      memp_free(MEMP_TCP_PCB, pcb2);
    } else {
    
    
      prev = pcb;
      pcb = pcb->next;
    }
  }

四,小结

tcp定时任务是tcp接收发送的动力来源,需要关注。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/111396150
今日推荐