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()中計算出來的,其計算方式如下所示:
- int inet_release(struct socket *sock)
- {
- ......
- timeout = 0;
- if (sock_flag(sk, SOCK_LINGER) &&
- !(current->flags & PF_EXITING))
- timeout = sk->sk_lingertime;
- ......
- }
接下來我們從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)中,如下所示:
- while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {
- u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq -
- tcp_hdr(skb)->fin;
- data_was_unread += len;
- __kfree_skb(skb);
- if (data_was_unread) {
- /* Unread data was tossed, zap the connection. */
- NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
- tcp_set_state(sk, TCP_CLOSE);
- tcp_send_active_reset(sk, sk->sk_allocation);
假設套接字的接收佇列為空,接著會呼叫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()的程式碼如下:
- /* Send a fin. The caller locks the socket for us. This cannot be
- * allowed to fail queueing a FIN frame under any circumstances.
- */
- void tcp_send_fin(struct sock *sk)
- {
- struct tcp_sock *tp = tcp_sk(sk);
- struct sk_buff *skb = tcp_write_queue_tail(sk);
- int mss_now;
- /* Optimization, tack on the FIN if we have a queue of
- * unsent frames. But be careful about outgoing SACKS
- * and IP options.
- */
- mss_now = tcp_current_mss(sk);
- if (tcp_send_head(sk) != NULL) {
- TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;
- TCP_SKB_CB(skb)->end_seq++;
- tp->write_seq++;
- } else {
- /* Socket is locked, keep trying until memory is available. */
- for (;;) {
- skb = alloc_skb_fclone(MAX_TCP_HEADER,
- sk->sk_allocation);
- if (skb)
- break;
- yield();
- }
- /* Reserve space for headers and prepare control bits. */
- skb_reserve(skb, MAX_TCP_HEADER);
- /* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */
- tcp_init_nondata_skb(skb, tp->write_seq,
- TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);
- tcp_queue_skb(sk, skb);
- }
- __tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF);
- }
在傳送完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,在這個假設前提下,我們會進入到下面這段程式碼的處理中:
- if (sk->sk_state != TCP_CLOSE) {
- int orphan_count = percpu_counter_read_positive(
- sk->sk_prot->orphan_count);
- sk_mem_reclaim(sk);
- if (tcp_too_many_orphans(sk, orphan_count)) {
- if (net_ratelimit())
- printk(KERN_INFO "TCP: too many of orphaned "
- "sockets\n");
- tcp_set_state(sk, TCP_CLOSE);
- tcp_send_active_reset(sk, GFP_ATOMIC);
- NET_INC_STATS_BH(sock_net(sk),
- LINUX_MIB_TCPABORTONMEMORY);
- }
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狀態的程式碼如下所示:
- /*
- * This function implements the receiving procedure of RFC 793 for
- * all states except ESTABLISHED and TIME_WAIT.
- * It's called from both tcp_v4_rcv and tcp_v6_rcv and should be
- * address independent.
- */
- int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
- struct tcphdr *th, unsigned len)
- {
- struct tcp_sock *tp = tcp_sk(sk);
- struct inet_connection_sock *icsk = inet_csk(sk);
- int queued = 0;
- int res;
- tp->rx_opt.saw_tstamp = 0;
- ......
- res = tcp_validate_incoming(sk, skb, th, 0);
- if (res <= 0)
- return -res;
- /* step 5: check the ACK field */
- if (th->ack) {
- int acceptable = tcp_ack(sk, skb, FLAG_SLOWPATH) > 0;
- switch (sk->sk_state) {
- ......
- case TCP_FIN_WAIT1:
- if (tp->snd_una == tp->write_seq) {
- tcp_set_state(sk, TCP_FIN_WAIT2);
- sk->sk_shutdown |= SEND_SHUTDOWN;
- dst_confirm(sk->sk_dst_cache);
- if (!sock_flag(sk, SOCK_DEAD))
- /* Wake up lingering close() */
- sk->sk_state_change(sk);
- else {
- int tmo;
- if (tp->linger2 < 0 ||
- (TCP_SKB_CB(skb)->end_seq != TCP_SKB_CB(skb)->seq &&
- after(TCP_SKB_CB(skb)->end_seq - th->fin, tp->rcv_nxt))) {
- tcp_done(sk);
-
相關推薦
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