lwip tcp_output源码解析

来源:互联网 发布:米尔格伦实验 知乎 编辑:程序博客网 时间:2024/05/22 17:26

原型:

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_ttcp_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 0
原创粉丝点击