lwip之数据收发流程_3
生活随笔
收集整理的這篇文章主要介紹了
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的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Cortex-M3 内部寄存器
- 下一篇: 记一次lwip中 遇到 pcb == p