所謂三次握手其實指的是三次信息交換過程,三次信息交換完畢後咱們就能夠認爲一個「鏈接」創建好了,那麼什麼是一個「鏈接」呢?程序員
一個鏈接惟一肯定了發送方和接收方,除此以外一個鏈接還肯定了雙方「說話的方式」,三次握手規定:雙方在說話前都要加一個數字,該數字用來記錄這是彼此的第幾句話了,好比:編程
A: 1,今每天氣不錯啊 1,對啊 :B 2,明每天氣也不錯 :B A: 2,不過好像明天有雨 A: 3,並且明天還要加班 A: 4,不想上班 :( 3, 我明天休假啦 :B 6, 下班啦 :B 4, 今天能夠早點撤 :B 5, 開心 :B
從這裏咱們能夠看出雙方說話前都報上這是本身說的第幾句話,固然在真實狀況下只有無聊到極點的人才會在微信裏用這種方式聊天。可是微信所依賴的網絡通訊協議其實就用這種看上去略顯神經質的方式在彼此通訊,有的同窗可能已經看出來了,這種方式的好處在於可靠性。服務器
從上面的聊天中咱們可看到,B最後說的幾句話可能由於網絡問題出現了亂序,但A依然知道該怎麼閱讀B發過來的信息,緣由就在於每一句都帶有編號,A能夠依照編號的順序而不是接收數據的順序進行閱讀,這就是每句話加編號的做用。微信
真實的網絡協議雙方的第一句話都不是從1開始的,三次握手的目的就在於協商雙方最開始的數字是幾,好比A和B說:"個人信息序號從X開始",B對A說:"個人信息序號從Y開始",此後雙方在X和Y的基礎上每說一句就加1,以此來確保通訊的可靠性。網絡
那麼什麼網絡協議須要依賴三次握手交換說話序號來確保可靠性呢,答案就是網絡通訊協議中的TCP,只有TCP協議在通訊前才須要進行三次握手彼此交換起始序號。socket
那麼UDP協議在雙方通訊前須要三次握手嗎?固然是不須要的,UDP協議不負責通訊的可靠性,依賴UDP的通訊雙方無需三次握手就能夠直接發送數據。函數
本文的關注重點在TCP協議,如下內容都是關於TCP協議的,這一點要注意。spa
TCP規定接收方須要對接收到的數據進行回覆確認,也就是發送ACK,發送方接收到ACK後就知道接收方確實已經收到信息了,若是在一段時間後尚未接收到ACK信息,那麼發送方就要重傳消息,這就是TCP協議中所謂的超時重傳機制,那麼這裏有一個問題,發送怎麼知道該重傳哪一個消息?操作系統
不要忘了使用TCP進行通訊的雙方每句話都帶有序號,接收方回覆的ACK中一樣帶有序號,好比接收方發送的ACK消息爲:code
ACK 15
這句話的意思實際上是在說:
序號14以前的消息我都已收到,能夠發送序號15以後的數據了
當發送方接收到該ACK後就知道序號15以前的全部信息接收到都已收到,這樣經過在ACK中攜帶上序號接收方能夠準確的知道哪些數據沒有發送成功。
這就是爲通訊加上序號的重要性,一句話,就是爲了確保TCP協議的可靠性。
能夠說序號是TCP實現可靠性的基石。
如今咱們已經知道了序號在TCP協議中的重要性,三次握手本質上就是交換彼此說話的起始序號,沒有該序號TCP的可靠性無從談起。三次握手實際上是相似以下過程:
A: 我說話的序號是從X開始的 收到(不要忘了TCP協議中須要對每句話進行確認) :B 我說話的序號是從Y開始的 :B A: 收到(不要忘了TCP協議中須要對每句話進行確認)
即:
可是,咱們能夠看到B說的兩句話起始徹底能夠合併成一句,所以:
A: 我說話的序號是從X開始的 收到,我說話的序號是從Y開始的 :B A: 收到
即:
這就是三次握手的由來,如今你應該明白了吧。
固然教科書上不是這樣寫的,教科書上是這樣寫的:
總之是以你看不懂的方式來講就對了 :) ,開玩笑哈,本文接下來的部分也以上圖爲例來說解,前兩張圖的目的是爲了讓你們更好的理解三次握手。
總之,交換雙方說話的起始序號就是三次握手的目的,該序號極其重要,是TCP協議實現可靠性的基礎。
三次握手以後TCP雙方就能夠交換數據了。
在講解socket API前咱們須要理解TCP協議的雙方分爲主動打開和被動打開,從三次握手的角度講,主動發起握手的一方屬於主動打開;被動接受握手的一方屬於被動打開。
很顯然,客戶端屬於主動打開,服務器端屬於被動打開。
接下來咱們就能夠看socket API了。
在socket編程中有幾個API很是重要,但不少資料對其解釋差強人意。
實際上這些API分爲兩類,一類客戶端和服務器端均可以調用;另外一類API獨屬於客戶端或者服務器端:
客戶端和服務器端均可以調用的API:
socket(), bind(), send/write(),write()/recv(),close()
獨屬於客戶端和服務器端的API:
客戶端:connect() 服務器端:listen(),accept()
在這裏咱們比較感興趣的是第二類API,爲何connect函數只能被客戶端調用、listen與accept函數只能被服務器端調用?
要想理解這個問題咱們必須清楚的知道這些API與三次握手之間的聯繫。
咱們再來看一下客戶端和服務器這兩個概念,實際上當雙方三次握手後正常通訊時無需區分服務器端和客戶端,服務器端能夠向客戶端發送數據,客戶端也能夠向服務器端發送數據,在這個階段客戶端也好服務器也罷沒什麼區別,惟一能區分服務器仍是客戶端實際上是經過三次握手這個過程來實現的。
怎麼區分呢?很簡單,主動發起鏈接的一方是客戶端,被動打開的一方是服務器端,而獨屬於客戶端或者服務器端的幾個API與三次握手密切相關。
實際上connect、listen以及accept函數與三次握手的關係以下:
從圖中咱們能夠看到,三次握手實際上是客戶端經過connect函數發起的,客戶端調用connect函數不會當即返回,只有當三次握手成功完成後connect函數纔會返回。
對於服務器server來講,調用listen僅僅是服務器告訴操做系統已經準備好了被動打開,也就是被動接受握手,當服務器端尚未執行listen函數時,客戶端調用connect函數是不會成功返回的,緣由很簡單,connect函數的功能其實是發起三次握手,但此時服務器端尚未準備好,所以三次握手不會成功,connect函數也不會成功返回。
只有當服務器端調用listen函數後,服務器端纔會作好準備來進行三次握手,注意這和調沒調用accept函數沒有任何關係。只要服務器端調用了listen函數,即便沒有調用accept三次握手也能夠成功。
三次握手後服務器端和客戶端成功創建起連接(準確講是成功交換了彼此說話的起始序號),服務器和相應客戶端的鏈接信息會被放到操做系統的等待隊列中,等等,爲何要放入隊列中呢?由於一個服務器能夠和多個客戶端創建鏈接,三次握手成功後須要維護這些客戶端的鏈接信息,所以這些信息一般是操做系統用隊列來維護的。
那麼隊列中的這些鏈接數據何時會被取出來呢?這就是accept函數的做用了,服務器端調用accept函數後會從隊列中取出一個已經成功三次握手的鏈接數據,此後雙方就能夠進行正常通訊了。
從這裏咱們也能夠看出accept函數不會影響三次握手,但該函數可否很快返回是和三次握手有關的,當服務器端調用listen準備進行三次握手後假設尚未任何客戶端同服務器端進行通訊,這時服務器端調用accept函數是不會返回的,緣由很簡單,由於此時隊列中尚未任何成功創建的鏈接,該情形就是上圖所示,當第一個客戶端同服務器端成功三次握手後隊列中才會有鏈接信息,此時accept函數從隊列取出該數據後纔會返回。
基於以上分析,connect、listen以及accept同三次握手有密切關係。
connect函數用於發起三次握手所以只能被客戶端使用。
listen用於準備接受握手,accept函數用於取出成功進行三次握手的鏈接信息,所以這兩個函數只能被服務器端使用。
本文中咱們講解了什麼是三次握手以及三次握手與socket API之間的聯繫,注意只有TCP協議才須要三次握手,但願本文的講解能加深同窗們對TCP協議的理解。
若是你喜歡這篇文章,歡迎關注微信公共帳號:碼農的荒島求生,獲取更多內容。
計算機內功決定程序員職業生涯高度