lwip源码分析之 TCP协议 数据输入 (二)

一,简介

在上一章输入的tcp报文在找到了其对应的TCP控制块后,需要根据tcp控制块的不同状态,对报文有不同的处理方式。这将可能导致tcp状态的变化,使用tcp状态机来表示这样状态变化。

在这里插入图片描述
(图侵删)
再结合tcp连接和断开的过程了解tcp状态机

在这里插入图片描述
在这里插入图片描述

二,源码分析

tcp状态机的源码就是上图状态机的实现;
首先是处理rst,若输入报文是rst报文,则判断该rst合不合法,合法则复位tcp连接:

  /*---------------------------------------优先处理rst报文----------------------------------------*/
  if (flags & TCP_RST) {
    
      //报文中带rst标志
   if (pcb->state == SYN_SENT) {
    
     //连接建立过程中,判断ackno来识别一个rst是否正确
      if (ackno == pcb->snd_nxt) {
    
    
        //连接建立过程。应答序号等于下一个要发送的序号,是正确的
        acceptable = 1;
      }
    } else {
    
      //其他状态的tcp需要检查报文的序号以判断rst是否合法
      
      //报文序号等于期待接收的序号时,pcb复位
      if (seqno == pcb->rcv_nxt) {
    
    
        acceptable = 1; 
      } else  if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,
                                  pcb->rcv_nxt + pcb->rcv_wnd)) {
    
    
        //如果序号是在接收窗口内,则发送ack,等待回复重新检查报文序号
        tcp_ack_now(pcb);
      }
    }

    if (acceptable) {
    
    
      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: Connection RESET\n"));
      LWIP_ASSERT("tcp_input: pcb->state != CLOSED", pcb->state != CLOSED);
      recv_flags |= TF_RESET; //process处理结果标志为复位
      pcb->flags &= ~TF_ACK_DELAY;  //清除ack
      return ERR_RST;
    } else {
    
      //不需要复位,忽略rst报文
      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
       seqno, pcb->rcv_nxt));
      LWIP_DEBUGF(TCP_DEBUG, ("tcp_process: unacceptable reset seqno %"U32_F" rcv_nxt %"U32_F"\n",
       seqno, pcb->rcv_nxt));
      return ERR_OK;
    }
  }
  //本地不处于SYN_SENT或SYN_RCVD,却收到了SYN,是异常情况,可能是对方重启了
  if ((flags & TCP_SYN) && (pcb->state != SYN_SENT && pcb->state != SYN_RCVD)) {
    
    
    /* Cope with new connection attempt after remote end crashed */
    //应对远程端崩溃后的新连接尝试。
    tcp_ack_now(pcb);
    return ERR_OK;
  }

  //如果本地接收未关闭,就复位保活计时器
  if ((pcb->flags & TF_RXCLOSED) == 0) {
    
    
    /* Update the PCB (in)activity timer unless rx is closed (see tcp_shutdown) */
    pcb->tmr = tcp_ticks; 
  }
  pcb->keep_cnt_sent = 0; //清除保活计数值

  tcp_parseopt(pcb);  //解析出mss

现在进入状态机的具体代码

  switch (pcb->state) {
    
    
  /*--------------------------------客户端发起连接请求等待服务器返回syn和ack----------------------------------------*/
  case SYN_SENT:  
    LWIP_DEBUGF(TCP_INPUT_DEBUG, ("SYN-SENT: ackno %"U32_F" pcb->snd_nxt %"U32_F" unacked %"U32_F"\n", ackno,
     pcb->snd_nxt, lwip_ntohl(pcb->unacked->tcphdr->seqno)));

    //报文是ack和syn,且序号能对的上,则发送ack,并进入连接建立状态
    if ((flags & TCP_ACK) && (flags & TCP_SYN)
        && (ackno == pcb->lastack + 1)) {
    
    
      pcb->rcv_nxt = seqno + 1;
      pcb->rcv_ann_right_edge = pcb->rcv_nxt;//?不晓得
      pcb->lastack = ackno;
      pcb->snd_wnd = tcphdr->wnd; //本地发送窗口为对方的通告窗口大小
      pcb->snd_wnd_max = pcb->snd_wnd;
      pcb->snd_wl1 = seqno - 1; //上次更新时序号
      pcb->state = ESTABLISHED;

      //计算ip路径下的mss
      pcb->mss = tcp_eff_send_mss(pcb->mss, &pcb->local_ip, &pcb->remote_ip);

      pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss); //初始化拥塞窗口
      LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SENT): cwnd %"TCPWNDSIZE_F
                                   " ssthresh %"TCPWNDSIZE_F"\n",
                                   pcb->cwnd, pcb->ssthresh));
      LWIP_ASSERT("pcb->snd_queuelen > 0", (pcb->snd_queuelen > 0));
      --pcb->snd_queuelen;  //发送报文队列减1
      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_process: SYN-SENT --queuelen %"TCPWNDSIZE_F"\n", (tcpwnd_size_t)pcb->snd_queuelen));
      
      //既然收到了ack,那么就将unack队列的第一个报文删除
      rseg = pcb->unacked;
      //lwip将重传定时到的unacked放到了unsent,所以unacked的报文可能被放到了unsent
      if (rseg == NULL) {
    
    
        rseg = pcb->unsent;
        LWIP_ASSERT("no segment to free", rseg != NULL);
        pcb->unsent = rseg->next;
      } else {
    
    
        pcb->unacked = rseg->next;  //重新组织unacked
      }
      tcp_seg_free(rseg); //释放被acked的报文

      //如果接下来没有需要应答的报文,则关闭重传定时器,否则重置它
      if (pcb->unacked == NULL) {
    
    
        pcb->rtime = -1;
      } else {
    
    
        pcb->rtime = 0;
        pcb->nrtx = 0;
      }

      //回调连接建立函数
      TCP_EVENT_CONNECTED(pcb, ERR_OK, err);
      if (err == ERR_ABRT) {
    
    
        return ERR_ABRT;
      }
      //最后发送一个ack,三次握手结束
      tcp_ack_now(pcb);
    }

    //只收到ack报文,可能是半连接
    else if (flags & TCP_ACK) {
    
    
    	//发送rst报文重新连接
      tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
        ip_current_src_addr(), tcphdr->dest, tcphdr->src);

      //重传次数不超限制则立即重传,包括SYN
      if (pcb->nrtx < TCP_SYNMAXRTX) {
    
    
        pcb->rtime = 0;
        tcp_rexmit_rto(pcb);
      }
    }
    break;
  /*------------------------------服务器收到SYN并发送完syn+ack,等待客户端回答-----------------------------------*/
  case SYN_RCVD:  
    if (flags & TCP_ACK) {
    
      //报文是客户端的ack报文
      if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {
    
     //确认序号在发送窗口的正确范围
        pcb->state = ESTABLISHED; //进入建立状态
        LWIP_DEBUGF(TCP_DEBUG, ("TCP connection established %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
#if LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG
#if LWIP_CALLBACK_API
        LWIP_ASSERT("pcb->listener->accept != NULL",
          (pcb->listener == NULL) || (pcb->listener->accept != NULL));
#endif
        if (pcb->listener == NULL) {
    
    
     
          err = ERR_VAL;  //listen pcb可能已经被关闭了
        } else
#endif /* LWIP_CALLBACK_API || TCP_LISTEN_BACKLOG */
        {
    
    
          tcp_backlog_accepted(pcb);  //无操作
          
          //回调连接建立函数
          TCP_EVENT_ACCEPT(pcb->listener, pcb, pcb->callback_arg, ERR_OK, err);
        }
        //如果回调函数返回错误,或者listen pcb已经被关闭,则终止tcp
        if (err != ERR_OK) {
    
    
          if (err != ERR_ABRT) {
    
    
            tcp_abort(pcb);
          }
          return ERR_ABRT;
        }
        //报文中还有其他的数据,则交给应用层处理
        tcp_receive(pcb);

        //如果本地发送的数据被报文中的ack确认,则实际acked-1,因为syn占一个字节
        if (recv_acked != 0) {
    
    
          recv_acked--;
        }

        pcb->cwnd = LWIP_TCP_CALC_INITIAL_CWND(pcb->mss); //设置阻塞窗口
        LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_process (SYN_RCVD): cwnd %"TCPWNDSIZE_F
                                     " ssthresh %"TCPWNDSIZE_F"\n",
                                     pcb->cwnd, pcb->ssthresh));
        //如果有fin标志,则回答,并进入closewait,等待应用程序
        if (recv_flags & TF_GOT_FIN) {
    
    
          tcp_ack_now(pcb);
          pcb->state = CLOSE_WAIT;
        }
      } else {
    
      //非法的ack确认序号,发送rst
        
        tcp_rst(ackno, seqno + tcplen, ip_current_dest_addr(),
          ip_current_src_addr(), tcphdr->dest, tcphdr->src);
      }
    } else if ((flags & TCP_SYN) && (seqno == pcb->rcv_nxt - 1)) {
    
      

      //收到对方重复的syn,说明服务器发出的syn+ack丢失,重发
      tcp_rexmit(pcb);
    }
    break;
  case CLOSE_WAIT:  //服务器不会接收数据
    /* FALLTHROUGH */
  case ESTABLISHED: //已经建立连接
    tcp_receive(pcb); //将数据交给上层

    //被动关闭连接
    if (recv_flags & TF_GOT_FIN) {
    
     /* passive close */
      tcp_ack_now(pcb);
      pcb->state = CLOSE_WAIT;
    }
    break;
  /*------------------------------------客户端发送断开请求,等待回答-----------------------------------*/
  case FIN_WAIT_1:  
    tcp_receive(pcb); //将数据交给上层
    if (recv_flags & TF_GOT_FIN) {
    
      //收到服务器的断开请求
      if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt) && //同时收到本地FIN的ack,且本地没有未发送的数据,跳过wait2,进入timewait
          pcb->unsent == NULL) {
    
    
        LWIP_DEBUGF(TCP_DEBUG,
          ("TCP connection closed: FIN_WAIT_1 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
        tcp_ack_now(pcb); //发送ack
        tcp_pcb_purge(pcb); //释放pcb
        TCP_RMV_ACTIVE(pcb);  //移除链表
        pcb->state = TIME_WAIT;
        TCP_REG(&tcp_tw_pcbs, pcb); //进入新的链表
      } else {
    
      //异常情况,可能是双方同时发起断开
        tcp_ack_now(pcb);
        pcb->state = CLOSING;
      }
    } else if ((flags & TCP_ACK) && (ackno == pcb->snd_nxt) &&
               pcb->unsent == NULL) {
    
    
      pcb->state = FIN_WAIT_2;  //收到ack,进入FIN_WAIT_2,等待服务器的FIN
    }
    break;
  /*----------------------------------客户端已经发送断开连接,等待服务端应用程序断开----------------------*/
  case FIN_WAIT_2:
    tcp_receive(pcb); //将数据交给上层
    //收到服务器的断开请求,回答
    if (recv_flags & TF_GOT_FIN) {
    
      
      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: FIN_WAIT_2 %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
      tcp_ack_now(pcb);
      tcp_pcb_purge(pcb);
      TCP_RMV_ACTIVE(pcb);
      pcb->state = TIME_WAIT;
      TCP_REG(&tcp_tw_pcbs, pcb);
    }
    break;
  case CLOSING: //两端同时关闭连接
    tcp_receive(pcb);

    //收到了对方的ack且没有未发送的数据,进入TIME_WAIT
    if ((flags & TCP_ACK) && ackno == pcb->snd_nxt && pcb->unsent == NULL) {
    
    
      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: CLOSING %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));
      tcp_pcb_purge(pcb);
      TCP_RMV_ACTIVE(pcb);
      pcb->state = TIME_WAIT;
      TCP_REG(&tcp_tw_pcbs, pcb);
    }
    break;
  case LAST_ACK:  //服务器上层处理完,发送断开请求,等待对方回答
    tcp_receive(pcb);

    //收到了对方的ack且没有未发送的数据,结束tcp一生
    if ((flags & TCP_ACK) && ackno == pcb->snd_nxt && pcb->unsent == NULL) {
    
    
      LWIP_DEBUGF(TCP_DEBUG, ("TCP connection closed: LAST_ACK %"U16_F" -> %"U16_F".\n", inseg.tcphdr->src, inseg.tcphdr->dest));

      recv_flags |= TF_CLOSED;  //tcp_input会对tcp进一步释放
    }
    break;
  default:
    break;
  }
  return ERR_OK;
}

可见tcp状态机完成的主要还是tcp状态之间的转换的逻辑。而真正的tcp报文里的数据则交给了tcp_receive(),该函数检查tcp报文数据,并将有序的数据传递给应用层。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_44821644/article/details/111357509