lwip tcp_output源码解析

原型:

err_t
tcp_output(struct tcp_pcb *pcb)

说明:

找到能发送的数据-->发送

函数可能将某个连接的pcb控制块 字段unsent队列上的报文段发送出去,或者只发送一个ACK报文段。

流程:

如果调用该函数时,pcb的flags字段

TF_ACK_NOW标志置位并且没有数据发送,构建一个空的ACK段然后发送(原因:或者因为->unsent 队列是空或者窗口不允许);

  /* If the TF_ACK_NOW flag is set and no data will be sent (either==如TF_ACK_NOW标志置位并且没有数据发送
   * because the ->unsent queue is empty or because the window does==(或者因为->unsent 队列是空或者窗口不允许)
   * not allow it), construct an empty ACK segment and send it.构建一个空的ACK段然后发送
   *
   * If data is to be sent, we will just piggyback the ACK (see below).==如果要发送数据,我们会捎带ACK(见下文)
   */
  if (pcb->flags & TF_ACK_NOW &&
     (seg == NULL ||//unsent 队列是空
      ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {/*窗口不允许*/
     return tcp_send_empty_ack(pcb);
  }

如果要发送数据,我们会捎带ACK,将unsent队列的第一个报文段发送(见下文)。

unsent 队列不为空 并且 窗口允许,发送带ACK的报文

  /* data available and window allows it to be sent? ==数据和窗口允许发送吗?*/
  while (seg != NULL &&
         ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {

期间,有这样一过程:如果发送出去的报文段长度不为0,或者带有SYN、FIN,则放入未确认队列unacked,以便超时重传。若当前的unacked队列非空,需要把当前的报文按顺序组织在队列中,处理如下:

/* put segment on unacknowledged list if length > 0  如果发送出去的报文段长度不为0,或者带有SYN、FIN,则放入未确认队列unacked,以便超时重传*/
    if (TCP_TCPLEN(seg) > 0) {
      seg->next = NULL;//清空报文段 next字段
      /* unacked list is empty? */
      if (pcb->unacked == NULL) {//unacked为空
        pcb->unacked = seg;//直接对接到,即尾部
        useg = seg;//useg指向unacked队尾
      /* unacked list is not empty? ==unacked非空,需要把当前的报文按顺序组织在队列中*/
      } else {
        /* In the case of fast retransmit, the packet should not go to the tail==在快速重传情况下,包不应该插入unacked的队尾
         * of the unacked queue, but rather somewhere before it. We need to check for==而是之前的某个地方,
         * this case. -STJ Jul 27, 2004 ==这种情况需要检查*/
        if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))) {//成立
          /* add segment to before tail of unacked list, keeping the list sorted ==查找合适的位置插入,以保持排序*/
          struct tcp_seg **cur_seg = &(pcb->unacked);
          while (*cur_seg &&
            TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {
              cur_seg = &((*cur_seg)->next );//找到插入位置,插入
          }
          seg->next = (*cur_seg);
          (*cur_seg) = seg;
        } else {
          /* add segment to tail of unacked list ==直接放入队尾便是*/
          useg->next = seg;
          useg = useg->next;
        }
      }
    /* do not queue empty segments on the unacked list */
    } else {
      tcp_seg_free(seg);//报文长度0,不需要重传,删除
    }


如果发送窗口被填满,导致不能发送,则启动零窗口探测,来保证能够准确的收到接收方的窗口通告:

if (seg != NULL && pcb->persist_backoff == 0 && /*如果发送窗口被填满,导致不能发送,则启动零窗口探测,来保证能够准确的收到接收方的窗口通告*/
      ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > pcb->snd_wnd) {
    /* prepare for persist timer==准备 坚持定时器*/
    pcb->persist_cnt = 0;
    pcb->persist_backoff = 1;
  }

完整的源码如下:

/**
 * Find out what we can send and send it==找到能发送的报文 发送
 *
 * @param pcb Protocol control block for the TCP connection to send data
 * @return ERR_OK if data has been sent or nothing to send
 *         another err_t on error
 */
err_t
tcp_output(struct tcp_pcb *pcb)
{
  struct tcp_seg *seg, *useg;
  u32_t wnd, snd_nxt;
#if TCP_CWND_DEBUG
  s16_t i = 0;
#endif /* TCP_CWND_DEBUG */

  /* First, check if we are invoked by the TCP input processing==首先,检查我们是否被调用通过TCP输入处理代码
     code. If so, we do not output anything. Instead, we rely on the==如果这样子,什么都不做
     input processing code to call us when input processing is done
     with. 相反,我们依靠输入处理代码调用我们 当输入处理完成时*/
  if (tcp_input_pcb == pcb) {
    return ERR_OK;
  }

  wnd = LWIP_MIN(pcb->snd_wnd, pcb->cwnd);

  seg = pcb->unsent;//未发送的报文段队列

  /* If the TF_ACK_NOW flag is set and no data will be sent (either==如TF_ACK_NOW标志置位并且没有数据发送
   * because the ->unsent queue is empty or because the window does==(或者因为->unsent 队列是空或者窗口不允许)
   * not allow it), construct an empty ACK segment and send it.构建一个空的ACK段然后发送
   *
   * If data is to be sent, we will just piggyback the ACK (see below).==如果要发送数据,我们会捎带ACK(见下文)
   */
  if (pcb->flags & TF_ACK_NOW &&
     (seg == NULL ||//unsent 队列是空
      ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > wnd)) {/*窗口不允许*/
     return tcp_send_empty_ack(pcb);
  }

  /* useg should point to last segment on unacked queue== 》useg 应该指向unacked队列上的最后一段(队尾) */
  useg = pcb->unacked;
  if (useg != NULL) {//遍历 找尾巴
    for (; useg->next != NULL; useg = useg->next);
  }

#if TCP_OUTPUT_DEBUG
  if (seg == NULL) {
    LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_output: nothing to send (%p)\n",
                                   (void*)pcb->unsent));
  }
#endif /* TCP_OUTPUT_DEBUG */
#if TCP_CWND_DEBUG
  if (seg == NULL) {
    LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"U16_F
                                 ", cwnd %"U16_F", wnd %"U32_F
                                 ", seg == NULL, ack %"U32_F"\n",
                                 pcb->snd_wnd, pcb->cwnd, wnd, pcb->lastack));
  } else {
    LWIP_DEBUGF(TCP_CWND_DEBUG, 
                ("tcp_output: snd_wnd %"U16_F", cwnd %"U16_F", wnd %"U32_F
                 ", effwnd %"U32_F", seq %"U32_F", ack %"U32_F"\n",
                 pcb->snd_wnd, pcb->cwnd, wnd,
                 ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len,
                 ntohl(seg->tcphdr->seqno), pcb->lastack));
  }
#endif /* TCP_CWND_DEBUG */
  /* data available and window allows it to be sent? ==数据和窗口允许发送吗?*/
  while (seg != NULL &&
         ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len <= wnd) {
    LWIP_ASSERT("RST not expected here!", 
                (TCPH_FLAGS(seg->tcphdr) & TCP_RST) == 0);
    /* Stop sending if the nagle algorithm would prevent it==如nagle算法阻止,则停止发送
     * Don't stop://不停下来的条件
     * - if tcp_write had a memory error before (prevent delayed ACK timeout) or
     * - if FIN was already enqueued for this PCB (SYN is always alone in a segment -
     *   either seg->next != NULL or pcb->unacked == NULL;
     *   RST is no sent using tcp_write/tcp_output.
     */
    if((tcp_do_output_nagle(pcb) == 0) &&
      ((pcb->flags & (TF_NAGLEMEMERR | TF_FIN)) == 0)){
      break;
    }
#if TCP_CWND_DEBUG
    LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_output: snd_wnd %"U16_F", cwnd %"U16_F", wnd %"U32_F", effwnd %"U32_F", seq %"U32_F", ack %"U32_F", i %"S16_F"\n",
                            pcb->snd_wnd, pcb->cwnd, wnd,
                            ntohl(seg->tcphdr->seqno) + seg->len -
                            pcb->lastack,
                            ntohl(seg->tcphdr->seqno), pcb->lastack, i));
    ++i;
#endif /* TCP_CWND_DEBUG */

    pcb->unsent = seg->next;//pcb->unsent指向下一个节点==从发送缓冲队列中删除当前的

    if (pcb->state != SYN_SENT) {
      TCPH_SET_FLAG(seg->tcphdr, TCP_ACK);//填充首部中的ACK标志
      pcb->flags &= ~(TF_ACK_DELAY | TF_ACK_NOW);//清除标志位
    }

    tcp_output_segment(seg, pcb);//发送报文段==》递交给IP层
    snd_nxt = ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg);/*计算snd_nxt==下一个将要发送的数据序号*/
    if (TCP_SEQ_LT(pcb->snd_nxt, snd_nxt)) {/*更新下一个将要发送的数据序号*/
      pcb->snd_nxt = snd_nxt;
    }
    /* put segment on unacknowledged list if length > 0  如果发送出去的报文段长度不为0,或者带有SYN、FIN,则放入未确认队列unacked,以便超时重传*/
    if (TCP_TCPLEN(seg) > 0) {//不为零
      seg->next = NULL;//清空报文段 next字段
      /* unacked list is empty? */
      if (pcb->unacked == NULL) {//unacked为空
        pcb->unacked = seg;//直接对接到,即尾部
        useg = seg;//useg指向unacked队尾
      /* unacked list is not empty? ==unacked非空,需要把当前的报文按顺序组织在队列中*/
      } else {
        /* In the case of fast retransmit, the packet should not go to the tail==在快速重传情况下,包不应该插入unacked的队尾
         * of the unacked queue, but rather somewhere before it. We need to check for==而是之前的某个地方,
         * this case. -STJ Jul 27, 2004 ==这种情况需要检查*/
        if (TCP_SEQ_LT(ntohl(seg->tcphdr->seqno), ntohl(useg->tcphdr->seqno))) {//成立
          /* add segment to before tail of unacked list, keeping the list sorted ==查找合适的位置插入,以保持排序*/
          struct tcp_seg **cur_seg = &(pcb->unacked);
          while (*cur_seg &&
            TCP_SEQ_LT(ntohl((*cur_seg)->tcphdr->seqno), ntohl(seg->tcphdr->seqno))) {
              cur_seg = &((*cur_seg)->next );//找到插入位置,插入
          }
          seg->next = (*cur_seg);
          (*cur_seg) = seg;
        } else {//报文长度位0
          /* add segment to tail of unacked list ==直接放入队尾便是*/
          useg->next = seg;
          useg = useg->next;
        }
      }
    /* do not queue empty segments on the unacked list */
    } else {
      tcp_seg_free(seg);//报文长度0,不需要重传,删除
    }
    seg = pcb->unsent;//下一枚
  }
#if TCP_OVERSIZE
  if (pcb->unsent == NULL) {
    /* last unsent has been removed, reset unsent_oversize */
    pcb->unsent_oversize = 0;
  }
#endif /* TCP_OVERSIZE */

  if (seg != NULL && pcb->persist_backoff == 0 && /*如果发送窗口被填满,导致不能发送,则启动零窗口探测,来保证能够准确的收到接收方的窗口通告*/
      ntohl(seg->tcphdr->seqno) - pcb->lastack + seg->len > pcb->snd_wnd) {
    /* prepare for persist timer==准备 坚持定时器*/
    pcb->persist_cnt = 0;
    pcb->persist_backoff = 1;
  }

  pcb->flags &= ~TF_NAGLEMEMERR;
  return ERR_OK;
}

总结:

由上诉可知:tcp_output只是检查某个报文是否满足被发送的条件,然后调用

tcp_output_segment函数将报文段发送出去,该函数需要填写报文中剩下的几个必要字段,之后调用IP层输出函数ip_output发送报文。

tcp_output_segment源码见下章。

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值