「linux」關(guān)于關(guān)閉一個還有沒發(fā)送數(shù)據(jù)完的TCP連接思考

tcp相關(guān)視頻解析:

tcp專題訓練營之深度解析tcp/ip協(xié)議棧

徒手實現(xiàn)網(wǎng)絡協(xié)議棧,請準備好環(huán)境,一起來寫代碼

linux后臺開發(fā)面試中tcpip,哪些容易被問到的

當 close 一個 TCP 連接時,如果還有沒發(fā)送完的數(shù)據(jù)在緩沖區(qū)中,內(nèi)核會怎么處理?

當時我認為,因為關(guān)閉 TCP 連接會觸發(fā)四次揮手過程,而為了讓四次揮手能夠快速完成,應該會把發(fā)送緩沖區(qū)的數(shù)據(jù)清空,然后發(fā)送四次揮手的數(shù)據(jù)包。

帶著疑問,我去查閱 Linux 源碼的實現(xiàn),下面就是關(guān)閉一個 TCP 連接的過程。

關(guān)閉 TCP 連接過程

關(guān)閉一個 TCP 連接可以使用?close()?系統(tǒng)調(diào)用,我們來分析一下當調(diào)用?close()?關(guān)閉一個 TCP 連接時會發(fā)生什么事情。

當調(diào)用?close()?系統(tǒng)調(diào)用時,會觸發(fā)調(diào)用?sys_close()?內(nèi)核函數(shù),其實現(xiàn)如下:

asmlinkagelongsys_close(unsignedintfd){structfile*filp;structfiles_struct*files=current->files;...returnfilp_close(filp, files);? ? ...}

sys_close()?函數(shù)最終會調(diào)用?file_close()?函數(shù)來關(guān)閉文件(由于在 Linux 中 socket 是一種特殊的文件),我們接著分析?filp_close()?函數(shù)的實現(xiàn):

int filp_close(structfile*filp, fl_owner_t id){? ? ...? ? fput(filp);returnretval;}void fput(structfile* file){? ? ...if(atomic_dec_and_test(&file->f_count)) {? ? ? ? ...if(file->f_op && file->f_op->release)? ? ? ? ? ? file->f_op->release(inode, file);? ? ? ? ...? ? }}

可以看到,最終會調(diào)用文件系統(tǒng)對應的?release()?方法來處理關(guān)閉操作。對于 socket 文件系統(tǒng),release()?方法對應的是?sock_close()?函數(shù),而?sock_close()?函數(shù)最終會調(diào)用?sock_release()?函數(shù),所以我們來看看?sock_release()?函數(shù)的實現(xiàn):

void sock_release(structsocket*sock){if(sock->ops)? ? ? ? sock->ops->release(sock);? ? ...}

sock_release()?函數(shù)也很簡單,就是調(diào)用對應?協(xié)議族?的?release()?方法,因為 Linux 的 socket 文件系統(tǒng)可以支持多種協(xié)議族,比如?INET、Unix Domain Socket、Netlink?等。而對應?INET協(xié)議族(網(wǎng)絡)?來說,這個?release()?方法對應的是?inet_release()?函數(shù),inet_release()?函數(shù)實現(xiàn)如下:

int inet_release(structsocket*sock){structsock*sk = sock->sk;if(sk) {? ? ? ? long timeout;? ? ? ? ...? ? ? ? timeout =0;if(sk->linger && !(current->flags & PF_EXITING))? ? ? ? ? ? timeout = sk->lingertime;? ? ? ? sock->sk = NULL;? ? ? ? sk->prot->close(sk, timeout);? ? }return(0);}

inet_release()?函數(shù)最終會調(diào)用對應?傳輸層(TCP或者UDP)?的?close()?方法,對于?TCP協(xié)議來說,close()?方法對應的是?tcp_close()?函數(shù),tcp_close()?就是關(guān)閉 TCP 連接的最后站點。

【文章福利】需要C/C++ Linux服務器架構(gòu)師學習資料加群812855908(資料包括C/C++,Linux,golang技術(shù),Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協(xié)程,DPDK,ffmpeg等)

由于?tcp_close()?函數(shù)比較復雜,我們這里只分析當發(fā)生緩沖區(qū)還有數(shù)據(jù)的情況下,內(nèi)核會怎么處理緩沖區(qū)的數(shù)據(jù)。

void tcp_close(structsock*sk, long timeout){structsk_buff*skb;? ? int data_was_unread =0;? ? ...// 如果接收緩沖區(qū)有數(shù)據(jù), 那么先情況接收緩沖區(qū)的數(shù)據(jù)while((skb= __skb_dequeue(&sk->receive_queue)) != NULL) {u32len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq - skb->h.th->fin;? ? ? ? data_was_unread += len;? ? ? ? __kfree_skb(skb);? ? }? ? ...if(data_was_unread !=0) {// 如果接收緩沖區(qū)有數(shù)據(jù)沒有處理tcp_set_state(sk, TCP_CLOSE);// 把socket狀態(tài)設置為TCP_CLOSEtcp_send_active_reset(sk, GFP_KERNEL);// 發(fā)送一個reset包給對端連接}elseif(sk->linger && sk->lingertime==0) {? ? ? ? ...? ? }elseif(tcp_close_state(sk)) {? ? ? ? tcp_send_fin(sk);// 開始發(fā)生四次揮手包}? ? ...}

從?tcp_close()?函數(shù)的實現(xiàn)可以看出,關(guān)閉過程主要有兩種情況:

如果接收緩沖區(qū)還有數(shù)據(jù)沒有被用戶處理,那么就先把接收緩沖區(qū)的數(shù)據(jù)清空,并且發(fā)送一個 reset 包給對端連接。

如果接收緩沖區(qū)沒有數(shù)據(jù),那么就調(diào)用?tcp_send_fin()?函數(shù)開始進行四次揮手過程。

四次揮手過程如下圖:

接下來,我們分析?tcp_send_fin()?函數(shù)的實現(xiàn):

void tcp_send_fin(structsock*sk){structtcp_opt*tp = &(sk->tp_pinfo.af_tcp);structsk_buff*skb = skb_peek_tail(&sk->write_queue);// 發(fā)送緩沖區(qū)列表最后一個緩沖塊unsigned int mss_now;? ? ...if(tp->send_head != NULL) {// 如果發(fā)送緩沖區(qū)不為空TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;// 把最后一個發(fā)送緩沖塊設置FIN標志TCP_SKB_CB(skb)->end_seq++;? ? ? ? tp->write_seq++;? ? }else{// 如果發(fā)送緩沖區(qū)為空for(;;) {? ? ? ? ? ? skb = alloc_skb(MAX_TCP_HEADER, GFP_KERNEL);// 申請一個新的緩沖塊if(skb)break;? ? ? ? ? ? current->policy |= SCHED_YIELD;? ? ? ? ? ? schedule();? ? ? ? }? ? ? ? skb_reserve(skb, MAX_TCP_HEADER);? ? ? ? skb->csum =0;? ? ? ? TCP_SKB_CB(skb)->flags = (TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);// 設置FIN標志TCP_SKB_CB(skb)->sacked =0;? ? ? ? TCP_SKB_CB(skb)->seq = tp->write_seq;? ? ? ? TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq +1;? ? ? ? tcp_send_skb(sk, skb,1, mss_now);// 發(fā)送給對端連接}? ? ...}

在?tcp_send_fin()?函數(shù)我們終于找到了當發(fā)送緩沖區(qū)不為空的處理,當發(fā)送緩沖區(qū)不為空時,首先會獲取發(fā)送緩沖區(qū)的最后一個緩沖塊,然后把這個緩沖區(qū)的?FIN標志位?設置上。

所以我前面的想法是錯的,當關(guān)閉一個 TCP 連接時,如果發(fā)送緩沖區(qū)還有數(shù)據(jù)沒發(fā)送完,那么內(nèi)核只會把發(fā)送緩沖區(qū)最后一個緩沖塊設置上?FIN標志,而不是把發(fā)送緩沖區(qū)清空。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容