![](https://img.51dongshi.com/20250105/wz/18528831952.jpg)
于TCP/IP協議棧的TCP協議的重傳功能是由在linux內核源碼(net/ipv4/tcp_output.c)中的函數tcp_retransmit_skb()實現的代碼如下:?/*ThisretransmitsoneSKB.?Policydecisionsandretransmitqueue?*stateupdatesaredonebythecaller.?Returnsnon-zeroifan?*erroroccurredwhichpreventedthesend.?*/inttcp_retransmit_skb(structsock*sk,structsk_buff*skb){structtcp_sock*tp=tcp_sk(sk);structinet_connection_sock*icsk=inet_csk(sk);unsignedintcur_mss;interr;?/*InconslusiveMTUprobe*/if(icsk->icsk_mtup.probe_size){icsk->icsk_mtup.probe_size=0;}?/*Donotsentmorethanwequeued.1/4isreservedforpossible*copyingoverhead:fragmentation,tunneling,manglingetc.*///說明在發送緩存區中消耗了許多內存去做其他的工作(比如分片等,只有1/4的緩存才是保留給這些工作的),暫時不能重傳if(atomic_read(&sk->sk_wmem_alloc)>??min(sk->sk_wmem_queued+(sk->sk_wmem_queued>>2),sk->sk_sndbuf))return-EAGAIN;//檢測重傳的段,接收方是否已經收到其部分或者全部,如果收到則說明有bug,否者就調整TCP段的負荷,即刪除SKB緩存區//前面部分已經接收到的數據if(before(TCP_SKB_CB(skb)->seq,tp->snd_una)){if(before(TCP_SKB_CB(skb)->end_seq,tp->snd_una))BUG();if(tcp_trim_head(sk,skb,tp->snd_una-TCP_SKB_CB(skb)->seq))return-ENOMEM;}//根據目的地址等條件獲取路由,如果獲取路由失敗就不能發送if(inet_csk(sk)->icsk_af_ops->rebuild_header(sk))return-ehostunreach;/*Routingfailureorsimilar.*/?cur_mss=tcp_current_mss(sk);?/*Ifreceiverhasshrunkhiswindow,andskbisoutof*newwindow,donotretransmitit.Theexceptionisthe*case,whenwindowisshrunktozero.Inthiscase*ourretransmitservesasazerowindowprobe.*///如果接收方已經減小了窗口,并且帶重傳的SKB已經不在新的窗口內,則不能重傳該SKB,//有一種情況例外,就是接收方的接受窗口減少為0,在這種情況下會發送0窗口探測段if(!before(TCP_SKB_CB(skb)->seq,tcp_wnd_end(tp))&&??TCP_SKB_CB(skb)->seq!=tp->snd_una)return-EAGAIN;?if(skb->len>cur_mss){//如果當前的SKB長度大于MSS,則要進行分段處理if(tcp_fragment(sk,skb,cur_mss,cur_mss))return-ENOMEM;/*We'lltryagainlater.*/}else{intoldpcount=tcp_skb_pcount(skb);?if(unlikely(oldpcount>1)){tcp_init_tso_segs(sk,skb,cur_mss);tcp_adjust_pcount(sk,skb,oldpcount-tcp_skb_pcount(skb));}}?tcp_retrans_try_collapse(sk,skb,cur_mss);?/*SomeSolarisstacksoveroptimizeandignoretheFINona*retransmitwhenolddataisattached.?Sostripitoff*sinceitischeaptodosoandsavesbytesonthenetwork.*///有以下Solaris系統的協議棧有時候會忽略重傳SKB上帶有的FIN標志的payload,將payload全部剝離掉,節省網絡流量if(skb->len>0&&??(TCP_SKB_CB(skb)->flags&TCPHDR_FIN)&&??tp->snd_una==(TCP_SKB_CB(skb)->end_seq-1)){if(!pskb_trim(skb,0)){/*Reuse,eventhoughitdoessomeunnecessarywork*/tcp_init_nondata_skb(skb,TCP_SKB_CB(skb)->end_seq-1,??TCP_SKB_CB(skb)->flags);skb->ip_summed=CHECKSUM_NONE;}}?/*Makeacopy,ifthefirsttransmissionSKBclonewemade*isstillinsomebody'shands,elsemakeaclone.*/TCP_SKB_CB(skb)->when=tcp_time_stamp;?err=tcp_transmit_skb(sk,skb,1,GFP_ATOMIC);//發送SKB?if(err==0){/*UpdateglobalTCPstatistics.*/TCP_INC_STATS(sock_net(sk),TCP_MIB_RETRANSSEGS);?tp->total_retrans++;?#ifFASTRETRANS_DEBUG>0if(TCP_SKB_CB(skb)->sacked&TCPCB_SACKED_RETRANS){if(net_ratelimit())printk(KERN_DEBUG"retrans_outleaked.");}#endifif(!tp->retrans_out)tp->lost_retrans_low=tp->snd_nxt;TCP_SKB_CB(skb)->sacked|=TCPCB_RETRANS;tp->retrans_out+=tcp_skb_pcount(skb);?/*Savestampofthefirstretransmit.*/if(!tp->retrans_stamp)tp->retrans_stamp=TCP_SKB_CB(skb)->when;?tp->undo_retrans+=tcp_skb_pcount(skb);?/*snd_nxtisstoredtodetectlossofretransmittedsegment,*seetcp_input.ctcp_sacktag_write_queue().*/TCP_SKB_CB(skb)->ack_seq=tp->snd_nxt;}returnerr;}我們知道,TCP的發送是有一個SKB隊列如圖,這樣維持一個發送隊列,如果收到發送了SKB的ACK,就將對應的SKB從隊列中刪除掉,在函數tcp_retransmit_skb中我們可以看到,接受方游有可能只是收到了部分SKB的數據,那么就將收到的SKB的數據刪除掉,這樣可以節省緩存空間這里注意的到函數tcp_retransmit_skb()最終的是調用函數tcp_transmit_skb(sk,skb,1,GFP_ATOMIC);將SKB發送出去,而構造TCP的頭部信息在函數tcp_transmit_skb()中,下面是函數tcp_transmit_skb()構造TCP頭部的片段所以也就是說在發送隊列中的SKB是沒有頭部的,這也是方便了選擇重傳等功能/*BuildTCPheaderandchecksumit.*/th=tcp_hdr(skb);th->source=inet->inet_sport;th->dest=inet->inet_dport;th->seq=htonl(tcb->seq);th->ack_seq=htonl(tp->rcv_nxt);*(((__be16*)th)+6)=htons(((tcp_header_size>>2)<<12)|tcb->flags);?本文來自系統大全為您提供如需轉載請注明!推薦win10下載