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