天天看點

TCP 連接配接狀态TCP十一種狀态TCP狀态遷移TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用 TCP通信中伺服器處理用戶端意外斷開

TCP 連接配接狀态TCP十一種狀态TCP狀态遷移TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用 TCP通信中伺服器處理用戶端意外斷開

全部11種狀态

    1. 用戶端獨有的:(1)syn_sent (2)fin_wait1 (3)fin_wait2 (4)closing (5)time_wait 。

    2. 伺服器獨有的:(1)listen (2)syn_rcvd (3)close_wait (4)last_ack 。

    3. 共有的:(1)closed (2)established 。

各個狀态的意義如下: 

各個狀态詳解:

主動關閉端接到ack後,就進入了fin-wait-2 ./* connection is closed, and the socket is waiting for a shutdown from the remote end. 從遠端tcp等待連接配接中斷請求 */

這就是著名的半關閉的狀态了,這是在關閉連接配接時,用戶端和伺服器兩次握手之後的狀态。在這個狀态下,應用程式還有接受資料的能力,但是已經無法發送資料,但是也有一種可能是,用戶端一直處于fin_wait_2狀态,而伺服器則一直處于wait_close狀态,而直到應用層來決定關閉這個狀态。

被動關閉(passive close)端tcp接到fin後,就發出ack以回應fin請求(它的接收也作為檔案結束符傳遞給上層應用程式),并進入close_wait. /* the remote end has shut down, waiting for the socket to close. 等待從本地使用者發來的連接配接中斷請求 */

比較少見./* both sockets are shut down but we still don't have all our data sent. 等待遠端tcp對連接配接中斷的确認 */

被動關閉端一段時間後,接收到檔案結束符的應用程式将調用close關閉連接配接。這導緻它的tcp也發送一個 fin,等待對方的ack.就進入了last-ack . /* the remote end has shut down, and the socket is closed. waiting for acknowledgement. 等待原來發向遠端tcp的連接配接中斷請求的确認 */

在主動關閉端接收到fin後,tcp就發送ack包,并進入time-wait狀态。/* the socket is waiting after close to handle packets still in the network.等待足夠的時間以確定遠端tcp接收到連接配接中斷請求的确認 */

time_wait等待狀态,這個狀态又叫做2msl狀态,說的是在time_wait2發送了最後一個ack資料報以後,要進入time_wait狀态,這個狀态是防止最後一次握手的資料報沒有傳送到對方那裡而準備的(注意這不是四次握手,這是第四次握手的保險狀态)。這個狀态在很大程度上保證了雙方都可以正常結束,但是,問題也來了。

由于插口的2msl狀态(插口是ip和端口對的意思,socket),使得應用程式在2msl時間内是無法再次使用同一個插口的,對于客戶程式還好一些,但是對于服務程式,例如httpd,它總是要使用同一個端口來進行服務,而在2msl時間内,啟動httpd就會出現錯誤(插口被使用)。為了避免這個錯誤,伺服器給出了一個平靜時間的概念,這是說在2msl時間内,雖然可以重新啟動伺服器,但是這個伺服器還是要平靜的等待2msl時間的過去才能進行下一次連接配接。

被動關閉端在接受到ack包後,就進入了closed的狀态。連接配接結束./* the socket is not being used. 沒有任何連接配接狀态 */

大家對netstat -a指令很熟悉,但是,你注意到state一欄沒,基本上顯示着established,time_wait,close_wait等,這些到底是 什麼意思呢?

大家很明白tcp初始化連接配接三次握手吧:發syn包,然後傳回syn/ack包,再發ack包,連接配接正式建立。但是這裡有點出入,當請求者收到sys /ack包後,就開始建立連接配接了,而被請求者第三次握手結束後才建立連接配接。但是大家明白關閉連接配接的工作原理嗎?關閉連接配接要四次握手:發fin包,ack 包,fin包,ack包,四次握手!!為什麼呢,因為tcp連接配接是全雙工,我關了你的連接配接,并不等于你關了我的連接配接。

用戶端正常情況下tcp狀态遷移:

<code>closed-&gt;syn_sent-&gt;established-&gt;fin_wait_1-&gt;fin_wait_2-&gt;time_wait-&gt;closed</code>

伺服器正常情況下tcp狀态遷移:

<code>closed-&gt;listen-&gt;syn收到 -&gt;established-&gt;close_wait-&gt;last_ack-&gt;closed</code>

當用戶端開始連接配接時,伺服器還處于listening,

用戶端發一個syn包後,他就處于syn_sent狀态,伺服器就處于sys收到狀态,

然後互相确認進入連接配接狀态established.

當用戶端請求關閉連接配接時,用戶端發送一個fin包後,用戶端就進入fin_wait_1狀态,等待對方的确認包,

伺服器發送一個ack包給客戶,用戶端收到ack包後結束fin_wait_1狀态,進入fin_wait_2狀态,等待伺服器發過來的關閉請求,

伺服器發一個fin包後,進入close_wait狀态,

當用戶端收到伺服器的fin包,fin_wait_2狀态就結束,然後給伺服器端的fin包給以一個确認包,用戶端這時進入time_wait,

當伺服器收到确認包後,close_wait狀态結束了,

這時候伺服器端真正的關閉了連接配接.但是用戶端還在time_wait狀态下,

什麼時候結束呢.我在這裡再講到一個新名詞:2msl等待狀态,其實time_wait就是2msl等待狀态,

為什麼要設定這個狀态,原因是有足夠的時間讓ack包到達伺服器端,如果伺服器端沒收到ack包,逾時了,然後重新發一個fin包,直到伺服器收到ack 包.

time_wait狀态等待時間是在tcp重新啟動後不連接配接任何請求的兩倍.

大家有沒有發現一個問題:如果對方在第三次握手的時候出問題,如發fin包的時候,不知道什麼原因丢了這個包,然而這邊一直處在fin_wait_2狀 态,而且tcp/ip并沒有設定這個狀态的過期時間,那他一直會保留這個狀态下去,越來越多的fin_wait_2狀态會導緻系統崩潰.

上面我碰到的這個問題主要因為tcp的結束流程未走完,造成連接配接未釋放。現設用戶端主動斷開連接配接,流程如下:

<code>client 消息 server  close() ------ fin -------&gt; fin_wait1 close_wait &lt;----- ack ------- fin_wait2  close() &lt;------ fin ------  time_wait last_ack   ------ ack -------&gt;  closed closed</code>

由于server的socket在用戶端已經關閉時而沒有調用關閉,造成伺服器端的連接配接處在“挂起”狀态,而用戶端則處在等待應答的狀态上。

此問題的典型特征是:一端處于fin_wait2 ,而另一端處于close_wait。不過,根本問題還是程式寫的不好,有待提高

-------------------------------------------------------------------------

close_wait,tcp的癌症,tcp的朋友。

close_wait狀态的生成原因

首先我們知道,如果我們的伺服器程式apache處于close_wait狀态的話,說明套接字是被動關閉的!

因為如果是client端主動斷掉目前連接配接的話,那麼雙方關閉這個tcp連接配接共需要四個packet:

client ---&gt; fin ---&gt; server

client &lt;--- ack &lt;--- server

這時候client端處于fin_wait_2狀态;而server 程式處于close_wait狀态。

client &lt;--- fin &lt;--- server

這時server 發送fin給client,server 就置為last_ack狀态。

client ---&gt; ack ---&gt; server

client回應了ack,那麼server 的套接字才會真正置為closed狀态。

server 程式處于close_wait狀态,而不是last_ack狀态,說明還沒有發fin給client,那麼可能是在關閉連接配接之前還有許多資料要發送或者其 他事要做,導緻沒有發這個fin packet。

通常來說,一個close_wait會維持至少2個小時的時間。如果有個流氓特地寫了個程式,給你造成一堆的 close_wait,消耗你的資源,那麼通常是等不到釋放那一刻,系統就已經解決崩潰了。

隻能通過修改一下tcp/ip的參數,來縮短這個時間:修改tcp_keepalive_*系列參數有助于解決這個 問題。

解決這個問題的方法是修改系統的參數,系統預設逾時時間的是7200秒,也就是2小時, 這個太大了,可以修改如下幾個參數:

<code>sysctl -w net.ipv4.tcp_keepalive_time=30 sysctl -w net.ipv4.tcp_keepalive_probes=2 sysctl -w net.ipv4.tcp_keepalive_intvl=2</code>

然後,執行sysctl指令使修改生效。

連接配接程序是通過一系列狀态表示的,這些狀态有:

listen,syn-sent,syn-received,established,fin-wait-1,fin-wait-2,close- wait,closing,last-ack,time-wait和closed

TCP 連接配接狀态TCP十一種狀态TCP狀态遷移TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用 TCP通信中伺服器處理用戶端意外斷開

1、建立連接配接協定(三次握手)

(1)客戶

端發送一個帶syn标志的tcp封包到伺服器。這是三次握手過程中的封包1。

(2)

伺服器端回應用戶端的,這是三次握手中的第2個封包,這個封包同時帶ack标志和syn标

志。是以它表示對剛才用戶端syn封包的回應;同時又标志syn給用戶端,詢問用戶端是否準備好進行資料通

訊。

(3)

客戶必須再次回應服務段一個ack封包,這是封包段3。

2、連接配接終止協定(四次握手)

   由于tcp連

接是全雙工的,是以每個方向都必須單獨進行關閉。這原則是當一方完成它的資料發送任務後就能發送一個fin來終

止這個方向的連接配接。收到一個 fin隻意味着這一方向上沒有資料流動,一個tcp連接配接

在收到一個fin後仍能發送資料。首先進行關閉的一方将執行主動關閉,而另一方執行被動關閉。

 (1) tcp客

戶端發送一個fin,用來關閉客戶到伺服器的資料傳送(封包段4)。

 (2)

伺服器收到這個fin,它發回一個ack,确認序号為收到的序号加1(封包段5)。和syn一

樣,一個fin将占用一個序号。

 (3)

伺服器關閉用戶端的連接配接,發送一個fin給用戶端(封包段6)。

 (4)

客戶段發回ack封包确認,并将确認序号設定為收到序号加1(封包段7)。

closed: 這個沒什麼好說的了,表示初始狀态。

listen: 這個也是非常容易了解的一個狀态,表示伺服器端的某個socket處

于監聽狀态,可以接受連接配接了。

syn_rcvd: 這個狀态表示接受到了syn報

過程中最後一個ack封包不予發送。是以這種狀态時,當收到用戶端的ack封包

後,它會進入到established狀态。

syn_sent: 這個狀态與syn_rcvd遙想呼應,當用戶端socket執行connect連接配接時,它首先發送syn封包,是以也随即它會進入到了syn_sent狀态,并等待服務端的發送三次握手中的第2個封包。syn_sent狀态表示用戶端已發送syn封包。

established:這個容易了解了,表示連接配接已經建立了。

fin_wait_1: 這個狀态要好好解釋一下,其實fin_wait_1和fin_wait_2狀态的真正含義都是表示等待對方的fin報

文。而這兩種狀态的差別是:fin_wait_1狀态實際上是當socket在established狀态時,它想主動關閉連接配接,向對方發送了fin封包,此時該socket即進入到fin_wait_1狀态。而當對方回應ack封包後,則進入到fin_wait_2狀态,當然在實際的正常情況

下,無論對方何種情況下,都應該馬上回應ack封包,是以fin_wait_1狀态一般是比較難見到的,而fin_wait_2狀态還有時常常可以用netstat看到。

fin_wait_2:上面已經詳細解釋了這種狀态,實際上fin_wait_2狀态下的socket,表示半連接配接,也即有一方要求close連接配接,但另外還告訴對方,我暫時還有點

資料需要傳送給你,稍後再關閉連接配接。

time_wait: 表示收到了對方的fin報

文,并發送出了ack封包,就等2msl後即可回到closed可用狀态了。如果fin_wait_1狀态下,收到了對方同時帶fin标

志和ack标志的封包時,可以直接進入到time_wait狀态,而無須經過fin_wait_2狀态。

closing: 這種狀态比較特殊,實際情況中應該是很少見,屬于一種比較罕見的例外狀态。正常情況下,當你發

送fin封包後,按理來說是應該先收到(或同時收到)對方的ack報

文,再收到對方的fin封包。但是closing狀态表示你發送fin封包後,并沒有收到對方的ack報

文,反而卻也收到了對方的fin封包。什麼情況下會出現此種情況呢?其實細想一下,也不難得出結論:那就是如果雙方幾乎在同時close一

個socket的話,那麼就出現了雙方同時發送fin封包的情況,也即會出現closing狀态,表示雙方都正在關閉socket連接配接。

close_wait: 這種狀态的含義其實是表示在等待關閉。怎麼了解呢?當對方close一

個socket後發送fin封包給自己,你系統毫無疑問地會回應一個ack封包

給對方,此時則進入到close_wait狀态。接下來呢,實際上你真正需要考慮的事情是察看你是否還有資料發送給對方,如果沒有的話,

那麼你也就可以close這個socket,發送fin封包給對方,也即關閉連接配接。是以你在close_wait狀态下,需要完成的事情是等待你去關閉連接配接。

last_ack: 這個狀态還是比較容易好了解的,它是被動關閉一方在發送fin報

文後,最後等待對方的ack封包。當收到ack封包後,也即可以進入到closed可用狀态了。

最後有2個問題

的回答,我自己分析後的結論(不一定保證100%正确)

1、 為什麼建立連接配接協定是三次握手,而關閉連接配接卻是四次握手呢?

這是因為服務端的listen狀态下的socket當收到syn封包的建連請求後,它可以把ack和syn(ack起

應答作用,而syn起同步作用)放在一個封包裡來發送。但關閉連接配接時,當收到對方的fin封包

通知時,它僅僅表示對方沒有資料發送給你了;但未必你所有的資料都全部發送給對方了,是以你可以未必會馬上會關閉socket,也即你可能還需要發送一些資料給對方之後,再發送fin封包給對方來表示你同意現在可以關閉連接配接了,是以它這裡的ack封包

和fin封包多數情況下都是分開發送的。

2、 為什麼time_wait狀态還需要等2msl後才能傳回到closed狀

态?

這是因為:雖然雙方 都同意關閉連接配接了,而且握手的4個封包也都協調和發送完畢,按理可以直接回到closed狀

态(就好比從syn_send狀态到establish狀态那樣);但是因為我們必須要假想網絡是不可靠的,你無法保證你最後發送的ack報

文會一定被對方收到,是以對方處于last_ack狀态下的socket可能會因為逾時未收到ack封包,而重發fin報

文,是以這個time_wait狀态的作用就是用來重發可能丢失的ack報

文,并保證于此。

     斷開連接配接的時候,

當發起主動關閉的左邊這方發送一個fin過去後,

右邊被動關閉的這方要回應一個ack,這個ack是tcp回應的,而不是應用程式發送的,

此時,被動關閉的一方就處于close_wait狀态了。

如果此時被動關閉的這一方不再繼續調用closesocket,那麼他就不會發送接下來的fin,導緻自己老是處于close_wait。

隻有被動關閉的這一方調用了 closesocket,才會發送一個fin給主動關閉的這一方,同時也使得自己的狀态變遷為last_ack。 

比如被動關閉的是用戶端

當對方調用closesocket的時候,你的程式正在 

很多人就是忘記了那句closesocket,這種代碼太常見了。

我的了解,

當主動關閉的一方發送fin到被動關閉這邊後,被動關閉這邊的tcp馬上回應一個ack過去,同時向上面應用程式送出一個error,

導緻上面的socket的send或者recv傳回socket_error.

正常情況下,如果上面在傳回socket_error後調用了closesocket, 那麼被動關閉的者一方的tcp就會發送一個fin過去,自己的狀态就變遷到last_ack.

伺服器上出現大量的close_wait的例子和解決方法(例子從網上找的,基本差不多)

程序被kill時,會釋放占用的所有連結句柄。

該問題的出現原因網上到處都是,也就是socket的client端出現異常沒有close就退出了。

tcp三次握手和四次握手的狀态遷移

在任意時刻發生丢包或者重複包時,tcp/ip的處理政策

linux系統調用對tcp/ip可以進行哪些設定,主要針對哪些方面的優化

tcp基本知識點

tcp由rfc793、rfc1122、rfc1323、rfc2001、rfc2018以及rfc2581定義

tcp提供可靠性保證

tcp發送資料後,要求對方傳回确認,如果沒有收到确認,tcp會進行重傳,數次重傳失敗後,tcp才會放棄

tcp含有動态估算rtt(round-trip time)的算法,可以根據網絡擁塞情況動态調整rtt,重新傳等待時間就是使用rtt來确定的

tcp通過給所發送資料的每一個位元組關聯一個序列号進行排序,進而處理分包非順序到達和重複包的情況

tcp提供流量控制。tcp總能告訴對方自己還能接收多少位元組的資料(advertised window——通告視窗),防止接收緩沖區溢出。視窗随着資料的到來和從緩沖區中取走資料而動态變化。

tcp是全雙工的。是以tcp必須跟蹤每個方向資料流的狀态資訊(如序列号和通告視窗的大小)

TCP 連接配接狀态TCP十一種狀态TCP狀态遷移TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用 TCP通信中伺服器處理用戶端意外斷開

上面的狀态遷移圖,基本上把tcp三次握手和四次握手的大緻流程描述的非常清楚了,下面我們用文字将上面的過程描述一遍,并對異常情況進行分析:

TCP 連接配接狀态TCP十一種狀态TCP狀态遷移TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用 TCP通信中伺服器處理用戶端意外斷開

伺服器主動進入listen狀态,監聽端口

客戶發送第一次握手請求,發送完畢後進入syn_send狀态,等待伺服器響應

伺服器收到第一次握手請求,向客戶确認第一次請求,連帶發送第二次握手請求,發送完畢後進入syn_recv狀态,等待客戶響應

客戶收到确認和第二次握手請求,對第二次握手請求進行确認(第三次握手),發送确認完畢後,進入established狀态

伺服器收到對第二次握手請求的确認之後(第三次握手),進入established狀态

至此,三次握手完成,客戶-伺服器完成連接配接的建立,開始資料通信

三次握手和程式設計的關聯:

伺服器通過socket()、bind()和listen()來完成closed狀态到listen狀态的轉化,稱為被動打開。被動打開完成之後,accept()阻塞,等待客戶請求

客戶通過connect()進行主動打開。這引起客戶tcp發送一個syn分節,用于通知伺服器客戶将在連接配接中發送資料的初始序列号(一般syn分節不包含任何資料,隻有tcp和ip的頭部資訊)

伺服器以單個分節,同時對客戶的syn序列号進行确認,并發送自己的syn序列号(此時accept()還在阻塞中)

客戶對伺服器的syn資料進行确認。客戶在收到伺服器syn并進行确認之後,connect()傳回

伺服器收到客戶的确認,accept()傳回

三次握手時的異常:

第一次握手丢包:預設情況下,connect()是阻塞式的,如果請求無法發送到伺服器,那麼connect會進行一段很長時間的等待和重試(重傳次數和時間間隔我們暫且不去深究),此時我們可以使用通過設定so_sndtimeo來為connect設定逾時以減少connect的等待時間

第二次握手丢包:對于客戶來說,依然是connect逾時,是以處理方式和第一次握手丢包是一樣的。對于伺服器來說,由于收不到第三次握手請求,是以會進行等待重傳,直到多次重傳失敗後,關閉半連接配接。

這裡需要提一下的是,伺服器會維護一個半連接配接隊列,用于等待客戶的第三次握手請求。當收到第三次握手請求或者多次重傳失敗後,伺服器會将該半連接配接從隊列中删除。(這裡暫且不去深究半連接配接隊列的等待重新政策和配置)

我們經常聽說的ddos攻擊,就可以這個環節實作,syn flood就是一種常見的ddos攻擊方式。簡單來說,syn flood就是隻發送第一次握手請求後,就關閉連接配接,将伺服器的半連接配接隊列占滿,進而讓正常使用者無法得到服務。

第三次握手丢包:由于客戶在發送第三次握手包後,不再等待确認,就直接進入了established狀态,是以一旦第三次握手失敗,客戶和伺服器的狀态就不同步了。當然,此時伺服器會進行多次重發,一旦客戶再次收到syn+ack(第二次握手請求),會再次确認。不過,如果第三次握手一直失敗,則會出現,客戶已經建立連接配接,而伺服器關閉連接配接的情況。随後,一旦客戶向伺服器發送資料,則會收到一條rst回應,告訴使用者連接配接已經重置,需要重新進行三次握手。

rst和sigpipe:有過網絡程式設計經驗的人都知道在寫網絡通信的時候,需要屏蔽sigpipe信号,否則的話,一旦收到pipe信号會導緻程式異常退出。其實這個sigpipe就是由于write()的時候,我們自己的狀态是established而對方的狀态不是established,那麼對方就會給我們一個rst回應,收到這個回應之後,系統就會自動生成一個pipe信号。

客戶發送fin請求(第一次握手),通知關閉連接配接,然後進入fin_wait1狀态

伺服器收到fin請求後,發送ack(第二次握手),對客戶的fin進行确認,然後進入close_wait狀态

伺服器進行一些收尾工作,然後主動相客戶發送fin請求(第三次握手),通知關閉連接配接,然後進入last_ack狀态

客戶收到fin,對fin進行确認(第四次握手),并進入time_wait狀态

伺服器收到客戶的确認,關閉連接配接

客戶等待一段時間後,關閉連接配接

四次握手和程式設計的關聯:

客戶調用close()執行主動關閉,發送fin到伺服器,fin表示不會再發送資料了

伺服器收到fin進行被動關閉,由tcp對fin進行确認。fin作為檔案結束符,傳遞給recv()。因為收到fin以後就意味着不會再有資料了

一段時間後,伺服器調用close()關閉自己的socket,并發送fin給客戶,宣告自己不會再發送資料了

客戶收到fin後,不再确認,等待一段時間後,自行關閉自己的socket

說明:

tcp是全雙工的連接配接,是以關閉的過程必須是兩個方向都關閉才行,這也就是為什麼需要兩次不同方向的fin

fin并不像syn一樣,一定是一個獨立的包,有時fin會随着資料一起發送,而對方也有可能将ack和fin放在一個包中進行發送,這成為捎帶。捎帶的機制在資料傳輸中也會出現。

四次握手的過程不像三次握手一樣,一定是由客戶發起。雖然一般來說,是由客戶發起,但是某些協定(例如http)則是伺服器執行主動關閉

close_wait:close_wait的狀态位于向對方确認fin之後,向對方發送fin之前,這段時間由于對方已經發送了fin,也就表示不會再收到資料,但是這并不表示自己沒有資料要發,畢竟隻有在發送了fin之後,才表示發送完畢。是以,close_wait這段時間主要的工作就是給對方發送必要的資料,對自己的資料進行收尾,所有工作結束之後,調用close(),發送fin,等待last_ack

time_wait:存在time_wait狀态有如下兩個理由:

實作終止tcp全雙工連接配接的可靠性:假如last-ack丢失,對方重發,但是自己已經關閉連接配接,那麼會傳回一個rst包,對放會将其解釋為錯誤,進而無法正常關閉。也就是說,time_wait的作用之一就是解決last-ack可能丢包的情況,因為在有些網絡不好的情況下,不得不重發last-ack

允許老的網絡分組在網絡中消逝:2msl的時間足夠讓所有的fin資料在網絡中消失,如果不等待,并立即開始一個新的連接配接,有可能出現老fin關閉了新連接配接的情況,因為在ip和端口一直的情況下,很難區分一個資料包是屬于哪一次連接配接的

第一次握手丢包:fin_wait1丢失會導緻客戶重傳,如果多次重傳失敗,則客戶逾時關閉連接配接,而伺服器依然保持established狀态。如果伺服器主動發送資料,則會收到一個rst包,重置連接配接。設定keepalive道理相同,核心是要求伺服器主動發資料。如果伺服器永遠不會主動發資料,那麼就會一直保持這樣一個“假連接配接”

第二次握手丢包:由于伺服器第二次握手不會重發,是以即使丢包也不管,直接向對方發送fin,此時客戶執行”同時關閉“的流程(這個流程後面再說),等待time_wait時間後關閉。在客戶進入time_wait之後,自己由于fin沒有相應,會重發,如果被客戶time_wait收到并發送last-ack,則流程正常結束,如果反複重發沒有響應,那麼逾時關閉

第三次握手丢包:伺服器會持續等待在last_ack狀态,而客戶會持續等待在fin_wait2狀态,最後雙方逾時關閉

第四次握手丢包:用戶端進入time_wait狀态,等待2msl,伺服器由于收不到last-ack則進行重發,如果多次重發失敗,則逾時關閉(這個流程和第二次握手丢包的後半段狀态是一樣的)

除了上面的順序打開,和順序關閉方式,tcp還有同時打開和同時關閉的流程:

同時打開流程:(引自:http://hi.baidu.com/psorqkxcsfbbghd/item/70f3bd91943b9248f14215cd)

兩個應用程式同時執行主動打開的情況是可能的,雖然發生的可能性較低。每一端都發送一個syn,并傳遞給對方,且每一端都使用對端所知的端口作為本地端口。

例如:

主機a中一應用程式使用7777作為本地端口,并連接配接到主機b 8888端口做主動打開。

主機b中一應用程式使用8888作為本地端口,并連接配接到主機a 7777端口做主動打開。

tcp協定在遇到這種情況時,隻會打開一條連接配接。

這個連接配接的建立過程需要4次資料交換,而一個典型的連接配接建立隻需要3次交換(即3次握手)

但多數伯克利版的tcp/ip實作并不支援同時打開

TCP 連接配接狀态TCP十一種狀态TCP狀态遷移TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用 TCP通信中伺服器處理用戶端意外斷開

同時關閉流程:(引自:http://hi.baidu.com/psorqkxcsfbbghd/item/70f3bd91943b9248f14215cd)

如果應用程式同時發送fin,則在發送後會首先進入fin_wait_1狀态。在收到對端的fin後,回複一個ack,會進入closing狀态。在收到對端的ack後,進入time_wait狀态。這種情況稱為同時關閉。

同時關閉也需要有4次封包交換,與典型的關閉相同。

TCP 連接配接狀态TCP十一種狀态TCP狀态遷移TCP連接配接過程是狀态的轉換,促使發生狀态轉換的是使用者調用 TCP通信中伺服器處理用戶端意外斷開

       如果上面的順序流程已經非常清楚的話,那麼這兩個同時打開、同時關閉的狀态圖就不難了解了……

       大家可以通過這兩張圖來對應上面socket關閉流程中,“第二次握手失敗”的解釋,其實也就不難了解,為什麼客戶會進入同時關閉狀态了。因為客戶在發送了fin之後,沒有等到ack,而是等到了伺服器的fin,自然符合同步關閉的流程。

如果tcp連接配接被對方正常關閉,也就是說,對方是正确地調用了closesocket(s)或者shutdown(s)的話,那麼上面的recv或send調用就能馬上傳回,并且報錯。這是由于close socket(s)或者shutdown(s)有個正常的關閉過程,會告訴對方“tcp連接配接已經關閉,你不需要再發送或者接受消息了”。

但是,如果意外斷開,用戶端(3g的移動裝置)并沒有正常關閉socket。雙方并未按照協定上的四次揮手去斷開連接配接。

那麼這時候正在執行recv或send操作的一方就會因為沒有任何連接配接中斷的通知而一直等待下去,也就是會被長時間卡住。

像這種如果一方已經關閉或異常終止連接配接,而另一方卻不知道,我們将這樣的tcp連接配接稱為半打開 的。

解決意外中斷辦法都是利用保活機制。而保活機制分又可以讓底層實作也可自己實作。

簡單的說也就是在自己的程式中加入一條線程,定時向對端發送資料包,檢視是否有ack,如果有則連接配接正常,沒有的話則連接配接斷開

一般由用戶端發送心跳包,服務端并不回應心跳,隻是定時輪詢判斷一下與上次的時間間隔是否逾時(逾時時間自己設定)。伺服器并不主動發送是不想增添伺服器的通信量,減少壓力。

但這會出現三種情況:

情況1.

用戶端由于某種網絡延遲等原因很久後才發送心跳(它并沒有斷),這時伺服器若利用自身設定的逾時判斷其已經斷開,而後去關閉socket。若用戶端有重連機制,則用戶端會重新連接配接。若不确定這種方式是否關閉了原本正常的用戶端,則在shutdown的時候一定要選擇send,表示關閉發送通道,伺服器還可以接收一下,萬一用戶端正在發送比較重要的資料呢,是不?

情況2.

用戶端很久沒傳心跳,确實是自身斷掉了。在其重新開機之前,服務端已經判斷出其逾時,并主動close,則四次揮手成功互動。

情況3.

用戶端很久沒傳心跳,确實是自身斷掉了。在其重新開機之前,服務端的輪詢還未判斷出其逾時,在未主動close的時候該用戶端已經重新連接配接。

這時候若用戶端斷開的時候發送了fin包,則服務端将會處于close_wait狀态;

這時候若用戶端斷開的時候未發送fin包,則服務端處還是顯示established狀态;

而新連接配接上來的用戶端(也就是剛才斷掉的重新連上來了)在服務端肯定是established;這時候就有個問題,若利用輪詢還未檢測出上條舊連接配接已經逾時(這很正常,timer總有個間隔吧),而在這時,用戶端又重複的上演情況3,那麼服務端将會出現大量的假的established連接配接和close_wait連接配接。

最終結果就是新的其他用戶端無法連接配接上來,但是利用netstat還是能看到一條連接配接已經建立,并顯示established,但始終無法進入程式代碼。個人最初感覺導緻這種情況是因為假的established連接配接和 close_wait連接配接會占用較大的系統資源,程式無法再次建立連接配接(因為每次我發現這個問題的時候我隻連了10個左右用戶端卻已經有40多條無效連接配接)。而最近幾天測試卻發現有一次程式内隻連接配接了2,3個裝置,但是有8條左右的虛連接配接,此時已經連接配接不了新用戶端了。這時候我就覺得我想錯了,不可能這幾條連接配接就占用了大量連接配接把,如果說幾十條還有可能。但是能肯定的是,這個問題的産生絕對是裝置在不停的重新開機,而伺服器這邊又是簡單的輪詢,并不能及時處理,暫時還未能解決。

其實keepalive的原理就是tcp内嵌的一個心跳包,

以伺服器端為例,如果目前 server 端檢測到超過一定時間(預設是 7,200,000 milliseconds ,也就是 2 個小時)沒有資料傳輸,那麼會向 client 端發送一個 keep-alive packet (該 keep-alive packet 就是 ack和 目前 tcp 序列号減一的組合),此時 client 端應該為以下三種情況之一:

1. client 端仍然存在,網絡連接配接狀況良好。此時 client 端會傳回一個 ack 。server 端接收到 ack 後重置計時器(複位存活定時器),在 2 小時後再發送探測。如果 2 小時内連接配接上有資料傳輸,那麼在該時間基礎上向後推延 2 個小時。

2. 用戶端異常關閉,或是網絡斷開。在這兩種情況下, client 端都不會響應。伺服器沒有收到對其發出探測的響應,并且在一定時間(系統預設為 1000 ms )後重複發送 keep-alive packet ,并且重複發送一定次數( 2000 xp 2003 系統預設為 5 次 , vista 後的系統預設為 10 次)。

3. 用戶端曾經崩潰,但已經重新開機。這種情況下,伺服器将會收到對其存活探測的響應,但該響應是一個複位,進而引起伺服器對連接配接的終止。對于應用程式來說,2小時的空閑時間太長。是以,我們需要手工開啟keepalive功能并設定合理的keepalive參數。

全局設定可更改 /etc/sysctl.conf ,加上:

net.ipv4.tcp_keepalive_intvl = 20

net.ipv4.tcp_keepalive_probes = 3

net.ipv4.tcp_keepalive_time = 60

在程式中設定如下:

在程式中表現為,當tcp檢測到對端socket不再可用時(不能發出探測包,或探測包沒有收到ack的響應包),select會傳回socket可讀,并且在recv時傳回-1,同時置上errno為etimedout.

繼續閱讀