TCP/IP狀態變遷圖和TCP三次握手與四次揮手
TCP/IP狀態變遷圖:
各狀態詳細描述:
CLOSED:表示初始狀態。對服務端和C客戶端雙方都一樣。
LISTEN:表示監聽狀態。服務端調用了listen函數,可以開始accept連接了。
SYN_SENT:表示客戶端已經發送了SYN報文。當客戶端調用connect函數發起連接時,首先發SYN給服務端,然后自己進入SYN_SENT狀態,并等待服務端發送ACK+SYN。
SYN_RCVD:表示服務端收到客戶端發送SYN報文。服務端收到這個報文后,進入SYN_RCVD狀態,然后發送ACK+SYN給客戶端。
ESTABLISHED:表示連接已經建立成功了。服務端發送完ACK+SYN后進入該狀態,客戶端收到ACK后也進入該狀態。
FIN_WAIT_1:表示主動關閉連接。無論哪方調用close函數發送FIN報文都會進入這個這個狀態。
FIN_WAIT_2:表示被動關閉方同意關閉連接。主動關閉連接方收到被動關閉方返回的ACK后,會進入該狀態。
TIME_WAIT:表示收到對方的FIN報文并發送了ACK報文,就等2MSL后即可回到CLOSED狀態了。如果FIN_WAIT_1狀態下,收到對方同時帶FIN標志和ACK標志的報文時,可以直接進入TIME_WAIT狀態,而無須經過FIN_WAIT_2狀態。
CLOSING:表示雙方同時關閉連接。如果雙方幾乎同時調用close函數,那么會出現雙方同時發送FIN報文的情況,就會出現CLOSING狀態,表示雙方都在關閉連接。
CLOSE_WAIT:表示被動關閉方等待關閉。當收到對方調用close函數發送的FIN報文時,回應對方ACK報文,此時進入CLOSE_WAIT狀態。
LAST_ACK:表示被動關閉方發送FIN報文后,等待對方的ACK報文狀態,當收到ACK后進入CLOSED狀態。
1. 連接建立
1) Client
當Client端調用socket函數調用時,相當于Client端產生了一個處于Closed狀態的套接字。
Client端又調用connect函數調用,系統為Client隨機分配一個端口,連同傳入connect中的參數(Server的IP和端口),這就形成了一個連接四元組,connect調用讓Client端的socket處于SYN_SENT狀態。
當Server返回確認,并發送SYN,Client返回確認及SYN后,套接字處于ESTABLISHED階段,此時雙方的連接已經可以進行讀寫操作。
2)Server
當Server端調用socket函數調用時,相當于Server端產生了一個處于Closed狀態的監聽套接字,Server端調用bind操作,將監聽套接字與指定的地址和端口關聯,然后又調用listen函數,系統會為其分配未完成隊列和完成隊列,此時的監聽套接字可以接受Client的連接,監聽套接字狀態處于LISTEN狀態。
當Server端調用accept操作時,會從完成隊列中取出一個已經完成的client連接,同時在server這端會產生一個會話套接字,用于和client端套接字的通信,這個會話套接字的狀態是ESTABLISH。
2. 連接關閉
與連接建立分為server/client不同,連接關閉并沒有絕對的server/client之分,連接關閉分為主動關閉和被動關閉。Server和client都可以擔任這兩個角色中的任意一個。如client可以關閉它與server的連接,同樣的server一樣也可以關閉一些長時間無讀寫事件發生的連接。既然這么說了,下面就會分成兩個部分:client主動關閉,server主動關閉。
Client主動關閉,Server被動關閉:
Client主動關閉,Server被動關閉的情況還是蠻多的,比如說短連接中,當一次會話結束時,client就可以關閉它與server之間的連接。
我們這邊直接說close而非shutdown。
當client想要關閉它與server之間的連接,首先client這邊會首先調用close函數,client端會發送一個FIN到server端,client端處于FIN_WAIT1狀態。當server端返回給client ACK后,client處于FIN_WAIT2狀態,server處于CLOSE_WAIT狀態。
當server端檢測到client端的關閉操作(read返回為0),server端也需要調用close操作,server端會向client端發送一個FIN。此時server的狀態為LAST_ACK,當 client收到來自server的FIN后,client端的套接字處于TIME_WAIT狀態,它會向server端再發送一個ack確認,此時server端收到ack確認后,此套接字處于CLOSED狀態。
Server端主動關閉的流程與Client端關閉類似,就不再多講,下面還需要關注的是TIME_WAIT這個狀態,分別按照client/server兩個部分講述。
首先說一下TCP/IP詳解中描述的關于TIME_WAIT的描述及其存在的必要性:
主動關閉的socket當收到對端的FIN操作后,該socket就會處于TIME_WAIT狀態,處于TIME_WAIT狀態的socket會存活2MSL(Max Segment Lifetime),
之所以存活這么長時間是有理由的:
一方面是可靠的實現TCP全雙工連接的終止,也就是當***的ACK丟失后,被動關閉端會重發FIN,因此主動關閉端需要維持狀態信息,以允許它重新發送最終的ACK。
另一方面TCP在2MSL等待期間,定義這個連接(4元組)不能再使用,任何遲到的報文都會丟棄。設想如果沒有2MSL的限制,恰好新到的連接正好滿足原先的4元組,這時候連接就可能接收到網絡上的延遲報文就可能干擾***建立的連接。重復的分節在網絡中消逝。
#p#3. Server端的監聽套接字與會話套接字的不同:
當server端的socket調用bind和listen之后,監聽套接字的狀態就會變為LISTEN狀態,監聽套接字的工作決定了它只是監聽連接。
當server端調用accept,相當于從套接字的完成隊列中取出一個client的連接,此時可以確定四元組,即:client的IP和port;server的IP和port,雙方各有一個套接字進行交互,這里需要說一下server端的socket,我稱server端accept后產生的套接字為會話套接字,這個套接字一出生的狀態就是ESTABLISH。由server端得四元組我們可以看出,一個server從理論上可以接收的連接數量取決于文件描述符的個數。
4,狀態為TIME_WAIT是不是所有執行主動關閉的socket都會進入TIME_WAIT狀態呢?
有沒有什么情況使主動關閉的socket直接進入CLOSED狀態呢?
主動關閉的一方在發送***一個 ack 后就會進入 TIME_WAIT 狀態 停留2MSL(max segment lifetime)時間,這個是TCP/IP必不可少的,也就是“解決”不了的。也就是TCP/IP設計者本來是這么設計的。
主要有兩個原因:
1。防止上一次連接中的包,迷路后重新出現,影響新連接(經過2MSL,上一次連接中所有的重復包都會消失)
2。可靠的關閉TCP連接。在主動關閉方發送的***一個 ack(fin) ,有可能丟失,這時被動方會重新發 fin, 如果這時主動方處于 CLOSED 狀態 ,就會響應 rst 而不是 ack。所以 主動方要處于 TIME_WAIT 狀態,而不能是 CLOSED 。
特別提示的是:為什么TIME_WAIT狀態還需要等待2MSL才能回到CLOSED狀態?或者為什么TCP要引入TIME_WAIT狀態?
《TCP/IP詳解》中如此解釋:當TCP執行一個主動關閉,并發回***一個ACK后,該連接必須在TIME_WAIT狀態停留的時間為2倍的MSL,這樣可以讓TCP再次發送***的ACK以防止這個ACK丟失(另一端超時重發***的FIN)。
附注:MSL(Maximum Segment Lifetime)即***生存時間,RFC 793中指出MSL為2分鐘,但是實現中的常用值為30秒、1分鐘或者2分鐘。
建立TCP連接(三次握手)
由于TCP協議提供可靠的連接服務,于是采用有保障的三次握手方式來創建一個TCP連接。三次握手的具體過程如下:
客戶端發送一個帶SYN標志的TCP報文(報文1)到服務器端,表示希望建立一個TCP連接。
服務器發送一個帶ACK標志和SYN標志的TCP報文(報文2)給客戶端,ACK用于對報文1的回應,SYN用于詢問客戶端是否準備好進行數據傳輸。
客戶端發送一個帶ACK標志的TCP報文(報文3),作為報文2的回應。
至此,一個TCP連接就建立起來了。
終止TCP連接(四次揮手)
由于TCP連接是全雙工的,因此每個方向都必須單獨進行關閉。原則是主動關閉的一方(如已傳輸完所有數據等原因)發送一個FIN報文來表示終止這個方向的連接,收到一個FIN意味著這個方向不再有數據流動,但另一個方向仍能繼續發送數據,直到另一個方向也發送FIN報文。
四次揮手的具體過程如下:
客戶端發送一個FIN報文(報文4)給服務器,表示我將關閉客戶端到服務器端這個方向的連接。
服務器收到報文4后,發送一個ACK報文(報文5)給客戶端,序號為報文4的序號加1。
服務器發送一個FIN報文(報文6)給客戶端,表示自己也將關閉服務器端到客戶端這個方向的連接。
客戶端收到報文6后,發回一個ACK報文(報文7)給服務器,序號為報文6的序號加1。
至此,一個TCP連接就關閉了。(4次揮手不是關閉TCP連接的唯一辦法,見下文Q3疑問)
TCP三次握手,四次揮手的時序圖:
TCP相關疑問
幾個常見的TCP/IP相關的疑問:
Q1 為什么在TCP協議里,建立連接是三次握手,而關閉連接卻是四次握手呢?
A1因為當處于LISTEN 狀態的服務器端SOCKET當收到SYN報文(客戶端希望新建一個TCP連接)后,它可以把ACK(應答作用)和SYN(同步作用)放在同一個報文里來發送給客戶端。但在關閉TCP連接時,當收到對方的FIN報文時,對方僅僅表示對方沒有數據發送給你了,但未必你的所有數據都已經全部發送給了對方,所以你大可不必馬上關閉SOCKET(發送一個FIN報文),等你發送完剩余的數據給對方之后,再發送FIN報文給對方來表示你同意現在關閉連接了,所以通常情況下,這里的ACK報文和FIN報文都是分開發送的。
Q2為什么TIME_WAIT 狀態還需要等2*MSL秒之后才能返回到CLOSED 狀態呢?
A2因為雖然雙方都同意關閉連接了,而且握手的4個報文也都發送完畢,按理可以直接回到CLOSED 狀態(就好比從SYN_SENT 狀態到ESTABLISH 狀態那樣),但是我們必須假想網絡是不可靠的,你無法保證你(客戶端)***發送的ACK報文一定會被對方收到,就是說對方處于LAST_ACK 狀態下的SOCKET可能會因為超時未收到ACK報文,而重發FIN報文,所以這個TIME_WAIT 狀態的作用就是用來重發可能丟失的ACK報文。
Q3關閉TCP連接一定需要4次揮手嗎?
A3不一定,4次揮手關閉TCP連接是最安全的做法。但在有些時候,我們不喜歡TIME_WAIT 狀態(如當MSL數值設置過大導致服務器端有太多TIME_WAIT狀態的TCP連接,減少這些條目數可以更快地關閉連接,為新連接釋放更多資源),這時我們可以通過設置SOCKET變量的SO_LINGER標志來避免SOCKET在close()之后進入TIME_WAIT狀態,這時將通過發送RST強制終止TCP連接(取代正常的TCP四次握手的終止方式)。但這并不是一個很好的主意,TIME_WAIT 對于我們來說往往是有利的。





























