TCP/IP传输层协议实现 - TCP的保活定时器(lwip)

Posted arm7star

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TCP/IP传输层协议实现 - TCP的保活定时器(lwip)相关的知识,希望对你有一定的参考价值。

参考《TCP-IP详解卷 1:协议》"第23章 TCP的保活定时器"。

1、TCP的保活定时器介绍

TCP的保活定时器主要是在双方都没有数据收发过程中,用于确认对方是否仍然在线(keep alive;网络断开可能导致连接断开,服务器重启也可能导致连接断开,很多没有收到FIN报文就已经断开了的情况,因此需要保活定时器检测连接是否已经断开)。

 

2、TCP的保活定时器实现

2.1、KEEPALIVE发送次数重置

连接设置了SOF_KEEPALIVE,那么tcp keep alive就会计时,与超时重传不一样,收到报文时KEEPALIVE发送次数keep_cnt_sent重置为0(收到报文表明对方仍然有保持连接;tcp_input调用tcp_process重置KEEPALIVE发送次数)。

  /* Update the PCB (in)activity timer. */
  pcb->tmr = tcp_ticks; // tmr设置为收到报文的时间
  pcb->keep_cnt_sent = 0; // KEEPALIVE发送次数重置为0

2.2、KEEPALIVE超时检测

tcp定时器周期性调用tcp_slowtmr,对定时器计时、检查是否有定时器超时。

KEEPALIVE超时检测是在tcp_slowtmr函数中。

KEEPALIVE总时间是否超时。tcp_ticks为当前时间,pcb->tmr为连接最后一次收到对方报文的时间,"tcp_ticks - pcb->tmr"即为多久没有收到对方的报文;pcb->keep_idle第一次发送KEEPALIVE前保持idle状态的时间,最后一次收到报文之后的keep_idle时间内不发送KEEPALIVE报文,pcb->keep_cnt*pcb->keep_intvl为允许发送KEEPALIVE的总时间(可以发送的次数乘以每次发送时间间隔)。

如果pcb->keep_cnt*pcb->keep_intvl时间没有收到对方的数据,那么认为对方已经断开连接或者不可达,直接关闭本地连接。

    /* Check if KEEPALIVE should be sent */
    if((pcb->so_options & SOF_KEEPALIVE) && // 连接设置了KEEPALIVE选项
       ((pcb->state == ESTABLISHED) || // ESTABLISHED连接状态
        (pcb->state == CLOSE_WAIT))) { // CLOSE_WAIT等待关闭状态
#if LWIP_TCP_KEEPALIVE
      if((u32_t)(tcp_ticks - pcb->tmr) > 
         (pcb->keep_idle + (pcb->keep_cnt*pcb->keep_intvl))
         / TCP_SLOW_INTERVAL) // KEEPALIVE总时间超时,关闭本地连接
#else      
      if((u32_t)(tcp_ticks - pcb->tmr) > 
         (pcb->keep_idle + TCP_MAXIDLE) / TCP_SLOW_INTERVAL)
#endif /* LWIP_TCP_KEEPALIVE */
      {
        LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: KEEPALIVE timeout. Aborting connection to %"U16_F".%"U16_F".%"U16_F".%"U16_F".\\n",
                                ip4_addr1(&pcb->remote_ip), ip4_addr2(&pcb->remote_ip),
                                ip4_addr3(&pcb->remote_ip), ip4_addr4(&pcb->remote_ip)));
        
        tcp_abort(pcb); // 关闭本地连接
      }

如果KEEPALIVE总时间没有超时,检查距离上一次发送KEEPALIVE报文的时间,确定是否需要再次发送KEEPALIVE报文。keep_cnt_sent为已经发送KEEPALIVE报文的次数。

#if LWIP_TCP_KEEPALIVE
      else if((u32_t)(tcp_ticks - pcb->tmr) > 
              (pcb->keep_idle + pcb->keep_cnt_sent * pcb->keep_intvl)
              / TCP_SLOW_INTERVAL)
#else
      else if((u32_t)(tcp_ticks - pcb->tmr) > 
              (pcb->keep_idle + pcb->keep_cnt_sent * TCP_KEEPINTVL_DEFAULT) 
              / TCP_SLOW_INTERVAL)
#endif /* LWIP_TCP_KEEPALIVE */
      {
        tcp_keepalive(pcb); // 发送KEEPALIVE报文
        pcb->keep_cnt_sent++; // KEEPALIVE发送次数keep_cnt_sent加1
      }

3、KEEPALIVE报文的发送

lwip调用tcp_keepalive发送KEEPALIVE报文。tcp_keepalive发送一个已经发送但是没有数据的报文(seqno已经发送过,seqno为最后一个报文的最后一个字节序号,与坚持定时器不一样,坚持定时器发送的是已经发送报文第一个字节的序号,KEEPALIVE报文发送时,正常情况下发送队列没有数据,对方如果还没断开连接,对方收到一个重发序号的报文时,将发送ACK,发送方收到ACK报文则认为对方仍处于alive状态,KEEPALIVE重传次数重置为0)。

void
tcp_keepalive(struct tcp_pcb *pcb)
{
  struct pbuf *p;
  struct tcp_hdr *tcphdr;

  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: sending KEEPALIVE probe to %"U16_F".%"U16_F".%"U16_F".%"U16_F"\\n",
                          ip4_addr1(&pcb->remote_ip), ip4_addr2(&pcb->remote_ip),
                          ip4_addr3(&pcb->remote_ip), ip4_addr4(&pcb->remote_ip)));

  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: tcp_ticks %"U32_F"   pcb->tmr %"U32_F" pcb->keep_cnt_sent %"U16_F"\\n", 
                          tcp_ticks, pcb->tmr, pcb->keep_cnt_sent));
   
  p = pbuf_alloc(PBUF_IP, TCP_HLEN, PBUF_RAM);
   
  if(p == NULL) {
    LWIP_DEBUGF(TCP_DEBUG, 
                ("tcp_keepalive: could not allocate memory for pbuf\\n"));
    return;
  }
  LWIP_ASSERT("check that first pbuf can hold struct tcp_hdr",
              (p->len >= sizeof(struct tcp_hdr)));

  tcphdr = tcp_output_set_header(pcb, p, 0, htonl(pcb->snd_nxt - 1)); // pcb->snd_nxt - 1已经发送报文的序号

#if CHECKSUM_GEN_TCP
  tcphdr->chksum = inet_chksum_pseudo(p, &pcb->local_ip, &pcb->remote_ip,
                                      IP_PROTO_TCP, p->tot_len);
#endif
  TCP_STATS_INC(tcp.xmit);

  /* Send output to IP */
#if LWIP_NETIF_HWADDRHINT
  ip_output_hinted(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP,
    &(pcb->addr_hint)); // 报文发送(不缓存到unacked队列,KEEPALIVE超时会再次发送KEEPALIVE报文)
#else /* LWIP_NETIF_HWADDRHINT*/
  ip_output(p, &pcb->local_ip, &pcb->remote_ip, pcb->ttl, 0, IP_PROTO_TCP);
#endif /* LWIP_NETIF_HWADDRHINT*/

  pbuf_free(p);

  LWIP_DEBUGF(TCP_DEBUG, ("tcp_keepalive: seqno %"U32_F" ackno %"U32_F".\\n",
                          pcb->snd_nxt - 1, pcb->rcv_nxt));
}

 

以上是关于TCP/IP传输层协议实现 - TCP的保活定时器(lwip)的主要内容,如果未能解决你的问题,请参考以下文章

网络协议|TCP/IP协议相关

TCP 的保活定时器

TCP的保活机制

TCP/IP协议之四TCP协议(上)—理论+实践给你讲清楚

网络基础tcp/ip协议五

TCP/IP传输层协议实现 - TCP报文接收/发送(lwip)