主要部分,四次握手:
斷開連線其實從我的角度看不區分客戶端和伺服器端,任何一方都可以呼叫close(or closesocket)之類
的函式開始主動終止乙個連線。這裡先暫時說正常情況。當呼叫close函式斷開乙個連線時,主動斷開的
一方傳送fin(finish報文給對方。有了之前的經驗,我想你應該明白我說的fin報文時什麼東西。也就是
乙個設定了fin標誌位的報文段。fin報文也可能附加使用者資料,如果這一方還有資料要傳送時,將資料附
加到這個fin報文時完全正常的。之後你會看到,這種附加報文還會有很多,例如ack報文。我們所要把握
的原則是,tcp肯定會力所能及地達到最大效率,所以你能夠想到的優化方法,我想tcp都會想到。
當被動關閉的一方收到fin報文時,它會傳送ack確認報文(對於ack這個東西你應該很熟悉了)。這裡有個
東西要注意,因為tcp是雙工的,也就是說,你可以想象一對tcp連線上有兩條資料通路。當傳送fin報文
時,意思是說,傳送fin的一端就不能傳送資料,也就是關閉了其中一條資料通路。被動關閉的一端傳送
了ack後,應用層通常就會檢測到這個連線即將斷開,然後被動斷開的應用層呼叫close關閉連線。
我可以告訴你,一旦當你呼叫close(or closesocket),這一端就會傳送fin報文。也就是說,現在被動
關閉的一端也傳送fin給主動關閉端。有時候,被動關閉端會將ack和fin兩個報文合在一起傳送。主動
關閉端收到fin後也傳送ack,然後整個連線關閉(事實上還沒完全關閉,只是關閉需要交換的報文傳送
完畢),四次握手完成。如你所見,因為被動關閉端可能會將ack和fin合到一起傳送,所以這也算不上
嚴格的四次握手---四個報文段。
在前面的文章中,我一直沒提tcp的狀態轉換。在這裡我還是在猶豫是不是該將那張四處通用的圖拿出來,
不過,這裡我只給出斷開連線時的狀態轉換圖,摘自:
給出乙個正常關閉時的windump資訊:
:38.819856
ip cd
-zhangmin.
1748
>
220.181
.37.55.80
: f 1:
1(0) ack
1win
65535
00:38.863989
ip 220.181
.37.55.80
>cd-
zhangmin.
1748
: f 1:
1(0) ack
2win
2920
00:38.864412
ip cd
-zhangmin.
1748
>
220.181
.37.55.80
: . ack
2win
65535
補充細節:
關於以上的四次握手,我補充下細節:
1. 預設情況下(不改變socket選項),當你呼叫close( or closesocket,以下說close不再重複)時,如果
傳送緩衝中還有資料,tcp會繼續把資料傳送完。
2. 傳送了fin只是表示這端不能繼續傳送資料(應用層不能再呼叫send傳送),但是還可以接收資料。
3. 應用層如何知道對端關閉?通常,在最簡單的阻塞模型中,當你呼叫recv時,如果返回0,則表示對端
關閉。在這個時候通常的做法就是也呼叫close,那麼tcp層就傳送fin,繼續完成四次握手。如果你不呼叫
close,那麼對端就會處於fin_wait_2狀態,而本端則會處於close_wait狀態。這個可以寫**試試。
4. 在很多時候,tcp連線的斷開都會由tcp層自動進行,例如你ctrl+c終止你的程式,tcp連線依然會正常關
閉,你可以寫**試試。
特別的time_wait狀態:
從以上tcp連線關閉的狀態轉換圖可以看出,主動關閉的一方在傳送完對對方fin報文的確認(ack)報文後,
會進入time_wait狀態。time_wait狀態也稱為2msl狀態。
什麼是2msl?msl即maximum segment lifetime,也就是報文最大生存時間,引用為什麼需要2msl?根據中的說法,有兩個原因:
其一,保證傳送的ack會成功傳送到對方,如何保證?我覺得可能是通過超時計時器傳送。這個就很難用
**演示了。
其二,報文可能會被混淆,意思是說,其他時候的連線可能會被當作本次的連線。直接引用
的說法:the second is to provide a 「buffering period」 between the end of this connection
and any subsequent ones. if not for this period, it is possible that packets from different
connections could be mixed, creating confusion.
time_wait狀態所帶來的影響:
當某個連線的一端處於time_wait狀態時,該連線將不能再被使用。事實上,對於我們比較有現實意義的
是,這個埠將不能再被使用。某個埠處於time_wait狀態(其實應該是這個連線)時,這意味著這個tcp
連線並沒有斷開(完全斷開),那麼,如果你bind這個埠,就會失敗。
對於伺服器而言,如果伺服器突然crash掉了,那麼它將無法再2msl內重新啟動,因為bind會失敗。解決這
個問題的乙個方法就是設定socket的so_reuseaddr選項。這個選項意味著你可以重用乙個位址。
對於time_wait的插曲:
當建立乙個tcp連線時,伺服器端會繼續用原有埠監聽,同時用這個埠與客戶端通訊。而客戶端預設情況
下會使用乙個隨機埠與伺服器端的監聽埠通訊。有時候,為了伺服器端的安全性,我們需要對客戶端進行
驗證,即限定某個ip某個特定埠的客戶端。客戶端可以使用bind來使用特定的埠。
對於伺服器端,當設定了so_reuseaddr選項時,它可以在2msl內啟動並listen成功。但是對於客戶端,當使
用bind並設定so_reuseaddr時,如果在2msl內啟動,雖然bind會成功,但是在windows平台上connect會失敗。
而在linux上則不存在這個問題。(我的實驗平台:winxp, ubuntu7.10)
要解決windows平台的這個問題,可以設定so_linger選項。so_linger選項決定呼叫close時,tcp的行為。
so_linger涉及到linger結構體,如果設定結構體中l_onoff為非0,l_linger為0,那麼呼叫close時tcp連線
會立刻斷開,tcp不會將傳送緩衝中未傳送的資料傳送,而是立即傳送乙個rst報文給對方,這個時候tcp連
接就不會進入time_wait狀態。
如你所見,這樣做雖然解決了問題,但是並不安全。通過以上方式設定so_linger狀態,等同於設定so_dontlinger
狀態。
斷開連線時的意外:
這個算不上斷開連線時的意外,當tcp連線發生一些物理上的意外情況時,例如網線斷開,linux上的tcp實現
會依然認為該連線有效,而windows則會在一定時間後返回錯誤資訊。
這似乎可以通過設定so_keepalive選項來解決,不過不知道這個選項是否對於所有平台都有效。
總結:
個人感覺,越寫越爛。接下來會講到tcp的資料傳送,這會涉及到滑動視窗各種定時器之類的東西。我真誠
希望各位能夠多提意見。對於tcp連線的斷開,我們只要清楚:
1. 在預設情況下,呼叫close時tcp會繼續將資料傳送完畢;
2. time_wait狀態會導致的問題;
3. 連線意外斷開時可能會出現的問題。
4. maybe more...
Tcp 斷開連線
tcp協議規定,對於已經建立的連線,網路雙方要進行四次握手才能成功斷開連線,如果缺少了其中某個步驟,將會使連線處於假死狀態,連線本身占用的資源不會被釋放。網路伺服器程式要同時管理大量連線,所以很有必要保證無用連線完全斷開,否則大量僵死的連線會浪費許多伺服器資源。在眾多tcp狀態中,最值得注意的狀態有...
TCP連線和斷開連線
4.4 tcp資料報結構 帶陰影的幾個字段需要重點說明一下 1 序號 seq sequence number 序號佔32位,用來標識從計算機a傳送到計算機b的資料報的序號,計算機傳送資料時對此進行標記。2 確認號 ack acknowledge number 確認號佔32位,客戶端和伺服器端都可以傳...
TCP連線與斷開
a機器與b機器三次握手檢驗雙方報文收發正常 第一次握手 a請求連線 syn 1,seq x b正常接收 ab a能發報 b能收報 第二次握手 syn 1,ack 1,seq y,ack x 1 a a能發報 收報,b能發報 收報 b a能發報 b能收報 第三次握手 ack 1,seq x 1,ack...