通過了過年的忙碌和年初的懈怠一切的日子,我又開始從新更新了~這是最新的一篇~完整版能夠去gitbook(https://rogerzhu.gitbooks.io/-tcp-udp-ip/content/)看到。linux
若是對和程序員有關的計算機網絡知識,和對計算機網絡方面的編程有興趣,雖說如今這種「看不見」的東西真正能在實用中遇到的機會很少,可是我始終以爲不管計算機的語言,熱點方向怎麼變化,做爲一個程序員,不少基本的知識都應該有所瞭解。而當時在網上搜索資料的時候,這方面的資料真的是少的可憐,因此,我有幸前兩年接觸了這方面的知識,我以爲我應該把我知道的記錄下來,雖然寫的不必定很好,可是但願能給須要幫助的人多個參考。個人計劃是用半年時間來寫完這一系列文章,這個標題也是我對太多速成文章的一種態度,好了,廢話再也不多扯了,下面是其中的一節內容,更多內容能夠去gitbook上找到。git
前面對於UDP已經闡述了有一些內容了,UDP能夠完成一些數據的傳輸,那麼爲何還要再研究出另一種傳輸層協議呢?由於在不少時候,不可靠的傳輸會形成上面的應用層協議變得毫無心義,並且面對愈來愈複雜的網絡,沒有管理控制的傳輸層協議更是會致使網絡擁堵不斷加重直至癱瘓。能夠設想一下,UDP就像是寄信,當你把信寄出去的時候,你是沒法知道這封信可不能夠到達收信人的的,若是說惟一你能作的就是相信郵遞機構。這封信在沿途是丟了仍是寄到什麼其餘地方去了,你徹底不知道(雖說在如今這個快遞信息極端透明的狀況下看起來不太可能,可是在快遞剛剛開始的時候,這種狀況太常見了)。可是若是是打電話,這種模式就不行了,不能說你播出一個號碼,說一段留言,而後還不知道對方能不能接收到這個留言,若是是這樣,我要電話還有個什麼用。再換個角度,即便我真的能有這麼個留言的模式打電話,可是因爲同時有太多留言,形成電話網絡壓力過大,你的留言可能由於延遲一年後纔到達對方,很明顯,電話不是這麼設計的,就像TCP同樣,設計他就是爲了能提供可靠的通訊。程序員
TCP裏最初級也是最重要的概念之一就是鏈接,UDP是沒有鏈接的協議,通俗點說就是UDP的兩端在通訊的時候只要任一方想發送消息給其餘方,他只要發就能夠了。UDP是一個很任性的協議,想發就發,想斷就斷,不須要實現通知對方,也不須要作些什麼準備工做。TCP就不一樣了,TCP是一個很紳士的協議,在發以前,發送方和接收方會先進行協調,結束的時候呢,雙方一樣也會進行相互的溝通並積極的作好本身的清理工做,英文中對這種行爲有個很恰當的詞語,叫作graceful。面試
前面說過,TCP是一個紳士的協議,在發送數據以前,雙方會進行友好的協商,這種協商也就是在全部介紹TCP的文章裏都會提到的「三次握手」。如今有個廣泛的現象,如今問面試者什麼是「三次握手」,基本都沒有答不出來可是若是再進一步,問一下,若是在某一個步驟的時候出現了丟失,那麼會怎麼樣,基本上就只剩百分之二十的人能答的出來。這就是像你只知作別人的紳士行爲是什麼樣的,殊不知道這些行爲的來源,因此若是盲目的學習只能給人一種學到皮毛的感覺。不過,這也是一篇介紹TCP的文章,因此固然也繞不開TCP的三次握手,我用一張wireshark裏真實抓包獲得的TCP流來進行圖示:編程
左邊是發送方,右邊是接收方,在介紹三次握手以前,首先你們得回憶下前面介紹過的TCP的報頭中的標識符位。TCP報頭中有6位標識符,在置1以後分別表明這一個TCP包有不一樣的含義。其中第五位是SYN位,當這一位置1時表示鏈接的開始或者同步序號請求,SYN就是英文同步synchronize的縮寫。除了這一個以外,另外一個會在三次握手中出現的就是ACK,這個是六個標識符中的第二個標識符,英文acknowledgement的縮寫,主要用來表示對於對端消息的迴應,簡單粗暴的理解的話,能夠理解爲,「啊,我知道了」。網絡
爲何我說TCP是一個紳士的協議呢?從其三次握手的過程當中就能夠體會的到,請求的發起方先發送一個編號爲0的SYN包到接收方,接收方接收到這個SYN包以後,首先確定是要通知發送方我已經接受到了你的SYN請求,也就是咱們上面說的ACK。但同時按照上面描述的,若是想創建鏈接,就必須發送SYN,因此,對於接收方,就有兩個須要發送的包,亦或是說兩個被置不一樣標識的包,可是很明顯,這兩個包是能夠合併的,因此說,發送方就會發送一個TCP包,這個包裏,SYN位和ACK位同時被置上。回到發送方,在接收到這個對端發送來的SYN包以後,一樣要回一個ACK包給對端。此時,TCP鏈接就創建好了,後面的通訊中,兩端就能夠自由的發送數據和消息了。併發
這裏還能夠了解到的就是貫穿整個TCP的確認消息,TCP如何讓對端知道本身已經收到了哪些包?前面一篇說過,TCP報頭中是有一個序列號的字段的,這個字段用來給每個報文編號,這個編號在一次通訊中是不斷遞增的,因此理論上接收端只要告訴發送端本身已經收到的包的序號就等於告訴發送端我已經收到比這個序號小的全部的報文。回到上面的圖中,能夠看到第一個SYN包的序號是0,那麼當接收端告知對方的ACK中所使用的序列號是1,表示標識符比1小的包我都接收到了。在這個特定狀況下,也就等於發送端已經知道了接收端已經良好的收到了本身的SYN請求。固然,這個序列號,確認號具體在TCP報頭的什麼位置,在上一篇文章中,能夠很容易的找出來。tcp
TCP的三次握手其實給人和人之間的交流提供了一個很好的模型,就拿開車並道這件事來講,若是人人都能遵循三次握手的原則,那麼我相信全部的由於並道而產生的事故都能避免。前車要並道以前先閃三次轉向燈,後車閃燈表示本身已經收到前車並道信號而且能夠並道,前車再次打轉向燈,而後開始並道。簡直是一個標準的三次握手過程,簡潔而有效,惋惜的就是在實際生活中,按照這樣的規則作並道的人太少。學習
雖說三次握手的設計是一個很紳士的設計,可是全部的時候理想和現實都是有差距的,因爲網絡的複雜性,三次握手的每個消息都有可能在傳遞的過程當中面臨三種狀況,丟失,延遲到達,重複。這三種狀況貫穿於整個的TCP通訊的每一步,而TCP中的不少設計也是由於解決這三個狀況而應運而生。spa
在創建鏈接的階段主要是丟失的問題,在介紹丟失問題的解決思路以前,先要介紹的一個概念是發送計時器。在TCP中,發送消息的時候會啓動一個計時器,這個計時器在收到相應回覆的時候會重置而從新計時,而若是一直沒有收到相應的回覆,在計時器到期的時候發送端就會重發消息,這是TCP重傳機制裏面第一層的保障。
由於TCP發起鏈接的時候只有三條消息,因此丟失也就三種狀況:
第一個SYN消息丟失,也就是發起者的發起請求丟失了,因此接收者也就不會回送SYN-ACK消息,由於他沒有得消息刺激他迴應。因此過一段時間後發起者發現本身沒有收到迴應消息,因而在計時器到期後,發起端會重發SYN消息。若是在通過了幾回重傳仍然沒有成功之後,嘗試鏈接過程就終止了。
第二個SYN-ACK消息丟失,發送端本質上和上一種狀況相同。接收者由於確實已經收到了SYN消息併發送了回覆消息,因此其計時器已經啓動了。在計時器到期以後,接收端會重發SYN-ACK消息,若是幾回以後尚未成功,那麼接收端會發送RST終止鏈接,RST的含義在後文中會詳細介紹。
第三個來自發送端的ACK丟失,接收端本質上會上一種狀況相同,最終會發送RST消息終止鏈接。
單獨分別從兩端看這三個錯誤的處理方式,並不難理解和理清楚其中的過程,可是若是從兩端一塊兒考慮,那麼稍微想一下就知道過程變得極端的複雜,由於在發生丟失的狀況下,兩端都有定時器在計時。
在linux的TCP-IP協議的實現中,分別使用兩個不一樣的計時器,在發送端啓動是普通的超時計時器,在接收端啓動的是SYN-ACK計時器。超時計時器就是在發送端發送SYN的時候開始計時,默認是1秒,若是過了1秒沒有收到確認,會再次發送SYN,而後將計時器設置成爲2秒,而後依4秒,8秒,16秒,以此類推。固然,在代碼中有一個重試上限,在linux上的默認是設置爲5次。一樣的SYN-ACK計時器在接收端接收到SYN以後發出SYN-ACK消息以後啓動,間隔和重試次數和普通計時器都是一致的,固然他會作一些其餘的事情因此和普通計時器是有一些區別的。
咱們考慮實際中的狀況二,發送端發送SYN後未收到SYN-ACK消息,同時啓動計時器A,過了一小段時間以後,接收端接收到了SYN消息,啓動計時器B,發送SYN-ACK消息,可是這個消息丟失了。1秒鐘後,發送端因爲A到期,重發SYN,而幾乎與此同時接收端也會因爲B到期重發SYN-ACK消息。那麼問題來了,假設這個時候重發的SYN又一次成功的到達了接收端會怎樣?答案很簡單,接收端會忽略它,由於seq序號重複了。接收端既不會再一次發送SYN-ACK消息,也不會重置計時器。因而就避免不斷重複的重發,形成網絡混亂甚至崩潰。
若是用一句話總結的話,就是經過超時計時器和序號的重複檢測,TCP能夠一樣能夠很紳士的解決這些不紳士的打斷。