1. 程式人生 > >TCP連線的終止----主動關閉

TCP連線的終止----主動關閉

在正常情況下,TCP連線的關閉需要連線的兩端進行四次分組交換,具體過程是:執行主動關閉的一端(A端)會首先發送FIN包給對端(B端),B端收到FIN包後會傳送一個ACK包給A段;B段執行關閉操作,傳送FIN給A端,A端傳送一個ACK給B端,連線徹底關閉。分組交換和狀態遷移如下圖所示:

  

  通常情況下,只有執行主動關閉的一端會進入TIME_WAIT狀態,還有一種情況會導致連線的兩端都進入TIME_WAIT狀態。當TCP的兩端同時給對端傳送FIN包,兩端的TCP狀態均從ESTABLISHED變為FIN_WAIT_1,在FIN_WAIT_1狀態下接收到FIN包後,狀態會由FIN_WAIT_1遷移到CLOSING,併發送最後的ACK。收到最後的ACK後,狀態變遷為TIME_WAIT狀態,如下圖所示:


  上述的兩種情況只是TCP連線關閉情況的一部分,在其他情況下,核心可能給對端傳送的不是FIN包,而是RST包。在這種情況下,有可能是應用層序的問題,也可能是核心資源短缺造成的,如何來查詢和確定這些異常情況的原因,需要對核心的實現有一個比較全面的瞭解。

  在應用層要關閉一個連線非常簡單,只需要指定要關閉的連線對應的套接字即可。核心中處理TCP連線關閉的系統呼叫是sys_close(),該函式做的事情不多,主要的關閉操作是由tcp_close()函式來完成的。tcp_close()函式的定義中比sys_close()多了一個timeout引數,不難看出這個timeout肯定是一個超時時間,第一次看到這個引數也是比較疑惑。在呼叫close()的時候並沒有指定超時引數,那這個timeout的值怎麼來的呢?這個值是在tcp_close()的上層函式inet_release()中計算出來的,其計算方式如下所示:

  1. int inet_release(struct socket *sock)  
  2. {  
  3.          ......  
  4.         timeout = 0;  
  5.         if (sock_flag(sk, SOCK_LINGER) &&  
  6.             !(current->flags & PF_EXITING))  
  7.             timeout = sk->sk_lingertime;  
  8.         ......  
  9. }  
  我們看到timeout的值和SOCK_LINGER選項有關,這個選項可以通過setsockopt()來設定,該選項表示在關閉連線時需要等待的時間,設定的事件值儲存在sk_lingertime中,這個值可以為負值。這個選項通常不會設定,所以在我們的討論上不關注這個選選項,認為timeout的值為0.

  接下來我們從tcp_close()開始,看核心中如何來執行應用層發出的關閉連線請求。

  tcp_close()中首先呼叫lock_sock()來獲取訪問sock例項的互斥鎖,獲取鎖後將sk_shutdown設定為SHUTDOWN_MASK。sk_shutdown可以設定的值有RCV_SHUTDOWN(值為1)、SEND_SHUTDOWN(值為2)、SHUTDOWN_MASK(值為3),分別代表關閉接收通道、關閉傳送通道、完全關閉,所以這裡是要同時關閉傳送和接收通道。接下來是檢查套接的狀態是否是LISTEN狀態,我們這裡要看的ESTABLISHED狀態下套接字的關閉,所以這個部分直接跳過。

  如果此時接收佇列中不為空,即接收到的資料沒有被上層讀取,這時需要將接受佇列中的SKB包全都釋放掉,釋放的資料長度儲存在區域性變數data_was_unread(它的初始值為0)中,如下所示:

  1. while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {  
  2. u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq -  
  3.       tcp_hdr(skb)->fin;  
  4. data_was_unread += len;  
  5. __kfree_skb(skb);  
  區域性變數data_was_unread中儲存的值並不是核心關心的,核心關心只是是不是有資料未被讀取。如果套接字的接收佇列中有資料未被讀取,這時核心傳送的不是FIN包給對端,而是RST,如下所示:
  1. if (data_was_unread) {  
  2. /* Unread data was tossed, zap the connection. */
  3. NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);  
  4. tcp_set_state(sk, TCP_CLOSE);  
  5. tcp_send_active_reset(sk, sk->sk_allocation);  
在傳送RST包給對端前會呼叫tcp_set_state()將套接字的狀態設定為TCP_CLOSE,這時沒有TIME_WAIT狀態,沒有FIN_WAIT_1狀態。所以在編寫應用程式時,在關閉連線前,一定要保證所有接收到的資料被讀取,否則連線會不正常關閉。

  假設套接字的接收佇列為空,接著會呼叫tcp_close_state()函式,這個函式根據sock例項的當前狀態找出執行關閉時下一個對應的狀態,其返回值要麼為0,要麼為TCP_ACTION_FIN。返回值為TCP_ACTION_FIN時,表示要傳送FIN包;為0時,表示不需要傳送FIN包。如果連線是在ESTABLISHED狀態執行關閉操作,其對應的下一個狀態為FIN_WAIT_1,tcp_close_state()的返回值為TCP_ACTION_FIN,所以接下來會呼叫tcp_send_fin()給對端傳送FIN包。在tcp_send_fin()中,如果sock例項的傳送佇列不為空,則將FIN標誌新增到傳送佇列中最後一個要傳送的SKB包中;如果傳送佇列為空,則會分配一個新的SKB包,新增上FIN標誌後加入到傳送佇列中。FIN標誌處理後,核心會呼叫__tcp_push_pending_frames()將傳送佇列的skb包傳送出去。tcp_send_fin()的程式碼如下:

  1. /* Send a fin.  The caller locks the socket for us.  This cannot be 
  2.  * allowed to fail queueing a FIN frame under any circumstances. 
  3.  */
  4. void tcp_send_fin(struct sock *sk)  
  5. {  
  6.     struct tcp_sock *tp = tcp_sk(sk);  
  7.     struct sk_buff *skb = tcp_write_queue_tail(sk);  
  8.     int mss_now;  
  9.     /* Optimization, tack on the FIN if we have a queue of 
  10.      * unsent frames.  But be careful about outgoing SACKS 
  11.      * and IP options. 
  12.      */
  13.     mss_now = tcp_current_mss(sk);  
  14.     if (tcp_send_head(sk) != NULL) {  
  15.         TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;  
  16.         TCP_SKB_CB(skb)->end_seq++;  
  17.         tp->write_seq++;  
  18.     } else {  
  19.         /* Socket is locked, keep trying until memory is available. */
  20.         for (;;) {  
  21.             skb = alloc_skb_fclone(MAX_TCP_HEADER,  
  22.                            sk->sk_allocation);  
  23.             if (skb)  
  24.                 break;  
  25.             yield();  
  26.         }  
  27.         /* Reserve space for headers and prepare control bits. */
  28.         skb_reserve(skb, MAX_TCP_HEADER);  
  29.         /* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */
  30.         tcp_init_nondata_skb(skb, tp->write_seq,  
  31.                      TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);  
  32.         tcp_queue_skb(sk, skb);  
  33.     }  
  34.     __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF);  
  35. }  
  在tcp_send_fin()中分配用於傳送FIN的SKB包時,如果分配失敗,記憶體會一直迴圈,知道分配成功。不太理解核心為什麼為了傳送一個FIN包,而這麼的“執著”,知道的朋友可以說一下,拜謝!

  在傳送完FIN包後,會將sock例項當前的狀態儲存在區域性變數state中,增加對sock例項的引用,然後呼叫sock_orphan()將sock例項從socket結構中分離,並且將套接字標誌設定為SOCK_DEAD,表示套接字即將關閉。在分離的過程中,有一項是分離等待佇列,核心只是簡單地將sk_sleep成員設定為NULL。這就有一個疑惑,sk_sleep是一個指標,直接設定為NULL,指向的記憶體怎麼釋放?等待的程序怎麼處理?其實sk_sleep成員指向的是socket例項的wait成員,所以等待佇列的釋放會在釋放socket結構時釋放。sk_sleep的值的設定是在sock_init_data()中進行的。接著呼叫release_sock()釋放sock例項後備佇列上的SKB包,如果此時後備佇列上的資料包不是剛好確認到FIN所在的序列好,在tcp_rcv_state_process()中處理時也有可能傳送RST給對端,這個和tcp_close()中對接收佇列的處理類似。這個過程可以在後面看FIN_WAIT_1狀態下接收到ACK時的處理時會看到。此時,sock例項要等待釋放,所以此時待銷燬的sock例項個數加1。

  為了完整的看到TCP連線的整個過程,在我們的討論中認為在tcp_close()中處理後sock的狀態為FIN_WAIT_1,在這個假設前提下,我們會進入到下面這段程式碼的處理中:

  1. if (sk->sk_state != TCP_CLOSE) {  
  2. int orphan_count = percpu_counter_read_positive(  
  3.                 sk->sk_prot->orphan_count);  
  4. sk_mem_reclaim(sk);  
  5. if (tcp_too_many_orphans(sk, orphan_count)) {  
  6.     if (net_ratelimit())  
  7.         printk(KERN_INFO "TCP: too many of orphaned "
  8.                "sockets\n");  
  9.     tcp_set_state(sk, TCP_CLOSE);  
  10.     tcp_send_active_reset(sk, GFP_ATOMIC);  
  11.     NET_INC_STATS_BH(sock_net(sk),  
  12.             LINUX_MIB_TCPABORTONMEMORY);  
  13. }  
如果tcp_too_many_orphans()返回true時,這裡我們看到核心會發送RST給對端。在以下兩種情況下tcp_too_many_orphans()會返回true:

  1、當前待銷燬的套接字數量大於系統配置sysctl_tcp_max_orphans變數

   2、如果當前sock例項傳送佇列中所有報文資料的總長度大於SOCK_MIN_SNDBUF(值為2048),並且TCP層分配記憶體的狀態處於pressure狀態

  到這裡,tcp_close()的處理基本上結束了,在上面我們已經說過,認為處理後sock例項處於FIN_WAIT_1狀態。

  在FIN_WAIT_1狀態下,接收到SKB包時會在tcp_rcv_state_process()函式中處理,函式中涉及FIN_WAIT_1狀態的程式碼如下所示:

  1. /* 
  2.  *  This function implements the receiving procedure of RFC 793 for 
  3.  *  all states except ESTABLISHED and TIME_WAIT. 
  4.  *  It's called from both tcp_v4_rcv and tcp_v6_rcv and should be 
  5.  *  address independent. 
  6.  */
  7. int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,  
  8.               struct tcphdr *th, unsigned len)  
  9. {  
  10.     struct tcp_sock *tp = tcp_sk(sk);  
  11.     struct inet_connection_sock *icsk = inet_csk(sk);  
  12.     int queued = 0;  
  13.     int res;  
  14.     tp->rx_opt.saw_tstamp = 0;  
  15.     ......  
  16.     res = tcp_validate_incoming(sk, skb, th, 0);  
  17.     if (res <= 0)  
  18.         return -res;  
  19.     /* step 5: check the ACK field */
  20.     if (th->ack) {  
  21.         int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH) > 0;  
  22.         switch (sk->sk_state) {  
  23.         ......  
  24.         case TCP_FIN_WAIT1:  
  25.             if (tp->snd_una == tp->write_seq) {  
  26.                 tcp_set_state(sk, TCP_FIN_WAIT2);  
  27.                 sk->sk_shutdown |= SEND_SHUTDOWN;  
  28.                 dst_confirm(sk->sk_dst_cache);  
  29.                 if (!sock_flag(sk, SOCK_DEAD))  
  30.                     /* Wake up lingering close() */
  31.                     sk->sk_state_change(sk);  
  32.                 else {  
  33.                     int tmo;  
  34.                     if (tp->linger2 < 0 ||  
  35.                         (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&  
  36.                          after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {  
  37.                         tcp_done(sk);  
  38. 相關推薦

    TCP連線終止----主動關閉

    在正常情況下,TCP連線的關閉需要連線的兩端進行四次分組交換,具體過程是:執行主動關閉的一端(A端)會首先發送FIN包給對端(B端),B端收到FIN包後會傳送一個ACK包給A段;B段執行關閉操作,傳送FIN給A端,A端傳送一個ACK給B端,連線徹底關閉。分組交換和狀態

    TCP/IP學習筆記(10)--TCP連線的建立與終止

    TCP連線的建立可以簡單的稱為三次握手,而連線的中止則可以叫做四次握手。   TCP是一個面向連線的協議,所以在連線雙方傳送資料之前,都需要首先建立一條連線。這和前面講到的協議完全不同。前面講的所有協議都只是傳送資料而已,大多數都不關心傳送的資料是不是送到,UDP尤其明顯,從

    TCP連線關閉的過程

    轉載的連結處:TCP連線和關閉 建立連線:三次握手 在 TCP/IP 協議中,TCP 協議提供可靠的連線服務,採用三次握手建立一個連線,如圖1所示。 圖1 TCP三次握手建立連線的過程 客戶端 A 傳送 SYN 包(SYN=j)到伺服器 B,並進入SYN_SEND 狀態

    TCP/IP協議--10 TCP 連線的建立與終止

    TCP連線的建立可以簡單的稱為三次握手,而連線的中止則可以叫做四次握手。 TCP是一個面向連線的協議,所以在連線雙方傳送資料之前,都需要首先建立一條連線。這和前面講到的協議完全不同。前面講的所有協議都只是傳送資料而已,大多數都不關心傳送的資料是不是送到,UDP尤其明顯,從程式設計的角度來說,UD

    TCP/IP學習筆記(10)-tcp連線的建立與終止

    TCP連線的建立可以簡單的稱為三次握手,而連線的中止則可以叫做四次握手。        TCP是一個面向連線的協議,所以在連線雙方傳送資料之前,都需要首先建立一條連線。這和前面講到的協議完全不同。前面講的所有協議都只是傳送資料而已,大多數都不關心

    TCP連線的建立和關閉

    TCP通過三次握手建立連線,通過四次揮手揮手關閉連線 1、三次握手                                            第一個TCP報文段包含SYN標誌,因此它是一個同步報文段,即ernest -lapyop(客戶端)向Kongmin

    TCP連線的狀態與關閉方式,及其對Server與Client的影響

    1. TCP連線的狀態   首先介紹一下TCP連線建立與關閉過程中的狀態。TCP連線過程是狀態的轉換,促使狀態發生轉換的因素包括使用者呼叫、特定資料包以及超時等,具體狀態如下所示: CLOSED:初始狀態,表示沒有任何連線。 LISTEN:Server端的某個Socket正在監聽來自遠

    TCP / IP學習筆記(10)-TCP連線的建立與終止

    前面曾經講述過UDP的伺服器設計,可以發現UDP的伺服器完全不需要所謂的併發機制,它只要建立一個數據輸入佇列就可以。但是TCP不同,TCP伺服器對於每一個連線都需要建立一個獨立的程序(或者是輕量級的,執行緒),來保證對話的獨立性。所以TCP伺服器是併發的。而且TCP還需要配備一個呼入連線請求佇列(UDP伺服器

    TCP連線的建立和終止

    1.TCP連線的建立         三路握手如下圖所示: 建立一個TCP連線情形如下:         (1)伺服器程序必須準備好接受外來的連線。通過呼叫socket,bind,listen這三個函式來完成,也可以稱為被動開啟。         (2)客戶程序通過con

    TCP 連線的建立和終止

    TCP連線的建立     三次握手 伺服器端準備好接收外來的連結,通過該socket、bind、listen3個函式完成,為被動開啟客戶端通過connect函式主動建立連線,通過傳送一個SYN(帶序號)伺服器確認(ACK)客戶的SYN,確認序號為服務SYN的序號加1,同時

    tcp連線的建立與終止

    理解TCP連線,需要首先記住以下幾點: TCP是雙向連線。兩個方向的連線可以獨立關閉。 TCP是基於位元組流的連線。每個tcp socket在核心裡有接收緩衝區和傳送緩衝區。 應用程式只能操縱緩衝區資料,而不能干擾實際的資料傳送過程。應用程協議可能有自己的協

    Golang Socket Tcp 連線優雅關閉

    主要是利用 系統監聽Interrupt 訊號,並且提前關閉伺服器 監聽,並且等待先前連線處理完畢後在退出程式。 Server 程式碼: package main import ( "net" "os" "os/signal" "strings" "sync"

    golang的垃圾回收與Finalizer——tcp連線是如何被自動關閉

          最近在做一個golang的連線池。測試過程中發現一個有趣的現象,獲取的連線沒有歸還給連線池,那麼過一段時間後該連線會自動關閉掉。猜測這跟連線池應該是沒有關係的,於是再用普通的連線做了實驗,即dial一個tcp連線,傳送請求,然後程式進入sleep,一段時間後該連

    TCP連線的建立和終止過程

    Richard Stevens先生在UNP2e的前言中寫道: I have found when teaching network programming that about 80% of all network programming problem

    TCP/IP】TCP連線的建立與終止

            TCP是一個面向連線的協議。無論哪一方想另一方傳送資料之前,都必須現在雙方之間建立一條連線。這種兩端間連線的建立與無連線協議如UDP不同。一端使用UDP想另一端傳送資料報時,無需任何預先的握手。連線建立與終止的時間系列       傳送第一個SYN的一端將執行

    計算機網路之TCP連線的建立和終止

    TCP提供一種面向連線的、可靠的基於流的服務。 面向連線:在彼此交換資料之前必須先建立一個TCP連線,雙方互相確認,僅有兩方彼此通訊。 可靠:資料被分割成TCP認為最適合傳送的資料塊;TCP發出一個段後啟動一個定時器,超時重傳;TCP收到另一端的資料後,將回

    TCP連線異常終止(RST包)

    轉自:http://blog.csdn.net/ixidof/article/details/8049667 TCP異常終止(reset報文) TCP的異常終止是相對於正常釋放TCP連線的過程而言的,我們都知道,TCP連線的建立是通過三次握手完成的,而TCP正常釋放

    TCP/IP詳解學習筆記(13)-- TCP連線的建立與終止

    1.TCP連線的建立            設主機B執行一個伺服器程序,它先發出一個被動開啟命令,告訴它的TCP要準備接收客戶程序的連續請求,然後服務程序就處於聽的狀態。不斷檢測是否有客戶程序發起連續請求,如有,作出響應。設客戶程序執行在主機A中,他先向自己的TCP發出主動開啟的命令,表明要向某個IP地址

    系統技術非業餘研究 » TCP連結主動關閉不發fin包奇怪行為分析

    問題描述: 多隆同學在做網路框架的時候,發現一條tcp連結在close的時候,對端會收到econnrest,而不是正常的fin包. 通過抓包發現close系統呼叫的時候,我端發出rst報文, 而不是正常的fin。這個問題比較有意思,我們來演示下: $ erl Erlang R14B03 (e

    tcp連線關閉詳解和注意事項

    注:tcp關閉連線不區分客戶端和服務端,哪一埠可以主動發起關閉連線請求。所以為了描述方便,描述中的“主動方”表示主動發起關閉連線一方,“被動方”表示被動關閉連線一方。 1. tcp關閉連線狀態轉換   上圖是tcp連線主動關閉端的狀態轉換圖: (1)應用層呼叫close函式發起關閉連線請求 (2