lwip tcp_output源码解析

2019-07-14 12:32发布

原型: 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) ", (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" ", 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" ", 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" ", 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源码见下章。