📄 tcp_out.c
字号:
#if LWIP_NETIF_TX_SINGLE_PBUF
/* Always copy to try to create single pbufs for TX */
apiflags |= TCP_WRITE_FLAG_COPY;
#endif /* LWIP_NETIF_TX_SINGLE_PBUF */
LWIP_DEBUGF(TCP_OUTPUT_DEBUG, ("tcp_write(pcb=%p, data=%p, len=%"U16_F", apiflags=%"U16_F")\n",
(void *)pcb, arg, len, (u16_t)apiflags));
LWIP_ERROR("tcp_write: arg == NULL (programmer violates API)",
arg != NULL, return ERR_ARG;);
err = tcp_write_checks(pcb, len);
if (err != ERR_OK) {
return err;
}
queuelen = pcb->snd_queuelen;
#if LWIP_TCP_TIMESTAMPS
if ((pcb->flags & TF_TIMESTAMP)) {
optflags = TF_SEG_OPTS_TS;
optlen = LWIP_TCP_OPT_LENGTH(TF_SEG_OPTS_TS);
}
#endif /* LWIP_TCP_TIMESTAMPS */
/*
* TCP segmentation is done in three phases with increasing complexity:
*
* 1. Copy data directly into an oversized pbuf.
* 2. Chain a new pbuf to the end of pcb->unsent.
* 3. Create new segments.
*
* We may run out of memory at any point. In that case we must
* return ERR_MEM and not change anything in pcb. Therefore, all
* changes are recorded in local variables and committed at the end
* of the function. Some pcb fields are maintained in local copies:
*
* queuelen = pcb->snd_queuelen
* oversize = pcb->unsent_oversize
*
* These variables are set consistently by the phases:
*
* seg points to the last segment tampered with.
*
* pos records progress as data is segmented.
*/
/* Find the tail of the unsent queue. */
if (pcb->unsent != NULL) {
u16_t space;
u16_t unsent_optlen;
/* @todo: this could be sped up by keeping last_unsent in the pcb */
for (last_unsent = pcb->unsent; last_unsent->next != NULL;
last_unsent = last_unsent->next);
/* Usable space at the end of the last unsent segment */
unsent_optlen = LWIP_TCP_OPT_LENGTH(last_unsent->flags);
space = pcb->mss - (last_unsent->len + unsent_optlen);
/*
* Phase 1: Copy data directly into an oversized pbuf.
*
* The number of bytes copied is recorded in the oversize_used
* variable. The actual copying is done at the bottom of the
* function.
*/
#if TCP_OVERSIZE
#if TCP_OVERSIZE_DBGCHECK
/* check that pcb->unsent_oversize matches last_unsent->unsent_oversize */
LWIP_ASSERT("unsent_oversize mismatch (pcb vs. last_unsent)",
pcb->unsent_oversize == last_unsent->oversize_left);
#endif /* TCP_OVERSIZE_DBGCHECK */
oversize = pcb->unsent_oversize;
if (oversize > 0) {
LWIP_ASSERT("inconsistent oversize vs. space", oversize_used <= space);
seg = last_unsent;
oversize_used = oversize < len ? oversize : len;
pos += oversize_used;
oversize -= oversize_used;
space -= oversize_used;
}
/* now we are either finished or oversize is zero */
LWIP_ASSERT("inconsistend oversize vs. len", (oversize == 0) || (pos == len));
#endif /* TCP_OVERSIZE */
/*
* Phase 2: Chain a new pbuf to the end of pcb->unsent.
*
* We don't extend segments containing SYN/FIN flags or options
* (len==0). The new pbuf is kept in concat_p and pbuf_cat'ed at
* the end.
*/
if ((pos < len) && (space > 0) && (last_unsent->len > 0)) {
u16_t seglen = space < len - pos ? space : len - pos;
seg = last_unsent;
/* Create a pbuf with a copy or reference to seglen bytes. We
* can use PBUF_RAW here since the data appears in the middle of
* a segment. A header will never be prepended. */
if (apiflags & TCP_WRITE_FLAG_COPY) {
/* Data is copied */
if ((concat_p = tcp_pbuf_prealloc(PBUF_RAW, seglen, space, &oversize, pcb, apiflags, 1)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n",
seglen));
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
last_unsent->oversize_left = oversize;
#endif /* TCP_OVERSIZE_DBGCHECK */
TCP_DATA_COPY2(concat_p->payload, (u8_t*)arg + pos, seglen, &concat_chksum, &concat_chksum_swapped);
#if TCP_CHECKSUM_ON_COPY
concat_chksummed += seglen;
#endif /* TCP_CHECKSUM_ON_COPY */
} else {
/* Data is not copied */
if ((concat_p = pbuf_alloc(PBUF_RAW, seglen, PBUF_ROM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2,
("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
/* calculate the checksum of nocopy-data */
tcp_seg_add_chksum(~inet_chksum((u8_t*)arg + pos, seglen), seglen,
&concat_chksum, &concat_chksum_swapped);
concat_chksummed += seglen;
#endif /* TCP_CHECKSUM_ON_COPY */
/* reference the non-volatile payload data */
concat_p->payload = (u8_t*)arg + pos;
}
pos += seglen;
queuelen += pbuf_clen(concat_p);
}
} else {
#if TCP_OVERSIZE
LWIP_ASSERT("unsent_oversize mismatch (pcb->unsent is NULL)",
pcb->unsent_oversize == 0);
#endif /* TCP_OVERSIZE */
}
/*
* Phase 3: Create new segments.
*
* The new segments are chained together in the local 'queue'
* variable, ready to be appended to pcb->unsent.
*/
while (pos < len) {
struct pbuf *p;
u16_t left = len - pos;
u16_t max_len = pcb->mss - optlen;
u16_t seglen = left > max_len ? max_len : left;
#if TCP_CHECKSUM_ON_COPY
u16_t chksum = 0;
u8_t chksum_swapped = 0;
#endif /* TCP_CHECKSUM_ON_COPY */
if (apiflags & TCP_WRITE_FLAG_COPY) {
/* If copy is set, memory should be allocated and data copied
* into pbuf */
if ((p = tcp_pbuf_prealloc(PBUF_TRANSPORT, seglen + optlen, pcb->mss, &oversize, pcb, apiflags, queue == NULL)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write : could not allocate memory for pbuf copy size %"U16_F"\n", seglen));
goto memerr;
}
LWIP_ASSERT("tcp_write: check that first pbuf can hold the complete seglen",
(p->len >= seglen));
TCP_DATA_COPY2((char *)p->payload + optlen, (u8_t*)arg + pos, seglen, &chksum, &chksum_swapped);
} else {
/* Copy is not set: First allocate a pbuf for holding the data.
* Since the referenced data is available at least until it is
* sent out on the link (as it has to be ACKed by the remote
* party) we can safely use PBUF_ROM instead of PBUF_REF here.
*/
struct pbuf *p2;
#if TCP_OVERSIZE
LWIP_ASSERT("oversize == 0", oversize == 0);
#endif /* TCP_OVERSIZE */
if ((p2 = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_ROM)) == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: could not allocate memory for zero-copy pbuf\n"));
goto memerr;
}
#if TCP_CHECKSUM_ON_COPY
/* calculate the checksum of nocopy-data */
chksum = ~inet_chksum((u8_t*)arg + pos, seglen);
#endif /* TCP_CHECKSUM_ON_COPY */
/* reference the non-volatile payload data */
p2->payload = (u8_t*)arg + pos;
/* Second, allocate a pbuf for the headers. */
if ((p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
/* If allocation fails, we have to deallocate the data pbuf as
* well. */
pbuf_free(p2);
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: could not allocate memory for header pbuf\n"));
goto memerr;
}
/* Concatenate the headers and data pbufs together. */
pbuf_cat(p/*header*/, p2/*data*/);
}
queuelen += pbuf_clen(p);
/* Now that there are more segments queued, we check again if the
* length of the queue exceeds the configured maximum or
* overflows. */
if ((queuelen > TCP_SND_QUEUELEN) || (queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_write: queue too long %"U16_F" (%"U16_F")\n", queuelen, TCP_SND_QUEUELEN));
pbuf_free(p);
goto memerr;
}
if ((seg = tcp_create_segment(pcb, p, 0, pcb->snd_lbb + pos, optflags)) == NULL) {
goto memerr;
}
#if TCP_OVERSIZE_DBGCHECK
seg->oversize_left = oversize;
#endif /* TCP_OVERSIZE_DBGCHECK */
#if TCP_CHECKSUM_ON_COPY
seg->chksum = chksum;
seg->chksum_swapped = chksum_swapped;
seg->flags |= TF_SEG_DATA_CHECKSUMMED;
#endif /* TCP_CHECKSUM_ON_COPY */
/* first segment of to-be-queued data? */
if (queue == NULL) {
queue = seg;
} else {
/* Attach the segment to the end of the queued segments */
LWIP_ASSERT("prev_seg != NULL", prev_seg != NULL);
prev_seg->next = seg;
}
/* remember last segment of to-be-queued data for next iteration */
prev_seg = seg;
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | LWIP_DBG_TRACE, ("tcp_write: queueing %"U32_F":%"U32_F"\n",
ntohl(seg->tcphdr->seqno),
ntohl(seg->tcphdr->seqno) + TCP_TCPLEN(seg)));
pos += seglen;
}
/*
* All three segmentation phases were successful. We can commit the
* transaction.
*/
/*
* Phase 1: If data has been added to the preallocated tail of
* last_unsent, we update the length fields of the pbuf chain.
*/
#if TCP_OVERSIZE
if (oversize_used > 0) {
struct pbuf *p;
/* Bump tot_len of whole chain, len of tail */
for (p = last_unsent->p; p; p = p->next) {
p->tot_len += oversize_used;
if (p->next == NULL) {
TCP_DATA_COPY((char *)p->payload + p->len, arg, oversize_used, last_unsent);
p->len += oversize_used;
}
}
last_unsent->len += oversize_used;
#if TCP_OVERSIZE_DBGCHECK
last_unsent->oversize_left -= oversize_used;
#endif /* TCP_OVERSIZE_DBGCHECK */
}
pcb->unsent_oversize = oversize;
#endif /* TCP_OVERSIZE */
/*
* Phase 2: concat_p can be concatenated onto last_unsent->p
*/
if (concat_p != NULL) {
LWIP_ASSERT("tcp_write: cannot concatenate when pcb->unsent is empty",
(last_unsent != NULL));
pbuf_cat(last_unsent->p, concat_p);
last_unsent->len += concat_p->tot_len;
#if TCP_CHECKSUM_ON_COPY
if (concat_chksummed) {
tcp_seg_add_chksum(concat_chksum, concat_chksummed, &last_unsent->chksum,
&last_unsent->chksum_swapped);
last_unsent->flags |= TF_SEG_DATA_CHECKSUMMED;
}
#endif /* TCP_CHECKSUM_ON_COPY */
}
/*
* Phase 3: Append queue to pcb->unsent. Queue may be NULL, but that
* is harmless
*/
if (last_unsent == NULL) {
pcb->unsent = queue;
} else {
last_unsent->next = queue;
}
/*
* Finally update the pcb state.
*/
pcb->snd_lbb += len;
pcb->snd_buf -= len;
pcb->snd_queuelen = queuelen;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_write: %"S16_F" (after enqueued)\n",
pcb->snd_queuelen));
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: valid queue length",
pcb->unacked != NULL || pcb->unsent != NULL);
}
/* Set the PSH flag in the last segment that we enqueued. */
if (seg != NULL && seg->tcphdr != NULL && ((apiflags & TCP_WRITE_FLAG_MORE)==0)) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
return ERR_OK;
memerr:
pcb->flags |= TF_NAGLEMEMERR;
TCP_STATS_INC(tcp.memerr);
if (concat_p != NULL) {
pbuf_free(concat_p);
}
if (queue != NULL) {
tcp_segs_free(queue);
}
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_write: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
}
LWIP_DEBUGF(TCP_QLEN_DEBUG | LWIP_DBG_STATE, ("tcp_write: %"S16_F" (with mem err)\n", pcb->snd_queuelen));
return ERR_MEM;
}
/**
* Enqueue TCP options for transmission.
*
* Called by tcp_connect(), tcp_listen_input(), and tcp_send_ctrl().
*
* @param pcb Protocol control block for the TCP connection.
* @param flags TCP header flags to set in the outgoing segment.
* @param optdata pointer to TCP options, or NULL.
* @param optlen length of TCP options in bytes.
*/
err_t
tcp_enqueue_flags(struct tcp_pcb *pcb, u8_t flags)
{
struct pbuf *p;
struct tcp_seg *seg;
u8_t optflags = 0;
u8_t optlen = 0;
LWIP_DEBUGF(TCP_QLEN_DEBUG, ("tcp_enqueue_flags: queuelen: %"U16_F"\n", (u16_t)pcb->snd_queuelen));
LWIP_ASSERT("tcp_enqueue_flags: need either TCP_SYN or TCP_FIN in flags (programmer violates API)",
(flags & (TCP_SYN | TCP_FIN)) != 0);
/* check for configured max queuelen and possible overflow */
if ((pcb->snd_queuelen >= TCP_SND_QUEUELEN) || (pcb->snd_queuelen > TCP_SNDQUEUELEN_OVERFLOW)) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 3, ("tcp_enqueue_flags: too long queue %"U16_F" (max %"U16_F")\n",
pcb->snd_queuelen, TCP_SND_QUEUELEN));
TCP_STATS_INC(tcp.memerr);
pcb->flags |= TF_NAGLEMEMERR;
return ERR_MEM;
}
if (flags & TCP_SYN) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -