欢迎访问 生活随笔!

生活随笔

当前位置: 首页 > 编程资源 > 编程问答 >内容正文

编程问答

lwip之数据收发流程_3

发布时间:2025/4/5 编程问答 53 豆豆
生活随笔 收集整理的这篇文章主要介绍了 lwip之数据收发流程_3 小编觉得挺不错的,现在分享给大家,帮大家做个参考.
// 只会被tcp_process函数调用,用于进一步完成对输入报文的处理,具体来说,该函数主要是完成输入报文的冗余截断,管理unacked、unsent、ooseq三张链表void tcp_receive(struct tcp_pcb *pcb){struct tcp_seg *next;struct tcp_seg *prev, *cseg;struct pbuf *p;s32_t off;s16_t m;u32_t right_wnd_edge;    // 本地发送窗口右边界u16_t new_tot_len;int found_dupack = 0;    // 重复ack标志,置1表示是重复ack    // 首先检测报文是否包含ACK标志if (flags & TCP_ACK){right_wnd_edge = pcb->snd_wl2 + pcb->snd_wnd;    // 获取本地发送窗口右边界// 有3种情况可以导致本地发送窗口更新if (TCP_SEQ_LT(pcb->snd_wl1, seqno)||                                // snd_wl1小于新seqno,说明对方有发来数据(pcb->snd_wl1 == seqno && TCP_SEQ_LT(pcb->snd_wl2, ackno))||    // snd_wl1等于新seqno且snd_wl2小于新ackno,说明对方没有发送数据,只是在收到数据后发送一个确认(pcb->snd_wl2 == ackno && tcphdr->wnd > pcb->snd_wnd))             // snd_wl2等于新ackno且snd_wnd小于报文首部的窗口通告wnd,说明我方没有发数据过去,但被对方告知接收窗口变大    {pcb->snd_wnd = tcphdr->wnd;        // 更新本地发送窗口大小    ,跟对方发来的接收窗口通告匹配pcb->snd_wl1 = seqno;            // 更新接收到的序号pcb->snd_wl2 = ackno;            // 更新接收到的确认号// 如果发送窗口非0,且探察开启if (pcb->snd_wnd > 0 && pcb->persist_backoff > 0){pcb->persist_backoff = 0;    // 停止窗口探察}}// 判断是否是一个重复的ACK,需要满足5个条件// 1.如果ackno小于等于lastack,即没有确认新数据if (TCP_SEQ_LEQ(ackno, pcb->lastack))                         {pcb->acked = 0;        // 没有确认新数据,那么acked为0// 2.如果报文段中没有数据if (tcplen == 0){// 3.本地发送窗口没有更新if (pcb->snd_wl2 + pcb->snd_wnd == right_wnd_edge){// 4.如果重传定时器正在运行,即本地有数据正等待被确认if (pcb->rtime >= 0){// 5.如果ackno等于lastackif (pcb->lastack == ackno){// 此时可以确定这是一个重复的ack,说明报文发生了丢失found_dupack = 1;// 该ack被重复收到的次数自增if (pcb->dupacks + 1 > pcb->dupacks)++pcb->dupacks;// 如果该ack重复收到超过3次,说明发生了拥塞if (pcb->dupacks > 3){if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd){pcb->cwnd += pcb->mss;}}// 如果该ack重复第3次收到,执行快速重传算法else if (pcb->dupacks == 3){tcp_rexmit_fast(pcb);}}}}}// 如果没有确认新数据但又不属于重复ackif (!found_dupack){pcb->dupacks = 0;        // 将ack重复收到的次数清0}}// 如果是正常情况的ACK,lastack+1<=ackno<=snd_nxtelse if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_nxt)){// 如果控制块处于快速重传状态    ,则关闭重传状态、拥塞功能    if (pcb->flags & TF_INFR){pcb->flags &= ~TF_INFR;pcb->cwnd = pcb->ssthresh;}pcb->nrtx = 0;                                // 重传次数清0pcb->rto = (pcb->sa >> 3) + pcb->sv;        // 复位重传超时时间pcb->acked = (u16_t)(ackno - pcb->lastack);    // 更新acked字段为被确认的已发送数据长度pcb->snd_buf += pcb->acked;                    // 更新可用的发送空间pcb->dupacks = 0;                            // 将ack重复收到的次数清0pcb->lastack = ackno;                        // 更新接收到的ackno// 如果处于TCP连接已经建立状态,调整拥塞算法功能模块if (pcb->state >= ESTABLISHED){if (pcb->cwnd < pcb->ssthresh){if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd){pcb->cwnd += pcb->mss;}}else{u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);if (new_cwnd > pcb->cwnd){pcb->cwnd = new_cwnd;}}}// 遍历unacked队列,将所有数据编号小于等于ackno的报文段移除while (pcb->unacked != NULL && TCP_SEQ_LEQ(ntohl(pcb->unacked->tcphdr->seqno) + TCP_TCPLEN(pcb->unacked), ackno)){      // 将满足要求的报文从unacked链表取出next = pcb->unacked;pcb->unacked = pcb->unacked->next;// 如果该报文包含FIN标志,意味着当前收到的ACK对FIN做了确认,则acked字段减1,即不需要提交上层使知道FIN被对方成功接收if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)){pcb->acked--;}pcb->snd_queuelen -= pbuf_clen(next->p);        // 释放被该报文占用的发送空间tcp_seg_free(next);                                // 释放被该报文占用的tcp报文段}// 当所有满足要求的报文段移除成功后,判断unacked队列是否为空if(pcb->unacked == NULL)pcb->rtime = -1;    // 若为空,关闭重传定时器elsepcb->rtime = 0;        // 否则复位重传定时器pcb->polltmr = 0;        // 复位轮询定时器}        // 如果该ACK既不是重复ACK,又不是正常ACK,则acked字段清0,即该ACK不确认任何已发送数据else{pcb->acked = 0;}// 遍历unsent队列,将所有数据编号小于等于ackno的报文段移除// 这是因为对于需要重传的报文段,lwip直接将它们挂在unsent队列上,所以收到的ACK可能是对已超时报文段的确认while (pcb->unsent != NULL && TCP_SEQ_BETWEEN(ackno, ntohl(pcb->unsent->tcphdr->seqno) + TCP_TCPLEN(pcb->unsent), pcb->snd_nxt)){// 将满足要求的报文从unsent链表取出next = pcb->unsent;pcb->unsent = pcb->unsent->next;// 如果该报文包含FIN标志,意味着当前收到的ACK对FIN做了确认,则acked字段减1,即不需要提交上层使知道FIN被对方成功接收if ((pcb->acked != 0) && ((TCPH_FLAGS(next->tcphdr) & TCP_FIN) != 0)){pcb->acked--;}pcb->snd_queuelen -= pbuf_clen(next->p);        // 释放被该报文占用的发送空间tcp_seg_free(next);                                // 释放被该报文占用的tcp报文段}// RTT计算,暂略if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno)){m = (s16_t)(tcp_ticks - pcb->rttest);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;}}// 如果该输入报文还包含了数据,则要继续对数据进行处理if (tcplen > 0){// 如果seqno + 1 <= rcv_nxt <= seqno + tcplen - 1,意味着收到的数据区域头部有无效数据(收到的数据有部分处于本地左侧接收窗口外),需要截断数据头if (TCP_SEQ_BETWEEN(pcb->rcv_nxt, seqno + 1, seqno + tcplen - 1)){off = pcb->rcv_nxt - seqno;                            // 需要截掉的数据长度p = inseg.p;                                        // 获取收到的报文段的pbuf链表头// 判断需要截断的长度是否超出了第一个pbuf中存储的数据长度if (inseg.p->len < off){new_tot_len = (u16_t)(inseg.p->tot_len - off);    // 截断重复数据后的有效数据长度// 如果超出,则需要遍历pbuf链表,依次摘除数据,直到最后一个包含摘除数据的pbufwhile (p->len < off){off -= p->len;                                // 剩余摘除长度p->tot_len = new_tot_len;                    // 更新当前pbuf中的数据总长,p->len = 0;                                    // 因为数据被摘除,所以当前pbuf中的数据分长清0p = p->next;                                // 指向下一个pbuf}// 处理最后一个包含摘除数据的pbuf,就是调整数据指针略过摘除数据pbuf_header(p, (s16_t)-off);}else{// 如果未超出,则调整第一个pbuf中的数据指针略过摘除数据pbuf_header(inseg.p, (s16_t)-off);}inseg.len -= (u16_t)(pcb->rcv_nxt - seqno);    // 更新TCP报文段数据总长inseg.tcphdr->seqno = seqno = pcb->rcv_nxt;    // 更新TCP头中的seqno,指向接收窗口头位置}else{// 如果seqno < rcv_nxt,意味着seqno+tcplen-1 < rcv_nxt,说明这是个完全重复的报文段if (TCP_SEQ_LT(seqno, pcb->rcv_nxt)){tcp_ack_now(pcb);        // 只回复一个ACK给对方(这里是否应该直接返回不再运行下去)}}// 如果数据起始编号在接收窗口内if (TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd - 1)){// 如果该报文数据处于接收起始位置,意味着该报文是连续到来的if (pcb->rcv_nxt == seqno){tcplen = TCP_TCPLEN(&inseg);        // 更新该报文的总数据长度// 如果总长大于接收窗口大小,就需要做尾部截断处理,这里包含对FIN和SYN两种标志的不同处理结果,注意体会if (tcplen > pcb->rcv_wnd){// 如果TCP头中带FIN标志,清除FIN标志,因为对方还有数据要发过来if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN){TCPH_FLAGS_SET(inseg.tcphdr, TCPH_FLAGS(inseg.tcphdr) &~ TCP_FIN);}inseg.len = pcb->rcv_wnd;        // 根据接收窗口调整数据长度// 如果TCP头中带SYN标志,报文段数据长度减1if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN){inseg.len -= 1;}pbuf_realloc(inseg.p, inseg.len);    //  因为数据被截断,pbuf中的参数需要相应调整tcplen = TCP_TCPLEN(&inseg);        // 再次更新该报文的总数据长度}// 如果无序报文段队列ooseq上存在报文段if (pcb->ooseq != NULL){// 判断当前有序报文段的TCP头中是否带FIN标志if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN){// 如果该有序报文段带FIN标志,意味着单向TCP连接结束// 不可能再从对方收到新的报文段,ooseq队列中的报文段没有成为有序报文段可能,只能作废while (pcb->ooseq != NULL){struct tcp_seg *old_ooseq = pcb->ooseq;pcb->ooseq = pcb->ooseq->next;tcp_seg_free(old_ooseq);}}else{next = pcb->ooseq;// 遍历ooseq链表,删除序号被当前有序报文段完全覆盖的报文段while (next && TCP_SEQ_GEQ(seqno + tcplen,next->tcphdr->seqno + next->len)){// 如果这些即将被删除的报文段带FIN标志且当前有序报文段不带SYN标志if (TCPH_FLAGS(next->tcphdr) & TCP_FIN &&(TCPH_FLAGS(inseg.tcphdr) & TCP_SYN) == 0){TCPH_SET_FLAG(inseg.tcphdr, TCP_FIN);    // 在当前有效报文段的TCP头中添加FIN标志tcplen = TCP_TCPLEN(&inseg);            // 再次更新该报文的总数据长度}prev = next;next = next->next;tcp_seg_free(prev);}// 如果当前有序报文段尾部与ooseq中的报文段存在部分重叠    if (next && TCP_SEQ_GT(seqno + tcplen,next->tcphdr->seqno)){inseg.len = (u16_t)(next->tcphdr->seqno - seqno);    // 截断当前有序报文段尾部的重叠部分,得到有效部分长度// 如果当前有序报文段TCP头中带SYN标志,报文段数据长度减1if (TCPH_FLAGS(inseg.tcphdr) & TCP_SYN){inseg.len -= 1;}pbuf_realloc(inseg.p, inseg.len);            //  因为数据被截断,pbuf中的参数需要相应调整tcplen = TCP_TCPLEN(&inseg);                // 再次更新该报文的总数据长度}pcb->ooseq = next;}}pcb->rcv_nxt = seqno + tcplen;    // 更新下一个期望接收到的序号,也就是接收窗口左边界pcb->rcv_wnd -= tcplen;            // 更新当前可用接收窗口tcp_update_rcv_ann_wnd(pcb);    // 更新公告窗口// 如果该有序报文段中存在数据if (inseg.p->tot_len > 0){recv_data = inseg.p;        // 将全局指针recv_data指向报文段中的数据pbufinseg.p = NULL;}// 如果该有序报文段的TCP头中带FIN标志if (TCPH_FLAGS(inseg.tcphdr) & TCP_FIN){recv_flags |= TF_GOT_FIN;        // 则在报文处理结果变量recv_flags添加TF_GOT_FIN标志}// 遍历ooseq队列,取出所有有序的报文段// (通过比较ooseq队列中报文段的seqno和当前TCP控制块中保存的rcv_nxt来判定该报文段是否有序)while (pcb->ooseq != NULL && pcb->ooseq->tcphdr->seqno == pcb->rcv_nxt){cseg = pcb->ooseq;seqno = pcb->ooseq->tcphdr->seqno;    // 更新序号pcb->rcv_nxt += TCP_TCPLEN(cseg);    // 更新下一个期望接收到的序号pcb->rcv_wnd -= TCP_TCPLEN(cseg);    // 更新当前可用接收窗口tcp_update_rcv_ann_wnd(pcb);        // 更新公告窗口// 如果该有序报文段中存在数据,则通过全局指针recv_data向上层提交数据if (cseg->p->tot_len > 0){// 判断全局指针recv_data是否为空if (recv_data){// 如果不为空,意味着有更早的数据准备向上提交pbuf_cat(recv_data, cseg->p);    // 将当前数据pbuf挂到recv_data指向的数据链表的尾部}else{// 如果为空,直接将当前数据pbuf赋给recv_datarecv_data = cseg->p;}cseg->p = NULL;}// 如果该有序报文段的TCP头中带FIN标志if (TCPH_FLAGS(cseg->tcphdr) & TCP_FIN){recv_flags |= TF_GOT_FIN;        // 则全局变量recv_flags添加TF_GOT_FIN标志// 如果当前TCP处于ESTABLISHED状态,则变成CLOSE_WAIT状态if (pcb->state == ESTABLISHED){pcb->state = CLOSE_WAIT;}}pcb->ooseq = cseg->next;tcp_seg_free(cseg);}// 以上都执行完毕后,向源端返回一个ACK,此处其实只是先在TCP控制块中添加ACK标志tcp_ack(pcb);}// 如果该报文数据不处于接收起始位置,意味着该报文不是有序的else{// 首先向源端返回一个立即ACKtcp_send_empty_ack(pcb);// 然后将该报文段放入ooseq队列if (pcb->ooseq == NULL){// 如果ooseq为空,则拷贝该报文段到新开辟的报文段空间,并将新开辟报文段作为ooseq起始单元pcb->ooseq = tcp_seg_copy(&inseg);}else{prev = NULL;    // 定义为ooseq链表中上一个报文段,这里首先清空// 遍历ooseq队列,选择合适位置插入该报文段for(next = pcb->ooseq; next != NULL; next = next->next){// 依次比较两个报文段的起始序号seqno,如果相等if (seqno == next->tcphdr->seqno){// 继续比较两个报文段的数据长度if (inseg.len > next->len){// 如果输入报文段数据长度更长// 拷贝该报文段到新开辟的报文段空间cseg = tcp_seg_copy(&inseg);// 插入ooseq链表if (cseg != NULL){// 如果不是ooseq上的第一个报文段if (prev != NULL){prev->next = cseg;    // 插入ooseq链表的上一个报文段之后}// 如果是第一个else{pcb->ooseq = cseg;    // 直接替换原有的第一个}tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的下一个报文段的影响,简单来说,就是切掉冗余,释放内存}break;    // 退出循环                            }else{// 如果输入报文段数据长度更短,则直接丢弃,并退出循环break;}}// 如果不相等else{// 如果是ooseq上的第一个报文段if (prev == NULL){// 如果该报文段的起始序号大于要插入的报文段起始序号if (TCP_SEQ_LT(seqno, next->tcphdr->seqno)){cseg = tcp_seg_copy(&inseg);            // 拷贝要插入的报文段到新开辟的报文段空间if (cseg != NULL){pcb->ooseq = cseg;                    // 将新报文段插到ooseq第一个位置tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的第一个报文段的影响}break;        // 退出循环}}// 如果不是第一个else{// 如果待插入报文段起始序号在前一个和后一个报文段起始序号之间if (TCP_SEQ_BETWEEN(seqno, prev->tcphdr->seqno+1, next->tcphdr->seqno-1)){cseg = tcp_seg_copy(&inseg);    // 拷贝要插入的报文段到新开辟的报文段空间if (cseg != NULL){// 如果与前一个报文段有数据重合if (TCP_SEQ_GT(prev->tcphdr->seqno + prev->len, seqno)){prev->len = (u16_t)(seqno - prev->tcphdr->seqno);    // 截断前一个报文段尾部pbuf_realloc(prev->p, prev->len);                    // 因为数据被截断,pbuf中的参数需要相应调整}prev->next = cseg;                    // 将新报文段插入前一个报文段之后tcp_oos_insert_segment(cseg, next);    // 处理好插入后与原有的下一个报文段的影响}break;}}// 如果已经是ooseq上的最后一个报文段// 且待插入的报文段起始序号大于该报文起始序号(其实函数运行到这里该条件必然成立)if (next->next == NULL && TCP_SEQ_GT(seqno, next->tcphdr->seqno)){// 如果该报文的TCP头中有FIN标志,则直接丢弃待插入的报文段,退出循环if (TCPH_FLAGS(next->tcphdr) & TCP_FIN){break;}next->next = tcp_seg_copy(&inseg);    // 拷贝要插入的报文段到新开辟的报文段空间,并插在队列尾部// 如果新插入的报文段不为空if (next->next != NULL){// 如果与前一个报文段有数据重合if (TCP_SEQ_GT(next->tcphdr->seqno + next->len, seqno)){next->len = (u16_t)(seqno - next->tcphdr->seqno);    // 截断前一个报文段尾部pbuf_realloc(next->p, next->len);                    // 因为数据被截断,pbuf中的参数需要相应调整}// 如果新插入的报文段数据长度超出了当前接收窗口大小if ((u32_t)tcplen + seqno > pcb->rcv_nxt + (u32_t)pcb->rcv_wnd){// 如果新插入的报文段的TCP头中有FIN标志if (TCPH_FLAGS(next->next->tcphdr) & TCP_FIN){TCPH_FLAGS_SET(next->next->tcphdr, TCPH_FLAGS(next->next->tcphdr) &~ TCP_FIN);    // 去掉TCP头中的FIN标志}next->next->len = pcb->rcv_nxt + pcb->rcv_wnd - seqno;    // 根据接收窗口大小调制新插入的报文段数据长度pbuf_realloc(next->next->p, next->next->len);            // 因为数据被截断,pbuf中的参数需要相应调整tcplen = TCP_TCPLEN(next->next);                        // 再次更新该报文的总数据长度}}break;}}prev = next;    // 以上都不满足,则遍历ooseq链表中下一个}}}}// 如果数据不在接收范围内else{tcp_send_empty_ack(pcb);    // 直接向源端返回一个立即确认ACK}}// 如果输入的报文段中不包含数据else{// 且序号位于接收窗口之内if(!TCP_SEQ_BETWEEN(seqno, pcb->rcv_nxt, pcb->rcv_nxt + pcb->rcv_wnd-1)){tcp_ack_now(pcb);        // 回一个ACK}}}err_t tcp_output(struct tcp_pcb *pcb){struct tcp_seg *seg,*useg;u32_t wnd,snd_nxt;if(tcp_input_pcb == pcb){return ERR_OK;    }wnd = LWIP_MIN(pcb->snd_wnd,pcb->cwnd);seg = pcb->unsent;if(pcb->flags & TF_ACK_NOW && (seg = NULL || ntohl(seg->tcphdr->seqno)  )){}}

 

总结

以上是生活随笔为你收集整理的lwip之数据收发流程_3的全部内容,希望文章能够帮你解决所遇到的问题。

如果觉得生活随笔网站内容还不错,欢迎将生活随笔推荐给好友。