tcp_receive函数分析

来源:互联网 发布:python网络编程 pdf 编辑:程序博客网 时间:2024/06/07 10:40
1.调用流程: 
    tcp_input接收IP层递交上来的数据包,获取TCP首部长度(包括选项部分),将p指针移向pbuf的有效数据部分,
    根据TCP报头,遍历tcp_active_pcbs链表,tcp_tw_pcbs链表,tcp_listen_pcbs链表,查找相应TCP控制块,
    若在tcp_active_pcbs链表中匹配,调用tcp_process()
    在process函数中主要实现和TCP状态转换,对数据的具体处理则调用tcp_receive()处理。

2.函数简析:
    只会被tcp_process函数调用,用于进一步完成对输入报文的处理,    更新滑动窗口。
    具体来说,该函数主要是完成输入报文的冗余截断,管理unacked、unsent、ooseq三张链表。
    unacked链表存的是已发送但未回ACK的报文。
    unsent链表存的是需要重传的报文。
    ooseq链表存的是已接收但乱序的报文。

3.具体分析:    (在源码中有详细注释)
    1.首先判断是否为ACK报文(报文中ACK标志置1),若是,通过以下三种情况判断是否更新窗口:
       a.上一次报文 seq < 输入报文中的 seq,(说明对方有发来数据);
       b.上一次报文 seq = 输入报文中的 seq,且上一次报文 ack < 输入报文的 ack,(说明对方没有发送数据,只是在收到数据后发送一个确认);

       c.上一次报文 ack = 输入报文的 ack,且发送窗口大小 wnd < 小于输入报文首部的窗口通告 wnd,(说明我方没有发数据过去,但被对方告知接收窗口变大);
       如果满足以上三种情况的其中一种,则做以下操作更新窗口
       a.更新本地窗口大小(与输入报文中接收窗口大小通告匹配);
       b.更新收到的序号,更新收到的确认号(即snd_wl1,snd_wl2被更新。);
       c.然后干一堆有点看不懂的操作(若发送窗口为0,开启坚持计时器,干嘛用暂时不明);
    2.判断是否是一个重复的ACK报文,需要满足5个条件:
       a.如果输入报文中的ack <= 上一次报文中的ack,(即没有确认新数据);
       b.如果报文段中没有数据;
       c.如果本地发送窗口没有更新;
       d.如果重传定时器正在运行,即本地有数据正等待被确认;
       e.如果ackno等于lastack(这个条件感觉和第一个条件差不多啊?);
       以上五个条件都满足确定这就是一个重传的ACK报文。
      (对重传的ACK报文会进行计数,重传超过3次认为发生拥塞,调用tcp_rexmit_fast函数,进行快速重传
    3.若不是重复的ACK报文,判断是否是一个合法ACK报文上一次报文中的确认号ACK <= 输入报文中的确认号ACK <= 控制块要发送的下一个序号
       如果处于TCP连接已经建立状态,调整拥塞算法功能模块;
       遍历unacked队列,将所有数据编号小于等于ackno的报文段移除(有点懂了,滑动窗口那块~);
    4.不是重复的ACK报文,也不是合法ACK报文,则调用tcp_send_empty_ack函数向对方回一个空的ACK报文,(即该ACK不确认任何已发送数据);
    5.遍历unsent队列,将所有数据编号小于等于ackno的报文段移除。
       (这是因为对于需要重传的报文段,lwip直接将它们挂在unsent队列上,所以收到的ACK可能是对已超时报文段的确认
    6.RTT计算,略。
       (以上部分做的是判断是否是一个ACK报文,更新窗口,更新unacked队列,更新unsent队列)。


    7. 若报文段中包含数据,则要继续对数据进行处理:
       a.如果seqno + 1 <= rcv_nxt <= seqno + tcplen - 1意味着收到的数据区域头部有无效数据
          (输入报文中的序号 < 期望收到的下一个序号 < 输入报文中的序号+报文长度即末尾序号)
          (收到的数据有部分处于本地左侧接收窗口外),需要截断数据头;
       b.如果seqno < rcv_nxt,且seqno + tcplen - 1 < rcv_nxt,说明这是个完全重复的报文段(末尾序号也比期望接收的序号要小)
          (只回复一个ACK)
          rcv_nxt:期望收到的下一个序号,若seqno = rcv_nxt说明该报文为连续报文
          (在接收窗口内的报文分连续报文和乱序报文)其中:
       c.如果seqno = rcv_nxt,说明报文是连续的,进行处理:
          (处理有点复杂,大致是更新各种参数,此时rcv_nxt已更新
          之后去ooseq队列中找是否已经有下一个连续报文存在(报文的seqno = rcv_nxt),若有继续处理更新。
       d.如果seqno > rcv_nxt,说明报文不是有序的(乱序报文,只要在接收窗口大小内都会保存)。处理:
           先返回一个ACK, 然后将该报文段放入ooseq队列;
       e.如果 seqno 不在 rcv_nxt 和 rcv_nxt + rcv_wnd - 1之间说明据不在接收窗口内,返回一个ACK。
    8.若报文段中没有包含数据,且序号位于接收窗口之内返回一个ACK;
           

(终于结束了! 有点乱,待更新...)
       

4.源码:

static voidtcp_receive(struct tcp_pcb *pcb){  struct tcp_seg *next;#if TCP_QUEUE_OOSEQ  struct tcp_seg *prev, *cseg;#endif /* TCP_QUEUE_OOSEQ */  s32_t off;  s16_t m;  u32_t right_wnd_edge;    //本地发送窗口右边界  u16_t new_tot_len;  int found_dupack = 0;    //重复ack标志,置1表示是重复ack#if TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS  u32_t ooseq_blen;  u16_t ooseq_qlen;#endif /* TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS */  LWIP_ASSERT("tcp_receive: wrong state", pcb->state >= ESTABLISHED);  /* 首先检测报文是否包含ACK标志 */  if (flags & TCP_ACK) {    /* 发送窗口右边界 = 发送窗口大小 + 初始发送窗口序号     * (pcb->snd_wl2 记录的是新建连接的窗口初始序号,厉害了~)*/    right_wnd_edge = pcb->snd_wnd + pcb->snd_wl2;    //获取本地发送窗口右边界    /* 有3种情况可以导致本地发送窗口更新 */    /* pcb->snd_wl1 记录的是上一次窗口更新后的序号 */    /* seqno:TCP报文中的序号 */    /* ackno:TCP报文中的确认序号(下一个期待接收的序号) */    if (TCP_SEQ_LT(pcb->snd_wl1, seqno) ||    //       /* snd_wl1小于新seqno,说明对方有发来数据 */       /* snd_wl1等于新seqno且snd_wl2小于新ackno,说明对方没有发送数据,只是在收到数据后发送一个确认 */       (pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno)) ||       /* snd_wl2等于新ackno且snd_wnd小于报文首部的窗口通告wnd,说明我方没有发数据过去,但被对方告知接收窗口变大 */       (pcb->snd_wl2 == ackno && (u32_t)SND_WND_SCALE(pcb, tcphdr->wnd) > pcb->snd_wnd)) {      pcb->snd_wnd = SND_WND_SCALE(pcb, tcphdr->wnd);    //更新本地发送窗口大小,跟对方发来的接收窗口通告匹配      /* 跟踪远程主机所宣布的最大窗口的计算段的最大尺寸 */      if (pcb->snd_wnd_max < pcb->snd_wnd) {        pcb->snd_wnd_max = pcb->snd_wnd;      }      pcb->snd_wl1 = seqno;    //更新接收到的序号      pcb->snd_wl2 = ackno;    //更新接收到的确认号      if (pcb->snd_wnd == 0) {        if (pcb->persist_backoff == 0) {          /* start persist timer */          pcb->persist_cnt = 0;          pcb->persist_backoff = 1;        }        /* 如果发送窗口非0,且探察开启 */      } else if (pcb->persist_backoff > 0) {        /* 停止窗口探察 */          pcb->persist_backoff = 0;      }      LWIP_DEBUGF(TCP_WND_DEBUG, ("tcp_receive: window update %"TCPWNDSIZE_F"\n", pcb->snd_wnd));#if TCP_WND_DEBUG    } else {      if (pcb->snd_wnd != (tcpwnd_size_t)SND_WND_SCALE(pcb, tcphdr->wnd)) {        LWIP_DEBUGF(TCP_WND_DEBUG,                    ("tcp_receive: no window update lastack %"U32_F" ackno %"                     U32_F" wl1 %"U32_F" seqno %"U32_F" wl2 %"U32_F"\n",                     pcb->lastack, ackno, pcb->snd_wl1, seqno, pcb->snd_wl2));      }#endif /* TCP_WND_DEBUG */    }    /* (From Stevens TCP/IP Illustrated Vol II, p970.) Its only a     * duplicate ack if:     * 1) It doesn't ACK new data     * 2) length of received packet is zero (i.e. no payload)     * 3) the advertised window hasn't changed     * 4) There is outstanding unacknowledged data (retransmission timer running)     * 5) The ACK is == biggest ACK sequence number so far seen (snd_una)     *     * If it passes all five, should process as a dupack:     * a) dupacks < 3: do nothing     * b) dupacks == 3: fast retransmit     * c) dupacks > 3: increase cwnd     *     * If it only passes 1-3, should reset dupack counter (and add to     * stats, which we don't do in lwIP)     *     * If it only passes 1, should reset dupack counter     *     */    /* Clause 1 */    /* 判断是否是一个重复的ACK,需要满足5个条件 */    /* 1.如果ackno小于等于lastack,即没有确认新数据 */    if (TCP_SEQ_LEQ(ackno, pcb->lastack)) {      /* 2.如果报文段中没有数据 */      if (tcplen == 0) {        /* 3.本地发送窗口没有更新 */        if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge) {          /* 4.如果重传定时器正在运行,即本地有数据正等待被确认 */          if (pcb->rtime >= 0) {            /* 5.如果ackno等于lastack */            if (pcb->lastack == ackno) {              found_dupack = 1;    //此时可以确定这是一个重复的ack,说明报文发生了丢失              if ((u8_t)(pcb->dupacks + 1) > pcb->dupacks) {                ++pcb->dupacks;    //该ack被重复收到的次数自增              }              /* 如果该ack重复收到超过3次,说明发生了拥塞 */              if (pcb->dupacks > 3) {                /* Inflate the congestion window, but not if it means that                   the value overflows. */                if ((tcpwnd_size_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {                  pcb->cwnd += pcb->mss;                }                /* 如果该ack重复第3次收到,执行快速重传算法 */              } else if (pcb->dupacks == 3) {                /* Do fast retransmit */                tcp_rexmit_fast(pcb);              }            }          }        }      }      /* 如果没有确认新数据但又不属于重复ack */      if (!found_dupack) {        pcb->dupacks = 0;    //将ack重复收到的次数清0      }      /* 如果是正常情况的ACK,lastack+1<=ackno<=snd_nxt */    } else if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)) {      /* 当ACK承认新的数据时,我们来到这里 */      /* 如果控制块处于快速重传状态,则关闭重传状态、拥塞功能  */      if (pcb->flags & TF_INFR) {        pcb->flags &= ~TF_INFR;        pcb->cwnd = pcb->ssthresh;      }      pcb->nrtx = 0;                           //重传次数清0      pcb->rto = (pcb->sa >> 3) + pcb->sv;     //复位重传超时时间      pcb->dupacks = 0;                        //将ack重复收到的次数清0      pcb->lastack = ackno;                    //更新接收到的ackno      /* 如果处于TCP连接已经建立状态,调整拥塞算法功能模块 */      if (pcb->state >= ESTABLISHED) {        if (pcb->cwnd < pcb->ssthresh) {          if ((tcpwnd_size_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {            pcb->cwnd += pcb->mss;          }          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd));        } else {          tcpwnd_size_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);          if (new_cwnd > pcb->cwnd) {            pcb->cwnd = new_cwnd;          }          LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: congestion avoidance cwnd %"TCPWNDSIZE_F"\n", pcb->cwnd));        }      }      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: ACK for %"U32_F", unacked->seqno %"U32_F":%"U32_F"\n",                                    ackno,                                    pcb->unacked != NULL?                                    lwip_ntohl(pcb->unacked->tcphdr->seqno): 0,                                    pcb->unacked != NULL?                                    lwip_ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked): 0));      /* 遍历unacked队列,将所有数据编号小于等于ackno的报文段移除 */      while (pcb->unacked != NULL &&             TCP_SEQ_LEQ(lwip_ntohl(pcb->unacked->tcphdr->seqno) +                         TCP_TCPLEN(pcb->unacked), ackno)) {        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: removing %"U32_F":%"U32_F" from pcb->unacked\n",                                      lwip_ntohl(pcb->unacked->tcphdr->seqno),                                      lwip_ntohl(pcb->unacked->tcphdr->seqno) +                                      TCP_TCPLEN(pcb->unacked)));        next = pcb->unacked;                //将满足要求的报文从unacked链表取出        pcb->unacked = pcb->unacked->next;        LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"TCPWNDSIZE_F" ... ", (tcpwnd_size_t)pcb->snd_queuelen));        LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));        pcb->snd_queuelen -= pbuf_clen(next->p);    //释放被该报文占用的发送空间        recv_acked += next->len;        tcp_seg_free(next);                         //释放被该报文占用的tcp报文段        LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"TCPWNDSIZE_F" (after freeing unacked)\n", (tcpwnd_size_t)pcb->snd_queuelen));        if (pcb->snd_queuelen != 0) {          LWIP_ASSERT("tcp_receive: valid queue length", pcb->unacked != NULL ||                      pcb->unsent != NULL);        }      }      /* 当所有满足要求的报文段移除成功后,判断unacked队列是否为空 */      if (pcb->unacked == NULL) {        pcb->rtime = -1;    //若为空,关闭重传定时器      } else {        pcb->rtime = 0;     //否则复位重传定时器      }      pcb->polltmr = 0;     //复位轮询定时器#if LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS      if (ip_current_is_v6()) {        /* Inform neighbor reachability of forward progress. */        nd6_reachability_hint(ip6_current_src_addr());      }#endif /* LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS*/      /* 如果该ACK既不是重复ACK,又不是正常ACK,则acked字段清0,即该ACK不确认任何已发送数据 */    } else {      /* Out of sequence ACK, didn't really ack anything */      tcp_send_empty_ack(pcb);    }    /* 遍历unsent队列,将所有数据编号小于等于ackno的报文段移除 */    /* 这是因为对于需要重传的报文段,lwip直接将它们挂在unsent队列上,所以收到的ACK可能是对已超时报文段的确认 */    while (pcb->unsent != NULL &&           TCP_SEQ_BETWEEN(ackno, lwip_ntohl(pcb->unsent->tcphdr->seqno) +                           TCP_TCPLEN(pcb->unsent), pcb->snd_nxt)) {      LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: removing %"U32_F":%"U32_F" from pcb->unsent\n",                                    lwip_ntohl(pcb->unsent->tcphdr->seqno), lwip_ntohl(pcb->unsent->tcphdr->seqno) +                                    TCP_TCPLEN(pcb->unsent)));      /* 将满足要求的报文从unsent链表取出 */      next = pcb->unsent;      pcb->unsent = pcb->unsent->next;#if TCP_OVERSIZE      if (pcb->unsent == NULL) {        pcb->unsent_oversize = 0;      }#endif /* TCP_OVERSIZE */      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_receive: queuelen %"TCPWNDSIZE_F" ... ", (tcpwnd_size_t)pcb->snd_queuelen));      LWIP_ASSERT("pcb->snd_queuelen >= pbuf_clen(next->p)", (pcb->snd_queuelen >= pbuf_clen(next->p)));      /* Prevent ACK for FIN to generate a sent event */      pcb->snd_queuelen -= pbuf_clen(next->p);    //释放被该报文占用的发送空间      recv_acked += next->len;      tcp_seg_free(next);                         //释放被该报文占用的tcp报文段      LWIP_DEBUGF(TCP_QLEN_DEBUG, ("%"TCPWNDSIZE_F" (after freeing unsent)\n", (tcpwnd_size_t)pcb->snd_queuelen));      if (pcb->snd_queuelen != 0) {        LWIP_ASSERT("tcp_receive: valid queue length",          pcb->unacked != NULL || pcb->unsent != NULL);      }    }    pcb->snd_buf += recv_acked;    /* End of ACK for new data processing. */    LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_receive: pcb->rttest %"U32_F" rtseq %"U32_F" ackno %"U32_F"\n",                                pcb->rttest, pcb->rtseq, ackno));    /* RTT estimation calculations. This is done by checking if the       incoming segment acknowledges the segment we use to take a       round-trip time measurement. */    /* RTT计算,暂略 */    if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno)) {      /* diff between this shouldn't exceed 32K since this are tcp timer ticks         and a round-trip shouldn't be that long... */      m = (s16_t)(tcp_ticks - pcb->rttest);      LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_receive: experienced rtt %"U16_F" ticks (%"U16_F" msec).\n",                                  m, (u16_t)(m * TCP_SLOW_INTERVAL)));      /* This is taken directly from VJs original code in his paper */      m = m - (pcb->sa >> 3);      pcb->sa += m;      if (m < 0) {        m = -m;      }      m = m - (pcb->sv >> 2);      pcb->sv += m;      pcb->rto = (pcb->sa >> 3) + pcb->sv;      LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_receive: RTO %"U16_F" (%"U16_F" milliseconds)\n",                                  pcb->rto, (u16_t)(pcb->rto * TCP_SLOW_INTERVAL)));      pcb->rttest = 0;    }  }  /* 如果该输入报文还包含了数据,则要继续对数据进行处理 */  if ((tcplen > 0) && (pcb->state < CLOSE_WAIT)) {    /* This code basically does three things:    +) If the incoming segment contains data that is the next    in-sequence data, this data is passed to the application. This    might involve trimming the first edge of the data. The rcv_nxt    variable and the advertised window are adjusted.    +) If the incoming segment has data that is above the next    sequence number expected (->rcv_nxt), the segment is placed on    the ->ooseq queue. This is done by finding the appropriate    place in the ->ooseq queue (which is ordered by sequence    number) and trim the segment in both ends if needed. An    immediate ACK is sent to indicate that we received an    out-of-sequence segment.    +) Finally, we check if the first segment on the ->ooseq queue    now is in sequence (i.e., if rcv_nxt >= ooseq->seqno). If    rcv_nxt > ooseq->seqno, we must trim the first edge of the    segment on ->ooseq before we adjust rcv_nxt. The data in the    segments that are now on sequence are chained onto the    incoming segment so that we only need to call the application    once.    */    /* First, we check if we must trim the first edge. We have to do       this if the sequence number of the incoming segment is less       than rcv_nxt, and the sequence number plus the length of the       segment is larger than rcv_nxt. */    /*    if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)) {          if (TCP_SEQ_LT(pcb->rcv_nxt, seqno + tcplen)) {*/  /* 如果seqno + 1 <= rcv_nxt <= seqno + tcplen - 1,   * 意味着收到的数据区域头部有无效数据   *(收到的数据有部分处于本地左侧接收窗口外),需要截断数据头 */    if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1)) {      /* Trimming the first edge is done by pushing the payload         pointer in the pbuf downwards. This is somewhat tricky since         we do not want to discard the full contents of the pbuf up to         the new starting point of the data since we have to keep the         TCP header which is present in the first pbuf in the chain.         What is done is really quite a nasty hack: the first pbuf in         the pbuf chain is pointed to by inseg.p. Since we need to be         able to deallocate the whole pbuf, we cannot change this         inseg.p pointer to point to any of the later pbufs in the         chain. Instead, we point the ->payload pointer in the first         pbuf to data in one of the later pbufs. We also set the         inseg.data pointer to point to the right place. This way, the         ->p pointer will still point to the first pbuf, but the         ->p->payload pointer will point to data in another pbuf.         After we are done with adjusting the pbuf pointers we must         adjust the ->data pointer in the seg and the segment         length.*/      struct pbuf *p = inseg.p;                   //获取收到的报文段的pbuf链表头      off = pcb->rcv_nxt - seqno;                 //需要截掉的数据长度      LWIP_ASSERT("inseg.p != NULL", inseg.p);      LWIP_ASSERT("insane offset!", (off < 0x7fff));      /* 判断需要截断的长度是否超出了第一个pbuf中存储的数据长度 */      if (inseg.p->len < off) {        LWIP_ASSERT("pbuf too short!", (((s32_t)inseg.p->tot_len) >= off));        new_tot_len = (u16_t)(inseg.p->tot_len - off);    //截断重复数据后的有效数据长度        /* 如果超出,则需要遍历pbuf链表,依次摘除数据,直到最后一个包含摘除数据的pbuf */        while (p->len < off) {          off -= p->len;                //剩余摘除长度          /* KJM following line changed (with addition of new_tot_len var)             to fix bug #9076             inseg.p->tot_len -= p->len; */          p->tot_len = new_tot_len;    //更新当前pbuf中的数据总长,          p->len = 0;                  //因为数据被摘除,所以当前pbuf中的数据分长清0          p = p->next;                 //指向下一个pbuf        }        /* 处理最后一个包含摘除数据的pbuf,就是调整数据指针略过摘除数据 */        if (pbuf_header(p, (s16_t)-off)) {          /* Do we need to cope with this failing?  Assert for now */          LWIP_ASSERT("pbuf_header failed", 0);        }      } else {        /* 如果未超出,则调整第一个pbuf中的数据指针略过摘除数据 */        if (pbuf_header(inseg.p, (s16_t)-off)) {          /* Do we need to cope with this failing?  Assert for now */          LWIP_ASSERT("pbuf_header failed", 0);        }      }      inseg.len -= (u16_t)(pcb->rcv_nxt - seqno);    //更新TCP报文段数据总长      inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;    //更新TCP头中的seqno,指向接收窗口头位置    }    else {    /* 如果seqno < rcv_nxt,意味着seqno+tcplen-1 < rcv_nxt,说明这是个完全重复的报文段 */      if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)) {        /* the whole segment is < rcv_nxt */        /* must be a duplicate of a packet that has already been correctly handled */        LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: duplicate seqno %"U32_F"\n", seqno));        tcp_ack_now(pcb);    //只回复一个ACK给对方(这里是否应该直接返回不再运行下去)      }    }    /* 如果数据起始编号在接收窗口内 */    if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt,                        pcb->rcv_nxt + pcb->rcv_wnd - 1)) {      /* 如果该报文数据处于接收起始位置,意味着该报文是连续到来的 */      if (pcb->rcv_nxt == seqno) {        /* The incoming segment is the next in sequence. We check if           we have to trim the end of the segment and update rcv_nxt           and pass the data to the application. */        tcplen = TCP_TCPLEN(&inseg);    //更新该报文的总数据长度        /* 如果总长大于接收窗口大小,就需要做尾部截断处理,         * 这里包含对FIN和SYN两种标志的不同处理结果,注意体会 */        if (tcplen > pcb->rcv_wnd) {          LWIP_DEBUGF(TCP_INPUT_DEBUG,                      ("tcp_receive: other end overran receive window"                       "seqno %"U32_F" len %"U16_F" right edge %"U32_F"\n",                       seqno, tcplen, pcb->rcv_nxt + pcb->rcv_wnd));          /* 如果TCP头中带FIN标志,清除FIN标志,因为对方还有数据要发过来 */          if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {            /* Must remove the FIN from the header as we're trimming             * that byte of sequence-space from the packet */            TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) & ~(unsigned int)TCP_FIN);          }          /* Adjust length of segment to fit in the window. */          TCPWND_CHECK16(pcb->rcv_wnd);          inseg.len = (u16_t)pcb->rcv_wnd;    //根据接收窗口调整数据长度          /* 如果TCP头中带SYN标志,报文段数据长度减1 */          if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {            inseg.len -= 1;          }          pbuf_realloc(inseg.p, inseg.len);    //因为数据被截断,pbuf中的参数需要相应调整          tcplen = TCP_TCPLEN(&inseg);         //再次更新该报文的总数据长度          LWIP_ASSERT("tcp_receive: segment not trimmed correctly to rcv_wnd\n",                      (seqno + tcplen) == (pcb->rcv_nxt + pcb->rcv_wnd));        }#if TCP_QUEUE_OOSEQ        /* Received in-sequence data, adjust ooseq data if:           - FIN has been received or           - inseq overlaps with ooseq */        /* 如果无序报文段队列ooseq上存在报文段 */        if (pcb->ooseq != NULL) {          /* 判断当前有序报文段的TCP头中是否带FIN标志 */          if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {            LWIP_DEBUGF(TCP_INPUT_DEBUG,                        ("tcp_receive: received in-order FIN, binning ooseq queue\n"));            /* Received in-order FIN means anything that was received             * out of order must now have been received in-order, so             * bin the ooseq queue */            /* 如果该有序报文段带FIN标志,意味着单向TCP连接结束 */            /* 不可能再从对方收到新的报文段,ooseq队列中的报文段没有成为有序报文段可能,只能作废 */            while (pcb->ooseq != NULL) {              struct tcp_seg *old_ooseq = pcb->ooseq;              pcb->ooseq = pcb->ooseq->next;              tcp_seg_free(old_ooseq);            }          } else {            next = pcb->ooseq;            /* Remove all segments on ooseq that are covered by inseg already.             * FIN is copied from ooseq to inseg if present. */            /* 遍历ooseq链表,删除序号被当前有序报文段完全覆盖的报文段 */            while (next &&                   TCP_SEQ_GEQ(seqno + tcplen,                               next->tcphdr->seqno + next->len)) {              /* inseg cannot have FIN here (already processed above) */            /* 如果这些即将被删除的报文段带FIN标志且当前有序报文段不带SYN标志 */              if ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0 &&                  (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) == 0) {                TCPH_SET_FLAG(inseg.tcphdr, TCP_FIN);    //在当前有效报文段的TCP头中添加FIN标志                tcplen = TCP_TCPLEN(&inseg);             //再次更新该报文的总数据长度              }              prev = next;              next = next->next;              tcp_seg_free(prev);            }            /* Now trim right side of inseg if it overlaps with the first             * segment on ooseq */            /* 如果当前有序报文段尾部与ooseq中的报文段存在部分重叠   */            if (next &&                TCP_SEQ_GT(seqno + tcplen,                           next->tcphdr->seqno)) {              /* inseg cannot have FIN here (already processed above) */              inseg.len = (u16_t)(next->tcphdr->seqno - seqno);    //截断当前有序报文段尾部的重叠部分,得到有效部分长度              /* 如果当前有序报文段TCP头中带SYN标志,报文段数据长度减1 */              if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) {                inseg.len -= 1;              }              pbuf_realloc(inseg.p, inseg.len);    //因为数据被截断,pbuf中的参数需要相应调整              tcplen = TCP_TCPLEN(&inseg);         //再次更新该报文的总数据长度              LWIP_ASSERT("tcp_receive: segment not trimmed correctly to ooseq queue\n",                          (seqno + tcplen) == next->tcphdr->seqno);            }            pcb->ooseq = next;          }        }#endif /* TCP_QUEUE_OOSEQ */        pcb->rcv_nxt = seqno + tcplen;    //更新下一个期望接收到的序号,也就是接收窗口左边界        /* Update the receiver's (our) window. */        LWIP_ASSERT("tcp_receive: tcplen > rcv_wnd\n", pcb->rcv_wnd >= tcplen);        pcb->rcv_wnd -= tcplen;           //更新当前可用接收窗口        tcp_update_rcv_ann_wnd(pcb);      //更新公告窗口        /* If there is data in the segment, we make preparations to           pass this up to the application. The ->recv_data variable           is used for holding the pbuf that goes to the           application. The code for reassembling out-of-sequence data           chains its data on this pbuf as well.           If the segment was a FIN, we set the TF_GOT_FIN flag that will           be used to indicate to the application that the remote side has           closed its end of the connection. */        /* 如果该有序报文段中存在数据 */        if (inseg.p->tot_len > 0) {          recv_data = inseg.p;    //将全局指针recv_data指向报文段中的数据pbuf          /* Since this pbuf now is the responsibility of the             application, we delete our reference to it so that we won't             (mistakingly) deallocate it. */          inseg.p = NULL;        }        /* 如果该有序报文段的TCP头中带FIN标志 */        if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN) {          LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: received FIN.\n"));          recv_flags |= TF_GOT_FIN;    //则在报文处理结果变量recv_flags添加TF_GOT_FIN标志        }#if TCP_QUEUE_OOSEQ        /* We now check if we have segments on the ->ooseq queue that           are now in sequence. */        /* 遍历ooseq队列,取出所有有序的报文段 */        /* (通过比较ooseq队列中报文段的seqno和当前TCP控制块中保存的rcv_nxt来判定该报文段是否有序) */        while (pcb->ooseq != NULL &&               pcb->ooseq->tcphdr->seqno == pcb->rcv_nxt) {          cseg = pcb->ooseq;          seqno = pcb->ooseq->tcphdr->seqno;    //更新序号          pcb->rcv_nxt += TCP_TCPLEN(cseg);     //更新下一个期望接收到的序号          LWIP_ASSERT("tcp_receive: ooseq tcplen > rcv_wnd\n", pcb->rcv_wnd >= TCP_TCPLEN(cseg));          pcb->rcv_wnd -= TCP_TCPLEN(cseg);     //更新当前可用接收窗口          tcp_update_rcv_ann_wnd(pcb);    /* 更新公告窗口 */          /* 如果该有序报文段中存在数据,则通过全局指针recv_data向上层提交数据 */          if (cseg->p->tot_len > 0) {            /* Chain this pbuf onto the pbuf that we will pass to               the application. */            /* With window scaling, this can overflow recv_data->tot_len, but               that's not a problem since we explicitly fix that before passing               recv_data to the application. */          /* 判断全局指针recv_data是否为空 */            if (recv_data) {              pbuf_cat(recv_data, cseg->p);    //如果不为空,意味着有更早的数据准备向上提交            } else {              recv_data = cseg->p;             //如果为空,直接将当前数据pbuf赋给recv_data            }            cseg->p = NULL;          }          /* 如果该有序报文段的TCP头中带FIN标志 */          if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN) {            LWIP_DEBUGF(TCP_INPUT_DEBUG, ("tcp_receive: dequeued FIN.\n"));            recv_flags |= TF_GOT_FIN;    //则全局变量recv_flags添加TF_GOT_FIN标志            /* 如果当前TCP处于ESTABLISHED状态,则变成CLOSE_WAIT状态 */            if (pcb->state == ESTABLISHED) { /* force passive close or we can move to active close */              pcb->state = CLOSE_WAIT;            }          }          pcb->ooseq = cseg->next;          tcp_seg_free(cseg);        }#endif /* TCP_QUEUE_OOSEQ */        /* Acknowledge the segment(s). */        /* 以上都执行完毕后,向源端返回一个ACK,此处其实只是先在TCP控制块中添加ACK标志 */        tcp_ack(pcb);#if LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS        if (ip_current_is_v6()) {          /* Inform neighbor reachability of forward progress. */          nd6_reachability_hint(ip6_current_src_addr());        }#endif /* LWIP_IPV6 && LWIP_ND6_TCP_REACHABILITY_HINTS*/      /* 如果该报文数据不处于接收起始位置,意味着该报文不是有序的 */      } else {        /* We get here if the incoming segment is out-of-sequence. */      /* 首先向源端返回一个立即ACK */        tcp_send_empty_ack(pcb);#if TCP_QUEUE_OOSEQ        /* We queue the segment on the ->ooseq queue. */        /* 然后将该报文段放入ooseq队列 */        if (pcb->ooseq == NULL) {          /* 如果ooseq为空,则拷贝该报文段到新开辟的报文段空间,           * 并将新开辟报文段作为ooseq起始单元 */          pcb->ooseq = tcp_seg_copy(&inseg);        } else {          /* If the queue is not empty, we walk through the queue and             try to find a place where the sequence number of the             incoming segment is between the sequence numbers of the             previous and the next segment on the ->ooseq queue. That is             the place where we put the incoming segment. If needed, we             trim the second edges of the previous and the incoming             segment so that it will fit into the sequence.             If the incoming segment has the same sequence number as a             segment on the ->ooseq queue, we discard the segment that             contains less data. */          prev = NULL;    //定义为ooseq链表中上一个报文段,这里首先清空          /* 遍历ooseq队列,选择合适位置插入该报文段 */          for (next = pcb->ooseq; next != NULL; next = next->next) {            /* 依次比较两个报文段的起始序号seqno,如果相等 */            if (seqno == next->tcphdr->seqno) {              /* The sequence number of the incoming segment is the                 same as the sequence number of the segment on                 ->ooseq. We check the lengths to see which one to                 discard. */              /* 继续比较两个报文段的数据长度 */              if (inseg.len > next->len) {                /* The incoming segment is larger than the old                   segment. We replace some segments with the new                   one. */            /* 如果输入报文段数据长度更长 */            /* 拷贝该报文段到新开辟的报文段空间 */                cseg = tcp_seg_copy(&inseg);                /* 插入ooseq链表 */                if (cseg != NULL) {                  /* 如果不是ooseq上的第一个报文段 */                  if (prev != NULL) {                    prev->next = cseg;    //插入ooseq链表的上一个报文段之后                  /* 如果是第一个 */                  } else {                    pcb->ooseq = cseg;    //直接替换原有的第一个                  }                  /* 处理好插入后与原有的下一个报文段的影响,简单来说,就是切掉冗余,释放内存 */                  tcp_oos_insert_segment(cseg, next);                }                break;    //退出循环              } else {                /* Either the lengths are the same or the incoming                   segment was smaller than the old one; in either                   case, we ditch the incoming segment. */              /* 如果输入报文段数据长度更短,则直接丢弃,并退出循环 */                break;              }              /* 如果不相等 */            } else {              /* 如果是ooseq上的第一个报文段 */              if (prev == NULL) {                /* 如果该报文段的起始序号大于要插入的报文段起始序号 */                if (TCP_SEQ_LT(seqno, next->tcphdr->seqno)) {                  /* The sequence number of the incoming segment is lower                     than the sequence number of the first segment on the                     queue. We put the incoming segment first on the                     queue. */                  /* 拷贝要插入的报文段到新开辟的报文段空间 */                  cseg = tcp_seg_copy(&inseg);                  if (cseg != NULL) {                    pcb->ooseq = cseg;    //将新报文段插到ooseq第一个位置                    tcp_oos_insert_segment(cseg, next);    //处理好插入后与原有的第一个报文段的影响                  }                  break;    //退出循环                }              /* 如果不是第一个 */              } else {                /*if (TCP_SEQ_LT(prev->tcphdr->seqno, seqno) &&                  TCP_SEQ_LT(seqno, next->tcphdr->seqno)) {*/            /* 如果待插入报文段起始序号在前一个和后一个报文段起始序号之间 */                if (TCP_SEQ_BETWEEN(seqno, prev->tcphdr->seqno+1, next->tcphdr->seqno-1)) {                  /* The sequence number of the incoming segment is in                     between the sequence numbers of the previous and                     the next segment on ->ooseq. We trim trim the previous                     segment, delete next segments that included in received segment                     and trim received, if needed. */                  cseg = tcp_seg_copy(&inseg);    //拷贝要插入的报文段到新开辟的报文段空间                  if (cseg != NULL) {                    /* 如果与前一个报文段有数据重合 */                    if (TCP_SEQ_GT(prev->tcphdr->seqno + prev->len, seqno)) {                      /* We need to trim the prev segment. */                      prev->len = (u16_t)(seqno - prev->tcphdr->seqno);    //截断前一个报文段尾部                      pbuf_realloc(prev->p, prev->len);    //因为数据被截断,pbuf中的参数需要相应调整                    }                    prev->next = cseg;                     //将新报文段插入前一个报文段之后                    tcp_oos_insert_segment(cseg, next);    //处理好插入后与原有的下一个报文段的影响                  }                  break;                }              }              /* If the "next" segment is the last segment on the                 ooseq queue, we add the incoming segment to the end                 of the list. */              /* 如果已经是ooseq上的最后一个报文段 */              /* 且待插入的报文段起始序号大于该报文起始序号(其实函数运行到这里该条件必然成立) */              if (next->next == NULL &&                  TCP_SEQ_GT(seqno, next->tcphdr->seqno)) {                /* 如果该报文的TCP头中有FIN标志,则直接丢弃待插入的报文段,退出循环 */                if (TCPH_FLAGS(next->tcphdr) & TCP_FIN) {                  /* segment "next" already contains all data */                  break;                }                next->next = tcp_seg_copy(&inseg);    //拷贝要插入的报文段到新开辟的报文段空间,并插在队列尾部                /* 如果新插入的报文段不为空 */                if (next->next != NULL) {                  /* 如果与前一个报文段有数据重合 */                  if (TCP_SEQ_GT(next->tcphdr->seqno + next->len, seqno)) {                    /* We need to trim the last segment. */                    next->len = (u16_t)(seqno - next->tcphdr->seqno);    //截断前一个报文段尾部                    pbuf_realloc(next->p, next->len);    //因为数据被截断,pbuf中的参数需要相应调整                  }                  /* check if the remote side overruns our receive window */                  /* 如果新插入的报文段数据长度超出了当前接收窗口大小 */                  if (TCP_SEQ_GT((u32_t)tcplen + seqno, pcb->rcv_nxt + (u32_t)pcb->rcv_wnd)) {                    LWIP_DEBUGF(TCP_INPUT_DEBUG,                                ("tcp_receive: other end overran receive window"                                 "seqno %"U32_F" len %"U16_F" right edge %"U32_F"\n",                                 seqno, tcplen, pcb->rcv_nxt + pcb->rcv_wnd));                    /* 如果新插入的报文段的TCP头中有FIN标志 */                    if (TCPH_FLAGS(next->next->tcphdr) & TCP_FIN) {                      /* Must remove the FIN from the header as we're trimming                       * that byte of sequence-space from the packet */                      /* 去掉TCP头中的FIN标志 */                      TCPH_FLAGS_SET(next->next->tcphdr, TCPH_FLAGS(next->next->tcphdr) & ~TCP_FIN);                    }                    /* Adjust length of segment to fit in the window. */                    /* 根据接收窗口大小调制新插入的报文段数据长度 */                    next->next->len = (u16_t)(pcb->rcv_nxt + pcb->rcv_wnd - seqno);                    /* 因为数据被截断,pbuf中的参数需要相应调整 */                    pbuf_realloc(next->next->p, next->next->len);                    /* 再次更新该报文的总数据长度 */                    tcplen = TCP_TCPLEN(next->next);                    LWIP_ASSERT("tcp_receive: segment not trimmed correctly to rcv_wnd\n",                                (seqno + tcplen) == (pcb->rcv_nxt + pcb->rcv_wnd));                  }                }                break;              }            }            prev = next;    //以上都不满足,则遍历ooseq链表中下一个          }        }#if TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS        /* Check that the data on ooseq doesn't exceed one of the limits           and throw away everything above that limit. */        ooseq_blen = 0;        ooseq_qlen = 0;        prev = NULL;        for (next = pcb->ooseq; next != NULL; prev = next, next = next->next) {          struct pbuf *p = next->p;          ooseq_blen += p->tot_len;          ooseq_qlen += pbuf_clen(p);          if ((ooseq_blen > TCP_OOSEQ_MAX_BYTES) ||              (ooseq_qlen > TCP_OOSEQ_MAX_PBUFS)) {             /* too much ooseq data, dump this and everything after it */             tcp_segs_free(next);             if (prev == NULL) {               /* first ooseq segment is too much, dump the whole queue */               pcb->ooseq = NULL;             } else {               /* just dump 'next' and everything after it */               prev->next = NULL;             }             break;          }        }#endif /* TCP_OOSEQ_MAX_BYTES || TCP_OOSEQ_MAX_PBUFS */#endif /* TCP_QUEUE_OOSEQ */      }    /* 如果数据不在接收范围内 */    } else {      /* The incoming segment is not within the window. */      /* 直接向源端返回一个立即确认ACK */      tcp_send_empty_ack(pcb);    }  /* 如果输入的报文段中不包含数据 */  } else {    /* Segments with length 0 is taken care of here. Segments that       fall out of the window are ACKed. *//* 且序号位于接收窗口之内 */    if (!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1)) {      tcp_ack_now(pcb);    // 回一个ACK    }  }}


原创粉丝点击