打印

狗拿耗子LWIP

[复制链接]
楼主: zheng522
手机看帖
扫描二维码
随时随地手机跟帖
21
zheng522|  楼主 | 2015-5-27 18:34 | 只看该作者 回帖奖励 |倒序浏览
case NETCONN_TCP:
msg->conn->err = tcp_bind(msg->conn->pcb.tcp,
msg->msg.bc.ipaddr, msg->msg.bc.port);
default:
break;
}
sys_mbox_post(msg->conn->mbox, NULL);
}
内核函数 tcp_bind 将本地 ip、 port 绑定到指定的 connection 上,源代码中的函数说明如下。
/**
* Binds the connection to a local portnumber and IP address. If the
* IP address is not given (i.e., ipaddr == NULL), the IP address of
* the outgoing network interface is used instead.
* */

使用特权

评论回复
22
zheng522|  楼主 | 2015-5-27 18:35 | 只看该作者
6、系统调用 netconn_listen
err_t netconn_listen(struct netconn *conn)
{
struct api_msg *msg;
if (conn == NULL) {
return ERR_VAL;
}
if (conn->acceptmbox == SYS_MBOX_NULL) {
conn->acceptmbox = sys_mbox_new();
if (conn->acceptmbox == SYS_MBOX_NULL) {
return ERR_MEM;
}
}
if ((msg = memp_malloc(MEMP_API_MSG)) == NULL) {
return (conn->err = ERR_MEM);
}
msg->type = API_MSG_LISTEN;
msg->msg.conn = conn;
api_msg_post(msg);
sys_mbox_fetch(conn->mbox, NULL);
memp_free(MEMP_API_MSG, msg);
return conn->err;
}
为 tcp connnection 分配 accept mailbox,然后发消息 API_MSG_LISTEN 给内核。

使用特权

评论回复
23
zheng522|  楼主 | 2015-5-27 18:35 | 只看该作者
7、内核函数 do_listen
static void do_listen(struct api_msg_msg *msg)
{
if (msg->conn->pcb.tcp != NULL) {
switch (msg->conn->type) {
......
case NETCONN_TCP:
msg->conn->pcb.tcp = tcp_listen(msg->conn->pcb.tcp);
if (msg->conn->pcb.tcp == NULL) {
msg->conn->err = ERR_MEM;
} else {
......
tcp_arg(msg->conn->pcb.tcp, msg->conn);
tcp_accept(msg->conn->pcb.tcp, accept_function);
}
default:
break;
}
}
sys_mbox_post(msg->conn->mbox, NULL);
}

使用特权

评论回复
24
zheng522|  楼主 | 2015-5-27 18:35 | 只看该作者
7.1 内核函数 tcp_listen
struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
{
struct tcp_pcb_listen *lpcb;
......
lpcb = memp_malloc(MEMP_TCP_PCB_LISTEN);
if (lpcb == NULL) {
return NULL;
}
lpcb->callback_arg = pcb->callback_arg;
lpcb->local_port = pcb->local_port;
lpcb->state = LISTEN;
lpcb->so_options = pcb->so_options;
lpcb->so_options |= SOF_ACCEPTCONN;
lpcb->ttl = pcb->ttl;
lpcb->tos = pcb->tos;
ip_addr_set(&lpcb->local_ip, &pcb->local_ip);
memp_free(MEMP_TCP_PCB, pcb);
#if LWIP_CALLBACK_API
lpcb->accept = tcp_accept_null;
#endif /* LWIP_CALLBACK_API */
TCP_REG(&tcp_listen_pcbs.listen_pcbs, lpcb);

使用特权

评论回复
25
zheng522|  楼主 | 2015-5-27 18:36 | 只看该作者
return (struct tcp_pcb *)lpcb;
}
分配一个类型为 tcp_pcb_listen 的 pcb,并将 tcp_pcb 的相应的参数拷贝过来,包括 self ip、 self port 等,设
置新分配 pcb 的状态为 LISTEN,最后将它放入队列 tcp_listen_pcbs。队列 tcp_listen_pcbs 存放着所有处于
LISTEN 状态的 pcb,相应的 connection 的指针可从 pcb->callback_arg 获得。

使用特权

评论回复
26
zheng522|  楼主 | 2015-5-27 18:36 | 只看该作者
7.2 注册 accept_function,当接收了一个 connect 链接请求并三次握手完成后,调用该函数通知用户任务。
8、 RTT 的估计
8.1 估计 RTT 的算法
static u8_t tcp_receive(struct tcp_pcb *pcb)
{
......
/* RTT estimation calculations. This is done by checking if the
incoming segment acknowledges the segment we use to take a
round-trip time measurement. */
if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno)) {
m = tcp_ticks - pcb->rttest;
......
/* This is taken directly from VJs original code in his paper */
m = m - (pcb->sa >> 3);
pcb->sa += m;
if (m < 0) {
m = -m;
}
m = m - (pcb->sv >> 2);
pcb->sv += m;
pcb->rto = (pcb->sa >> 3) + pcb->sv;
......
pcb->rttest = 0;
}
......
}

使用特权

评论回复
27
zheng522|  楼主 | 2015-5-27 18:36 | 只看该作者
z tcp_ticks,内核的 tick, 500ms 递增一次。
z pcb->rttest,发起 rtt 估计时的 tick,当该值为 0 时表示不需要做 rtt 估计。
z pcb->rtseq,用于 rtt 估计的 segment,当该 segment 的 ack 到达时,进行 rtt 估计。
z pcb->sa, A = sa >> 3。
z pcb->sv, D = sv >> 2。
z pcb->rto, rtt 的估计值。
算法的详细叙述见《TCP/IP 详解,卷 1:协议》第 21 章,上述代码可转换为:
m = m - (pcb->sa >> 3); /* Err = M – A */
pcb->sa += m; /* 8A = 8A + Err,即 A = A + Err / 8 */
if (m < 0) { /* e = |Err| */
m = -m;

使用特权

评论回复
28
zheng522|  楼主 | 2015-5-27 18:36 | 只看该作者
}
m = m - (pcb->sv >> 2); /* t = |Err| - D */
pcb->sv += m; /* 4D = 4D + (|Err| - D),即 D = D + (|Err| - D) / 4 */
pcb->rto = (pcb->sa >> 3) + pcb->sv; /* RTT = A + 4D */
可见,与《卷 1:协议》中描述的一模一样。

使用特权

评论回复
29
zheng522|  楼主 | 2015-5-27 18:37 | 只看该作者
8.2 启动 RTT 估计的时机
《卷 1:协议》说在内核没有进行 rtt 估计时,且不处于超时重传状态(避免重传多义性)(此处指的不是快
速重传状态)时可以进行 rtt 估计。
1) 在窗口允许的前提下, 即 MIN( self cwnd, peer rcv_wnd) 大于待发送 segment 的长度, 内核函数 tcp_output
无条件调用内核函数 tcp_output_segment。
2) 内核函数 tcp_output_segment 发现当前没有进行 rtt 估计,则无条件立刻启动 rtt 估计。
static void tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
{
......
pcb->rtime = 0;
if (pcb->rttest == 0) {
pcb->rttest = tcp_ticks;
pcb->rtseq = ntohl(seg->tcphdr->seqno);
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_output_segment: rtseq %"U32_F"\n", pcb->rtseq));
}
......
ip_output(seg->p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos,
IP_PROTO_TCP);
}

使用特权

评论回复
30
zheng522|  楼主 | 2015-5-27 18:37 | 只看该作者
与《卷 1:协议》不一致的是:快速重传内核函数 tcp_rexmit 会调用 tcp_output,这里没问题,因为不存在
重传二义性; 但超时重传内核函数 tcp_rexmit_rto 也会调用 tcp_output,这是与《卷 1:协议》矛盾的。如
果我的理解没出错的话, lwip 在这个地方在估计 rtt 时会存在一定的风险。
8.3 rtt 估计的关闭
将 pcb->rttest 置为 0 即关闭 rtt 估计,共有三处会关闭 rtt 估计:
z tcp_receive,完成了 rtt 估计的运算,正常关闭。
z tcp_rexmit,快速重传,关闭 rtt 估计。
z tcp_rexmit_rto,超时重传,关闭 rtt 估计。

使用特权

评论回复
31
zheng522|  楼主 | 2015-5-27 18:37 | 只看该作者
9、 segment 的超时重传与慢启动
void tcp_slowtmr(void)
{
......
pcb_remove = 0;
if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) {
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));
}
else if (pcb->nrtx == TCP_MAXRTX) {
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));
} else {
++pcb->rtime;
if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {
/* Time for a retransmission. */
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %"U16_F" pcb->rto %"U16_F"\n",
pcb->rtime, pcb->rto));
/* Double retransmission time-out unless we are trying to
* connect to somebody (i.e., we are in SYN_SENT). */
if (pcb->state != SYN_SENT) {
pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];
}
/* Reduce congestion window and ssthresh. */
eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
pcb->ssthresh = eff_wnd >> 1;
if (pcb->ssthresh < pcb->mss) {
pcb->ssthresh = pcb->mss * 2;
}
pcb->cwnd = pcb->mss;
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"U16_F" ssthresh %"U16_F"\n",
pcb->cwnd, pcb->ssthresh));
/* The following needs to be called AFTER cwnd is set to one mss - STJ */
tcp_rexmit_rto(pcb);
}
}
......
}

使用特权

评论回复
32
zheng522|  楼主 | 2015-5-27 18:38 | 只看该作者
9.1 相应数据结构
z pcb->rto,重传定时器时长, rtt 估计时给出初始值,当超时重传时该值按指数增大。
z pcb->rtime, segment 从被发出到此时(没有收到 ack),所经历的 tick 数。在 tcp_output_segment 中,
该值被置 0。在 tcp_slowtmr 中,该值被递增。
z pcb->nrtx, segment 重传的次数。在 tcp_receive 中,收到新的 ack 时,该值被置 0。在 tcp_rexmit、
tcp_rexmit_rto 中,该值被递增。

使用特权

评论回复
33
zheng522|  楼主 | 2015-5-27 18:38 | 只看该作者
9.2 主要流程
当重传次数大于 TCP_MAXRTX 时, segment 被扔掉。当 rtime 大于 rto 时,启动慢启动流程,慢启动门限
被置为当前窗口的一半,但要不小于 2 个 mss;拥塞窗口为一个 mss, rto 按指数增大;最后重传该 segment。
详细介绍见《卷 1:协议》 21.6。此后如果有新的 ack 到达时,拥塞窗口每次递增一个 mss。

使用特权

评论回复
34
zheng522|  楼主 | 2015-5-27 18:38 | 只看该作者
10、快速重传与快速恢复
tcp_receive(struct tcp_pcb *pcb)
{
......
if (pcb->lastack == ackno) { /* 到达的 ack 为重复 ack */
pcb->acked = 0;
if (pcb->snd_wl1 + pcb->snd_wnd == right_wnd_edge){ /* peer rcv_wnd 没有张开 */
++pcb->dupacks;
if (pcb->dupacks >= 3 && pcb->unacked != NULL) { /* 收到了三个以上的重复 ack */
if (!(pcb->flags & TF_INFR)) { /* 不处于快速恢复状态 */
/* This is fast retransmit. Retransmit the first unacked segment. */
......
tcp_rexmit(pcb);
......
/* Set ssthresh to half of the minimum of the currenct cwnd and the advertised window */
if (pcb->cwnd > pcb->snd_wnd) /* 慢启动门限被置为当前窗口的一半 */
pcb->ssthresh = pcb->snd_wnd / 2;
else
pcb->ssthresh = pcb->cwnd / 2;
pcb->cwnd = pcb->ssthresh + 3 * pcb->mss; /* 拥塞窗口为慢启动门限加上 3 个 mss */
pcb->flags |= TF_INFR; /* 标记当前状态为快速恢复 */
} else { /* 已处于快速恢复状态 */
/* Inflate the congestion window, but not if it means that
the value overflows. */
if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss; /* 当前处于快速恢复状态,拥塞窗口递增一个 mss */
}
}
}
} else {
LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: dupack averted %"U32_F" %"U32_F"\n",
pcb->snd_wl1 + pcb->snd_wnd, right_wnd_edge));
}
} else
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_max)){
  /* We come here when the ACK acknowledges new data. */
/* Reset the "IN Fast Retransmit" flag, since we are no longer
in fast retransmit. Also reset the congestion window to the
slow start threshold. */
if (pcb->flags & TF_INFR) { /* 如果处于快速恢复,新的 ack 到达时,应将拥塞控制窗口置为慢启动
门限的值(拥塞发生时的窗口大小的一半);并清除快速恢复标识;
此时传入拥塞避免状态 */
pcb->flags &= ~TF_INFR;
pcb->cwnd = pcb->ssthresh;
}
  /* Reset the number of retransmissions. */
pcb->nrtx = 0;
  /* Reset the retransmission time-out. */
pcb->rto = (pcb->sa >> 3) + pcb->sv;
/* Update the send buffer space. */
pcb->acked = ackno - pcb->lastack;
pcb->snd_buf += pcb->acked;
/* Reset the fast retransmit variables. */
pcb->dupacks = 0;
pcb->lastack = ackno;
/* Update the congestion control variables (cwnd and ssthresh). */
if (pcb->state >= ESTABLISHED) {
if (pcb->cwnd < pcb->ssthresh) { /* 当前处于慢启动状态,拥塞窗口递增一个 mss */
if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss;
}
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"U16_F"\n", pcb->cwnd));
} else { /* 当前处于拥塞避免状态,拥塞窗口递增 mss/N,其中 cwnd = N*mss */
u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);
if (new_cwnd > pcb->cwnd) {
pcb->cwnd = new_cwnd;
}
......
}
}
......
}
}

使用特权

评论回复
35
zheng522|  楼主 | 2015-5-27 18:39 | 只看该作者
11、内核函数 tcp_enqueue
err_t tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len, u8_t flags, u8_t copy, u8_t *optdata, u8_t optlen)
z arg,待发送数据指针
z len,待发送数据长度
z flags,待发送 segment 的 flag,如 FIN、 SYN
z copy,表示 tcp_enqueue 是否需要拷贝待发送数据, 1 表示将持有数据的拷贝的 segment 放入待发送队
列, 0 表示将持有数据的引用的 segment 放入待发送队列。
z optdata, 待发送选项数据指针
z optlen,待发送选项数据长度

使用特权

评论回复
36
zheng522|  楼主 | 2015-5-27 18:39 | 只看该作者
11.1 对 tcp_enqueue 的引用
z tcp_connect,发送 SYN 和 mss。
z tcp_listen_input,发送 SYN、 ACK 和 mss。
z tcp_send_ctrl,发送给定的 flags
z tcp_write,发送给定的数据
可见只有 tcp_connect、 tcp_listen_input 发送了 optdata,可以总结出:
1)当有 optdata 发送时, flags 一定被掩上了 SYN
2) optdata 与数据不同时发送

使用特权

评论回复
37
zheng522|  楼主 | 2015-5-27 18:40 | 只看该作者
11.2 主要流程
err_t tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len,
u8_t flags, u8_t copy,
u8_t *optdata, u8_t optlen)
{
struct pbuf *p;
struct tcp_seg *seg, *useg, *queue;
u32_t left, seqno;
u16_t seglen;
void *ptr;
u8_t queuelen;
......
/* fail on too much data */
if (len > pcb->snd_buf) {
......
return ERR_MEM;
}
left = len;
ptr = arg;
/* seqno will be the sequence number of the first segment enqueued
* by the call to this function. */
seqno = pcb->snd_lbb; /* snd_lbb,下一个 segment 的 sequence number */
......
/* If total number of pbufs on the unsent/unacked queues exceeds the
* configured maximum, return an error */
queuelen = pcb->snd_queuelen;
if (queuelen >= TCP_SND_QUEUELEN) { /* buffer 最多值放 TCP_SND_QUEUELEN 个 segment */
......
return ERR_MEM;
}
......
/* First, break up the data into segments and tuck them together in
* the local "queue" variable. */
useg = queue = seg = NULL;
seglen = 0;
while (queue == NULL || left > 0) {
/* The segment length should be the MSS if the data to be enqueued
* is larger than the MSS. */
seglen = left > pcb->mss? pcb->mss: left; /* 待发送数据被分割成 mss 大小的 segment */
/* Allocate memory for tcp_seg, and fill in fields. */
seg = memp_malloc(MEMP_TCP_SEG); /* 分配一个 segment */
if (seg == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_enqueue: could not allocate memory for tcp_seg\n"));
goto memerr;
}
seg->next = NULL;
seg->p = NULL;
/* first segment of to-be-queued data? */
if (queue == NULL) {
queue = seg;
}
/* subsequent segments of to-be-queued data */
else {
/* Attach the segment to the end of the queued segments */
LWIP_ASSERT("useg != NULL", useg != NULL);
useg->next = seg;
}
/* remember last segment of to-be-queued data for next iteration */
useg = seg;
  /* If copy is set, memory should be allocated
* and data copied into pbuf, otherwise data comes from
* ROM or other static memory, and need not be copied. If
* optdata is != NULL, we have options instead of data. */
/* options? */
if (optdata != NULL) { /* 分配一个 RAM 类型的 pbuf, optdata 的拷贝在后面 */

使用特权

评论回复
38
zheng522|  楼主 | 2015-5-27 18:40 | 只看该作者
if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
goto memerr;
}
++queuelen;
seg->dataptr = seg->p->payload;
}
/* copy from volatile memory? */
else if (copy) { /* 分配一个 RAM 类型的 pbuf,将待发送数据拷贝进去 */
if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_RAM)) == NULL) {
......
goto memerr;
}
++queuelen;
if (arg != NULL) {
memcpy(seg->p->payload, ptr, seglen);
}
seg->dataptr = seg->p->payload;
}
/* do not copy data */
else {
/* 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. */
if ((p = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_ROM)) == NULL) {
......
goto memerr;
}
++queuelen;
/* reference the non-volatile payload data */
p->payload = ptr;
seg->dataptr = ptr;
  /* Second, allocate a pbuf for the headers. */
if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, 0, PBUF_RAM)) == NULL) {
/* If allocation fails, we have to deallocate the data pbuf as
* well. */
pbuf_free(p);
......
goto memerr;
}
++queuelen;
/* Concatenate the headers and data pbufs together. */
pbuf_cat(seg->p/*header*/, p/*data*/);
p = NULL;
}
/* 此时,已分配一个 segment,并关联上了一个 pbuf或 pbuf chain */
/* Now that there are more segments queued, we check again if the
length of the queue exceeds the configured maximum. */
if (queuelen > TCP_SND_QUEUELEN) {
......
goto memerr;
}
seg->len = seglen;
  /* build TCP header */
if (pbuf_header(seg->p, TCP_HLEN)) {
......
goto memerr;
}
seg->tcphdr = seg->p->payload;
seg->tcphdr->src = htons(pcb->local_port);
seg->tcphdr->dest = htons(pcb->remote_port);
seg->tcphdr->seqno = htonl(seqno);
seg->tcphdr->urgp = 0;
TCPH_FLAGS_SET(seg->tcphdr, flags);
  /* don't fill in tcphdr->ackno and tcphdr->wnd until later */ /* 到真正发送时再填写 */
  /* Copy the options into the header, if they are present. */
if (optdata == NULL) {
TCPH_HDRLEN_SET(seg->tcphdr, 5);
}
else {
TCPH_HDRLEN_SET(seg->tcphdr, (5 + optlen / 4));
/* Copy options into data portion of segment.
Options can thus only be sent in non data carrying
segments such as SYN|ACK. */
memcpy(seg->dataptr, optdata, optlen);
}
......
left -= seglen;
seqno += seglen;
ptr = (void *)((u8_t *)ptr + seglen);
}

使用特权

评论回复
39
zheng522|  楼主 | 2015-5-27 18:41 | 只看该作者
/* Now that the data to be enqueued has been broken up into TCP
segments in the queue variable, we add them to the end of the

pcb->unsent queue. */
if (pcb->unsent == NULL) {
useg = NULL;
}
else {
for (useg = pcb->unsent; useg->next != NULL; useg = useg->next);
}
/* { useg is last segment on the unsent queue, NULL if list is empty } */
/* If there is room in the last pbuf on the unsent queue,
chain the first pbuf on the queue together with that. */
if (useg != NULL &&
TCP_TCPLEN(useg) != 0 &&
!(TCPH_FLAGS(useg->tcphdr) & (TCP_SYN | TCP_FIN)) &&
!(flags & (TCP_SYN | TCP_FIN)) &&
/* !(flags & (TCP_SYN | TCP_FIN))保证了 queue 中第一个 segment:
1)不包含 option,只有 SYN 被掩上时,才会发送 option
2)不包含 flag, RST 由 ip_output 直接发送, ACK 由 tcp_output 掩上, PSH 在下面掩上
3)包含数据,既不包含 option 又不包含 flag,则一定包含数据
所以 tcp header 大小为 TCP_HLEN,且该 segment 只包含数据 */
/* fit within max seg size */
useg->len + queue->len <= pcb->mss) {
  /* Remove TCP header from first segment of our to-be-queued list */
pbuf_header(queue->p, -TCP_HLEN);
pbuf_cat(useg->p, queue->p);
useg->len += queue->len;
useg->next = queue->next;
......
if (seg == queue) {
seg = NULL;
}
memp_free(MEMP_TCP_SEG, queue);
}
else {
/* empty list */
if (useg == NULL) {
/* initialize list with this segment */
pcb->unsent = queue;
}
/* enqueue segment */
else {
useg->next = queue;
}
}

使用特权

评论回复
40
zheng522|  楼主 | 2015-5-27 18:41 | 只看该作者
if ((flags & TCP_SYN) || (flags & TCP_FIN)) {
++len;
}
pcb->snd_lbb += len; /* 更新下一个 segment 的 sequence number */
pcb->snd_buf -= len; /* 更新 buffer 的大小 */
/* update number of segments on the queues */ /* 更新 buffer 中 segment 的个数 */
pcb->snd_queuelen = queuelen;
......
/* Set the PSH flag in the last segment that we enqueued, but only
if the segment has data (indicated by seglen > 0). */
if (seg != NULL && seglen > 0 && seg->tcphdr != NULL) {
TCPH_SET_FLAG(seg->tcphdr, TCP_PSH);
}
return ERR_OK;
memerr:
TCP_STATS_INC(tcp.memerr);
if (queue != NULL) {
tcp_segs_free(queue);
}
if (pcb->snd_queuelen != 0) {
LWIP_ASSERT("tcp_enqueue: valid queue length", pcb->unacked != NULL ||
pcb->unsent != NULL);
}
......
return ERR_MEM;
}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则