TCP protocol timer tcp_slowtmr() of lwip source code analysis; and tcp_fasttmr();

Many places in the TCP protocol require the use of timing functions, such as timing retransmission, keepalive keepalive, and persistent timer functions. These timing functions will be implemented in two timer functions in lwip.

One, the timer clock

Second, fast timed tasks

void tcp_fasttmr(void)Relatively simple, its function is mainly to process the delayed ack message and fin message every 250ms, and at the same time notify the upper layer application to process the data.

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;
    }
  }
}

Three, low-speed timing tasks

void tcp_slowtmr(void)Call every 500ms, this function is completed the retransmission timeout, TCP keep-alive function, and will traverse activeand timewaitthe list PCB, PCB out or remove those errors, while the transmission data in the PCB unsent queue out. Generally use tcp_write(); After writing the data, the data will not be sent immediately, but will be sent in a timed task.

1. Retransmit after timeout

The key code comments are as follows:

    //请求连接次数超出限制
    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

The server needs to check whether the client can still communicate. If there is no communication within two hours, the client sends a probe message. If the client ack, the keepalive timer is updated. Otherwise, a probe message is sent every 75s. If there are more than 9 messages, it is considered that the client has hung up

    /* 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. Delete the timeout 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"));
      }
    }

In the above code, if the current PCB needs to be deleted, pcb_remove is not 0. The specific deletion code is as follows:

    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报文
        }
      }
    }
  }

The above are the pcbs that deal with the active linked list, and then the pcbs that deal with the timewait. Who will God spare?

  /*--------------------------------遍历所有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;
    }
  }

Four, summary

The tcp timing task is the power source for tcp to receive and send, which needs to be paid attention to.
Insert picture description here

Guess you like

Origin blog.csdn.net/weixin_44821644/article/details/111396150