上篇介紹了socket程式設計的準備知識,是不是有一種很想馬上就開始瞭解網路程式設計,甚至開始寫點程式碼的感覺,彆著急,網路程式設計中還有一個比較重要的概念是TCP/IP,中文名稱叫網路傳輸協議,本質上,TCP/IP是一種協議,同時也是網路程式設計中最重要的協議之一。TCP/IP涉及到的內容實在太多,無奈筆者才疏學淺,無法把整個TCP/IP介紹給大家,這篇文章的目的主要是基於上一篇文章的前提下,介紹TCP連線三次握手和斷開連線四次揮手究竟做了什麼?socket的狀態有哪些?在各個API執行的過程中,socket的狀態是怎麼變化的?希望通過這篇文章,能讓大家對在TCP連線建立與斷開過程中,socket的整個狀態變化流程有更深入的瞭解。
幾個術語
SYN : 同步序列編號,Synchronize Sequence Numbers,僅在三次握手建立TCP連線時有效。表示一個新的TCP連線請求。
ACK : 確認編號,Acknowledgement Number,對TCP請求的確認標誌,同時提示對端系統已經成功接收所有資料。
FIN : 結束標誌,FINISH,用來結束一個TCP會話,但對應埠仍處於開放狀態,準備接收後續資料。
TCP三次握手
建立一次連線會有下面的流程
1)伺服器通過socket(初始化socket)、bind(繫結ip埠)、listen(開始監聽服務)完成一次socket連線的建立,並呼叫accept函式準備好接收外部請求連線,這一步被稱為被動開啟
2)客戶端通過socket(初始化socket)完成了連線建立,呼叫connect函式發起主動開啟,此時客戶端會傳送一個SYN分節J給伺服器,J的作用是告訴伺服器,在接下來的資料傳輸過程中,J是客戶端在連線中傳輸資料的初始序列號
3)伺服器收到客戶端的SYN分節後,需要回復確認訊號ACK,J+1,代表伺服器已經收到客戶端的請求,且已經確認了客戶端的初始序列號,同時伺服器會傳送SYN分節K給客戶端,K的作用是告訴客戶端,在接下來的資料傳輸過程中,K是伺服器在連線中傳輸資料的初始序列號
4)客戶端收到伺服器的SYN分節後,回覆確認訊號(ACK)K+1。連線建立成功。
整個建立連線過程至少需要三個分節,因此被稱為TCP三路握手。下面是TCP三次握手的流程圖:
TCP四次握手
TCP通過三次握手建立連線,然而,斷開連線需要四次握手,TCP斷開連線的流程描述如下:
1、客戶端應用程式呼叫close函式,TCP中,稱首先呼叫close的那一端為主動關閉,主動關閉的這一端傳送FIN分節,意味著已經完成傳送資料了;
2、另一端(即伺服器),接收到關閉請求,也收到FIN分節,開始執行被動關閉操作,稱為被動關閉的一端。這個FIN分節由TCP確認,傳送一個確認分節ACK給客戶端,收到的FIN同時也是作為檔案結束符傳遞給應用,意味著應用程式在接收了FIN之後就不會再接收連線上的資料;
3、之後,接收到檔案結束符的應用程式會關閉它的socket,服務端的TCP也會傳送一個FIN分節;
4、客戶端接收到FIN分節,並確認最後的關閉操作,傳送ACK分節給服務端。
整個連線過程中,每一端的關閉和確認關閉都各自需要一個FIN和ACK分節,整個過程通常共需要4個分節,因此也稱為TCP四次握手。下面是TCP關閉連線四次握手的流程圖:
實際上,網路上的傳輸是沒有連線的,包括TCP也是一樣,TCP所謂的"連線"與"斷開連線",其實只是一種虛擬的叫法,只不過是在通訊的雙方維護一個"連線狀態",讓它看上去好像有連線一樣,所以,瞭解TCP的狀態變換是非常重要的,接下來介紹socket的狀態以及在TCP建立連線與斷開連線過程中,socket狀態的變化。
socket狀態
socket共定義了11種狀態:LISTEN、SYN-SENT、SYN-RECEIVED、ESTABLISHED、FIN-WAIT-1、FIN-WAIT-1、CLOSE_WAIT、FIN_WAIT、LAST-ACK、TIME-WAIT、CLOSING、CLOSED。
我們假定A服務請求連線B服務。
LISTEN:開始建立連線,此時socket已經初始化成功,正在等待連線
SYN-SENT:A成功傳送連線請求給B,等待對方響應
SYN-RECEIVED:B接收到A的連線請求,並回復了確認進行連線給A,此狀態表示正在等待A也回覆確認收到此訊息
ESTABLISHED:表示連線已經成功建立;這個狀態是連線階段中進行資料傳輸的正常狀態
FIN-WAIT-1:等待主動斷開連線請求的確認,或者併發請求被拒絕的斷開連線,這種狀態通常持續時間很短,比較難捕捉
FIN-WAIT-2:等待B斷開連線操作,這個狀態通常持續時間也很短,但是如果B發生阻塞或者其他原因沒有關閉連線,那麼這個狀態就會持續較長時間
CLOSE-WAIT:B已經收到了A的斷開連線請求,正在等待本地應用程式傳送斷開連線請求
CLOSING:A正在等待B的關閉連線確認訊號,當A接收到本地程式斷開連線的請求後,就傳送斷開連線請求給B,並進入此狀態
LAST-ACK:B等待斷開連線的確認訊號
TIME-WAIT:等待一段時間,確認B接收到A的關閉連線確認訊號
CLOSED:連線完全關閉
那麼在一個完整的TCP連線過程,TCP的狀態是怎麼轉換的呢?
先來看看下面這張圖,是UNIX網路程式設計中,TCP狀態變化的經典流程圖:
在server端,呼叫socket函式建立一個sockect,函式返回一個socket檔案描述符,呼叫bind函式繫結ip地址和埠,之後呼叫listen函式,scokect變成正在監聽的socket,進入LISTEN狀態,並呼叫accept等待請求(想要測試LISTEN狀態,可以建立socket=>bind=>listen,然後sleep10秒之後退出,啟動server之後馬上用netstat命令可以看到)
客戶端呼叫connect,主動開啟檔案描述符,請求建立連線,此時會觸發TCP的三次握手,進入SYN-SENT狀態
此時伺服器呼叫了accept正在阻塞階段,接收到客戶端的連線請求後,進入SYN-RECEIVED狀態,回覆確認報文給客戶端,等待客戶端確認連線
客戶端收到伺服器的連線確認報文SYN後,此時TCP已經完成了三次握手,connect函式返回,確認建立連線,轉成ESTABLISHED狀態,併傳送確認報文ACK給伺服器。(當第二次握手完成,握手步驟的第二個segment被client接收到的時候,connect函式返回。)
在伺服器側,接收到客戶端的確認報文,accept也收到返回,伺服器也進入ESTALISHED狀態。(握手步驟的第三個分節被server接收到的時候,accept函式返回,即connect返回後經過一半的RTT時間才返回。)
客戶端和服務端成功建立"連線"後,就進行開始通訊,此時會呼叫read/write函式進行讀寫資料,讀寫資料完畢後,就準備關閉連線
假定客戶端應用程式,收到了伺服器的響應報文,完成了通訊過程,準備關閉應用,呼叫close函式發起主動關閉,此時客戶端進入FIN-WAIT1狀態,傳送FIN分節到伺服器,等待伺服器確認
服務端收到斷開連線請求分節FIN(M),此時read函式返回0,伺服器準備執行關閉操作,不再接收任何資料,併傳送確認報文ACK(M+1)給客戶端
客戶端收到確認分節後,表示伺服器已經接收到斷開連線請求,此時不會再傳送資料並等待伺服器斷開連線,進入FIN-WAIT2狀態
伺服器傳送確認斷開連線後,呼叫close函式,斷開連線,close函式成功返回後,傳送FIN分節給客戶端,進入LASK-ACK狀態,等待客戶端的確認
客戶端收到伺服器的斷開連線分節FIN(N)後,傳送確認分節ACK給伺服器,並進入TIME-WAIT狀態,此時會等待足夠的時間,大約是最長分節生命期的2倍(2MSL),確認伺服器收到斷開連線的確認分節,之後就會消失。 伺服器收到客戶端的斷開連線的確認分節ACK(N+1)後,表示連線已經完全斷開了,此時進入CLOSED狀態
看完上面的一大段文字可能有點枯燥,甚至有點懵,來看看這張動畫圖:
總結
本次主要介紹了TCP狀態,以及在TCP連線建立和斷開過程中,TCP狀態的變化,掌握了這個,對理解網路程式設計中,各個流程的狀態有比較大的幫助,比如在排查服務是否啟動的時候,就可以通過netstat -nlp | grep '埠號'
,檢視服務的狀態描述字串,如果是LISTEN狀態表示服務已經正常啟動,如果服務處於其他狀態,則可以通過服務狀態來進一步排查問題。
原創文章,文筆有限,才疏學淺,文中若有不正之處,萬望告知。
如果本文對你有幫助,請點個贊吧,謝謝^_^
更多精彩內容,請關注個人公眾號。