TCP/IP摘要

TCP/IP是互聯網的基礎協議棧,它包括大大小小几十個協議。本篇文章主要涉及到就是HTTP、TCP、IP協議。我們經常學的網絡模型是七層或者五層,實際上一般認爲一共只有四層就可以了。

    Application layer           ->HTTP
-------------------------- 
     Transport layer            ->TCP
--------------------------
     Internet layer             ->IP
--------------------------
      Link layer                ->Ethernet

應用層、傳輸層、網際層、連接層,這一眼看上去,很直白嘛,就是分層抽象,層與層互相隔離,這確實沒問題。不過,我們可以換個角度來整體理解一下這四層。網絡所做的就是傳輸數據嘛,那這四層就是數據在不同階段的不同形式。應用層是數據的最終形式,傳輸層是數據的字節形式,網際層是數據的二進制形式,連接層是數據的信號形式。這樣換一個角度去看世界也是蠻有意思的,並且這麼看四層就更像是一個整體了。

數據在發送通過這四層的時候,每經過一層會將數據作爲Body,並加上這一層的控制信息作爲header。而到達目的地後,再這通過四層時,每層會將相應的header剝離,最後給接收方一個與發送方發送的一模一樣的數據。

HTTP

HTTP已有很多文章介紹了,這篇文章就不多說了。僅聊一聊對HTTP協議的理解,HTTP協議內部實際上是一個,協議字符串解析器。HTTP協議以字符串形式將數據解析、逆解析成請求行/響應行、請求頭、請求體構成的報文。爲什麼說是字符串解析器呢?實際上,請求行、響應行、header甚至是分隔標識(CRLF)都是字符串。而body就是數據,並且HTTP將數據的最終形式交由協議使用者來決定(text、xml、json等)。

TCP

你需要知道的關於TCP的第一件事就是TCP是個包含非常多知識點的協議(⊙﹏⊙),並且它還是一個實踐性很強的協議,TCP中很多爲什麼這麼做,都是應用於網絡中實踐出真知得出的“真理”。TCP是個可靠傳輸協議,在如此動盪(不安)的網絡環境裏,想要確保這一點可不容易,TCP獨有很多機制來做到這一點。先來聊一下可靠傳輸協議。

可靠傳輸協議要求,雙方的報文段(segment)必須都能無損的到達,並且發送方和接收方最後的報文段順序需要一致。這代表,要檢查報文是否完好無損,完好無損纔算個合格的報文。如果報文受損或者發送方報文段根本沒有到達接收方,發送方需要重傳。如何知道報文根本沒有到達接收方?發送方啓動一個計時器就可以,計時器超時就認爲報文沒有到達接收方。

並且既然重傳就需要接收方要對發送方確認,確認後發送方纔放心。最簡單的確認做法就是,一方發送報文段,另一方再發送一個單獨做確認的報文段,但這樣太低效了。不如,在發送自己的報文段的時候,順便確認下所接受的報文段。爲了最後要確認順序,報文段還需要有序號來標識順序。而上面說的這個幾點在TCP協議中都有體現,更直接的在TCP header中就有體現。

TCP還是一個面向連接的協議,建立連接是通過大名鼎鼎的三次握手,斷開連接通過四次揮手。這個連接肯定不是建立物理上的連接,而是邏輯上建立了連接。連接也就指的是,發送方和接收方都要初始化一些狀態會被用來跟蹤(track)發送過程,也就說TCP是個有狀態的協議,三次握手和四次揮手後面會聊。

TCP提供端到端的服務,它會具體到應用程序的port。不同於IP提供點到點的服務,如果將發送數據比作送快遞的話,IP提供的服務就是,快遞員準確的送到你家,TCP提供的服務就是,不僅送到你家,還將快遞準確的交到接收人的手上。

TCP除了要做到可靠傳輸,並且還要照顧接收方和網絡總體狀況,這其中還有流量控制和擁塞控制。上面說的這些機制後文會一個一個聊,現在先來看一下TCP的header。

TCP Header

blog_TCP_Header

我們來逐一的過一下這些字段,Source Port和Destination Port代表目的和源端口。

Sequence Number代表報文段的序號來表示順序。Acknowledgment Number代表發送方作爲接收方已接收的報文段,並且期望收到下個報文段的開始序列號。TCP實際上將傳送數據看做成數據的字節流,而不是一個個單獨的報文段。這點從Sequence Number就可以看出,Sequence Number以傳送的字節數作爲單位,而不以報文段的數量作爲單位。

Data Offset作爲對齊的空位,Reserved作爲保留位。下面是重點Flag位。

Flag包括Urgent、Ack、Push、Reset、Syn、Fin這6位。Urgent作爲報文段的緊急數據標識,但具體如何處理交給接收方決定。Reset作爲報文段的連接異常結束或端口號錯誤的標識。而Ack確認、Syn同步、Fin結束在三次握手和四次揮手中作爲關鍵標識位。Push代表TCP不再等待是否還有其他報文段到達,馬上交給上層應用層。

Window位作爲流量控制的基礎,後面會更具體的聊。

Check Sum作爲校驗位,校驗報文段是否在傳輸過程中受損。Urgent Pointer在Urgent位爲1時,纔會出現,指向緊急數據的最後一個字節。

Options常見的標識有nop、TS val(time stamp)、ecr(echo reply)、mss。nop標識就和氣泡指令一個意思,就是佔位的。而TS val和ecr分別代表發送方的時間戳和接收方的時間戳,基於這兩個時間戳來計算出RTT往返時間 (round-trip time) ,當然還要加權平均,具體計算就不多說了,RTT會被用來衡量重傳計時器的超時時長。mss(Maximum Segment Size)指的是,連接層每次傳輸的數據有個最大限制MTU(Maximum Transmission Unit),一般是1500比特,超過這個量要分成多個報文段,mss則是這個最大限制減去TCP的header,光是要傳輸的數據的大小,一般爲1460比特。

三次握手

三次握手,只能由客戶端向服務端發起。第一次客戶端發送SYN爲1,序列號seq爲某序列號,表示客戶端想要建立連接。第二次服務端返回ACK、SYN都爲1,序列號seq爲某序列號,確認號爲接收的序列號加1,表示確認服務端也想要建立連接。第三次客戶端發送ACK爲1,確認號爲所接收序列號加1,再次確認,然後連接建立。

爲何要進行三次握手?爲了防止失效的報文段又到達了服務端產生錯誤。假設,客戶端發送的第一個報文段延時到達了服務端,這個報文應被認爲失效。但服務端誤認爲客戶端想要建立一個新連接,就發出了確認,若沒有第三次確認再建立起連接。服務端就錯誤地建立起一個連接。

如果三次握手,第三次失敗了會怎麼辦?此時,服務端並不會馬上放棄,服務端還會嘗試重新發送確認,默認重試5次,間隔從1秒開始,後來每次是前一次的2倍。5次重試後,未果則放棄連接。

四次揮手

四次揮手,客戶端和服務端都可以發起。第一次發送方發送FIN爲1、ACK爲1,序列號爲某序列號,表示發送方想結束連接。第二次接收方發送ACK爲1,確認號爲接收序列號加1,表示我還沒有準備好結束連接。第三次接收方發送FIN爲1、ACK爲1,序列號爲某序列號,表示我已經準備好結束連接了。第四次發送方,發送ACK爲1,確認號爲所接收序列號加1,表示確認,結束連接。

半關閉

在四次揮手的基礎上,發送方可以在接收第二次接收方發送ACK後,可以形成發送方不再發送報文段,但仍然接收接收方發送的報文段的這種現象。這就形成了半關閉。

一開始,我對TCP/IP一點都不感冒,這確實是自己懶。光看書和概念,味如嚼蠟。還是動手實驗,纔會有更深的體會,推薦tcpdump工具。tcpdump如何使用點我。下面是我用tcpdump截的幾個報文段。

TCP三次握手

tcpdump第一行截到的信息翻譯:

16:26:13.702723 IP 10.174.73.57.65133 > 120.92.234.238.http: Flags[S], seq 37233370769, win 65535,
       ↓        ↓         ↓                    ↓        ↓       ↓       ↓                ↓
    發送時間   IP協議    源IP.port     到   目的IP.port http協議 Flags位 seq確認號         win大小

options [mss 1460,nop,wscale 5,nop,nop,TS val 1021470802 ecr 0, sackOK, eol], length 0
                                 ↓                                               ↓
                             options位                                  報文段不包含header的長度

實際上,圖中的五個報文段,其中的三個報文段正是TCP三次握手的過程。讀者可以嘗試找一下哪三個是。

這裏還有一張TCP四次揮手的截圖:

TCP四次揮手

TCP的確認方式

實際上,TCP實現的可靠傳輸協議還有更多的細節。上文說道可靠協議在發送自己的報文段的時候,可以順便確認下所接受的報文段。TCP就是這樣去確認,以至於TCP確認會延遲,去等待是否有報文段發送,讓報文段捎上確認。延遲一般爲200毫秒。

比如說TCP使用的確認方式。TCP使用了累計確認。接收方爲了交付給應用層正確的順序,只有順序正確的報文段會被確認然後交付給上層。發送方如果收到了接收方的某個確認號,即使這個確認號以前的報文段沒有收到確認號也會被認爲正確接收。

TCP還會使用選擇確認(selective acknowledgement)。假設,發送方發送了多個報文段,初始的報文段出現了問題,沒有抵達接收方。發送方會僅僅認爲這個初始的報文段失效了,而後發送的幾個報文段準確到達了接收方。也就是說,後發送的報文段雖然沒有接收到直接的確認,而發送方選擇性的確認了他們。接收方會將後發送的失序報文段先放入緩衝(buffer)中。這就是TCP實踐出真知的最好例子。網絡環境動盪一般會影響單個報文段,而不會影響一大片的報文段。

基於上面所說的累計確認和選擇確認,若是報文段失效,發送可能會收到多次對於同一個報文段的冗餘確認,若是收到了三次冗餘確認,就認爲這個報文段失效了,TCP不會等待計時器超時再重傳,TCP會直接啓動快速重傳(fast retransmit),直接重傳。這一點,實際上就是以時間和數據量兩個指標作爲衡量重傳的條件。

同樣,還是用工具實踐一下有意思一點,推薦使用WireShark,WireShark的Filter非常強大,在進行網絡診斷的時候非常有用。WireShark會根據TCP header中的Sequence Number,分析出冗餘ACK、快速重傳等現象,具體點我。WireShark在Filter中輸入tcp.analysis.fast_retransmission,就可以找出快速重傳的報文段。

TCP_fast_retransmission

流量控制

流量控制實際上是發送方發送和接收方處理速度匹配的過程。

TCP連接發送的報文段,都會放入上文所說的緩衝中等待應用程序取出。如果一端持續發送報文段,另一端一直沒有及時處理完並接着取出報文段,就會造成緩衝溢出。這時,就需要進行雙方的速度匹配,進行流量控制。接收方會將自己的緩衝剩餘空間rwnd告訴發送方,發送方爲了控制速度,只能再發送所得到的剩餘空間rwnd容量的報文段。由於上文所說TCP採取的確認方式,發送方得到的這個rwnd容量不會限制已發送而未得到確認的報文段,這些報文段很可能已經在接收方的緩衝中了,只限制將要發送的報文段。

將傳送數據看做數據流後,上面的這個過程就像在以序號作爲基準在數據流上移動窗口一樣,所以得名流量窗口。而剩餘空間rwnd,也就是TCP header中的window位。

這裏還有個問題,如果一方接收到了零剩餘空間信息,這方就再也不發送報文了嗎?不是,TCP爲應對這個情況會有個計時器(persist timer),出現這種情況就會讓計時器記時,當計時器觸發,這方會發送個剩餘空間探測報文段(window probe),以檢測是否可以重新發送報文段。如果一直沒有剩餘空間,計時器永遠不會終止,仍會做重新記時、超時的循環。

在WireShark中可以通過tcp.analysis.zero_window_probe、tcp.analysis.window_full找出剩餘空間探測報文段和通知發送方接收方空間已滿的報文段。

擁塞控制(Congestion-Control)

擁塞控制很好理解,TCP如果不照顧網絡總體狀況,一股腦的傳送數據的話,在極差的網絡環境下只會惡性循環,可以直接搞癱網絡。而傳送數據太小心的話又不能充分利用帶寬資源。所以,擁塞控制就是一個動態平衡的策略。擁塞控制實際上就是一個由三個狀態組成的有限狀態機(FSM),這三個狀態是慢啓動(slow start)、擁塞避免(congestion avoid)、快速恢復(fast recovery)。

首先,聊這三個狀態之前先聊一下TCP對擁塞的認識。

  • TCP如何根據自己的有限信息判定網絡擁塞?只要出現超時重傳和3次冗餘ACK引起的快速重傳就認爲網絡擁塞了。
  • 網絡擁塞也有程度的區分,TCP如何判定擁塞的程度?超時重傳被認爲擁塞程度強,快速重傳被認爲擁塞程度弱。
  • 相對於擁塞,TCP如何判斷網絡不擁塞?只要收到了非冗餘的ACK,TCP就認爲一切順利,沒有擁塞。
  • TCP如何判斷快要擁塞了?TCP會在每次發生擁塞後,記錄下導致發生擁塞的報文段的數量的一半,最小不能小於2單位(mss)報文段,這個值被用來衡量下次是否快要擁塞。
|  不擁塞   |    快要擁塞       |擁塞程度弱|擁塞程度強|
     ↑              ↑               ↑         ↑
|正常收到ACK|到了上次擁塞一半的量| 快速重傳 | 超時重傳 |

TCP有了這四個認識,就可以愉快地照顧網絡總體情況了。TCP以cwnd標識能夠發送的報文段的量,不用多說,擁塞控制整個過程也像是在數據流上移動窗口,所以也叫擁塞窗口。

首先,慢啓動。

在慢啓動階段,TCP以cwnd爲1作爲初始量,然後每確認一個報文段,都會爲cwnd加1。這樣,如果TCP一直保持最大限度的發送報文段,每過一個RTT,TCP發送的報文段量就會翻倍。所以,在慢啓動階段,TCP是指數級增長。慢啓動的語義是,現在對網絡狀態不是很清楚,先假設狀態不好,一上來少發送點,然後多發點試探網絡狀況。

其次,擁塞避免。

當cwnd增長到快要擁塞的時候會狀態遷移到擁塞避免。上文說到爲標誌快要擁塞會維護一個值ssthresh(slow start threshold),當cwnd大於等於ssthresh,慢啓動遷移到擁塞避免狀態。進入擁塞狀態後,每確認一個報文段,都會爲cwnd加1/cwnd。這樣,如果TCP一直保持最大限度的發送報文段,每過一個RTT,TCP發送的報文段量會加1。所以,在擁塞避免狀態,TCP是線性增長。擁塞避免的語義是,快要擁塞,小心一點。

然後,快速恢復。

如果出現了快速重傳怎麼辦?不管是慢啓動還是擁塞避免,都遷移到快速恢復。既然擁塞程度弱,那就適當的降低cwnd,將cwnd除2,並且維護ssthresh記錄擁塞的量,將cwnd的值賦給ssthresh。發生快速恢復就說明出現了3次冗餘ACK,TCP基於選擇確認,認爲引起3次冗餘ACK的報文段順利到達,將cwnd加上3個單位(mss)的量。如果再收到這個報文段的冗餘ACK,爲cwnd加1。如果收到了非這個報文段的冗餘ACK,表明這個報文段正確到達了,將ssthresh賦給cwnd,並結束快速恢復,遷移到擁塞避免狀態。所以,在快速恢復狀態,TCP增長的量級在擁塞避免和慢啓動之間。快速恢復的語義是,出了點小岔子,沒問題穩一穩,回到正軌上後,既然出了點小岔子,那以後就小心一點。

最後,狀態變遷。

現在已經有了對快速重傳的處理。那超時重傳怎麼辦?如果出現超時重傳,無論在哪個狀態都遷移到慢啓動,將cwnd重置爲1。這樣,這三個狀態都可以兩兩互相遷移到。TCP的擁塞控制就在遷移狀態中度過。

最後的最後,上圖。

TCP_FSM

更多的問題

實際上到這裏,TCP的核心已經聊完了。但是。。是的,你沒有猜錯,TCP還有更多的問題。

糊塗窗口綜合症(Silly-Window-Syndrome)與Nagle算法

流量控制很好的照顧了接收方,但是也引來了問題,如果接收方一直告訴發送方的剩餘空間rwnd很小。那麼發送方將一直髮送內容很小的報文段。相對於TCP header20字節,如果每次內容只有個位數字節,那這樣網絡基本上就都在傳輸控制信息,網絡使用率就太低了。這就出現了糊塗窗口綜合症。而出現這種情況發送方和接收方自然也都有自己的應對辦法。

對於接收方一般會使用David D Clark’s的策略,就是“欺騙”發送方,如果是剩餘空間很小的情況,乾脆就通告發送方剩餘空間是零,這樣發送方就不會再發送小內容了。等到剩餘空間超過1單位或者剩餘空間超過緩衝的一半的時候,再不“欺騙”發送方。

對於發送方會使用Nagle算法。就是對於小內容的停等協議。如果是小內容的話,要查看是否所有已發送的小內容都已被確認,都被確認才能發送,這就形成了對小內容的停止發送等待確認的協議。 Nagle認爲小內容就是小於1單位(mss)的量。

實際上Nagle算法不光是設計來解決發送方的糊塗窗口綜合徵,它還減輕了擁塞。它可以將多個等待的小內容合併成一個數據報發送。這樣直接的減少數據報的數量,從而減輕了擁塞。

TCP保活(Keep-Alive)

TCP的保活與HTTP的長連接不是一個意思,HTTP的長連接是複用TCP連接,減少連接時延。而TCP的保活是檢查連接的對方是否還響應。一般是服務器端對客戶端經行保活,如果客戶端不響應,服務器端就不浪費資源,斷掉連接。TCP自然是使用計時器來實現保活,超時時間默認爲兩小時。如果對方無反應,每隔75秒需重試9次。有趣的是,如果對方重啓了或者說崩潰後又恢復了,對方接到保護探測報文段後會設置RST flag(復位)返回給發送方,然後發送方會斷掉連接。

總結

TCP協議是個可靠協議,通過序號、校驗和、超時重傳、快速重傳、確認來做到這點。並且還要照顧接收方和網絡總體狀況,主要體現在流量控制和擁塞控制。它還是個會建立連接的協議,需要在雙方記錄一些狀態去跟蹤傳輸過程。並提供端到端的服務。

IP

IP協議最大的任務就是尋路,找到發往目的地的路徑然後發送過去,也就是說IP協議提供“點到點”的服務。IP協議不是可靠傳輸協議,只能盡力將數據報(digram)發送到目的地。這也代表着,數據報和數據包之間是獨立的,沒有狀態。相對與TCP協議像是可變數據,IP協議就像是不可變數據。實際上,IP協議無狀態流就像是響應式編程,具體點我

IP協議尋路

先聊一下IP協議如何尋路,IP協議不可能一次性將數據報發送到目的地,必須經過多箇中轉站。如果要求一次性發送到目的地,要求雙方有個獨有的連接,然而爲網絡上所有人都建立這樣一個連接是不可能的。並且這個中轉站不可能強大到知道整個網絡的拓撲結構,它只知道周圍的節點的拓撲結構。

這就呈現出了IP尋路模型。路由器充當中轉站的角色,主機和路由器都有一個路由表,路由表指示周圍路由器的拓撲結構,就像一個地圖一樣,數據報通過查詢路由表的結果尋路到下一個路由器。下一個路由器以同樣方式負責尋路到再下一個路由器。這樣,每一個路由器只負責到下一跳路由器(next-hop router)。最後IP協議通過多個路由器就到達了目的地。路由表不僅可以通過精確的目的地主機號尋路,還可以以子網的網絡號尋路。當然還有保底的默認路徑。

子網實際上作爲比主機更大粒度的劃分網絡,以子網尋路可以極大的減少路由表的體積。相當於通過加大劃分的粒度,減少了維護整個網絡系統的成本。

IP協議尋路還有更多的問題。比如,主機也可以將數據報以發送給自己,當發現IP地址是自己時,數據報會交給以太網環回程序,環回程序將數據報加入本地的IP隊列與其他數據報一視同仁。

主機可以被設置成路由器轉發數據報,如果主機接收到了不是自己IP地址的數據報,只要被設置可以轉發出去。但是如果沒有尋路到下一跳怎麼辦?主機要返回一個ICMP(網絡控制消息協議),代表差錯。

ICMP還可以用來重定向,比如說主機想發送一個數據報到目的地,可以發送給A和B,尋路的結果下一跳爲A,主機發送給了A。A尋路的下一跳爲B,發送給了B,A可以偵測出這個情況,然後發送給主機一個重定向ICMP,讓主機的路由表修改爲尋路到B。

數據分片(IP-Fragmentation)

當數據報量超過了MTU怎麼辦?對比於TCP的分段,IP要分片。然而,這兩個步驟互不干擾,是完全隔離開的。IP分片後,接收方接收到數據報後,將分片要重新組合起來。IP分片對於UDP協議比較有用,對TCP沒有太大用處,TCP更希望自己來分段,而不靠IP去分片。IP不是個可靠協議,如果分片其中的一片出了問題,TCP也無法重傳單個分片,自然TCP就更希望自己來分段,做到重傳單個分段。

IP Header

IP協議分爲IPv4和IPv6版本,兩種版本header不相同,版本在Version區域區分。首先IPv4,

IP_Header

其次,IPv6

IPv6_Header

這裏,就撿幾個體現出IP服務的說一下,相比於TCP。IP協議的header沒有那麼能體現出IP協議的特點。

IPv4 header一般爲20字節,IPv6 header一般爲40字節。IPv4中address爲32位,而IPv6增大到了128位。這樣就從address分配緊張到地球上的每一顆砂礫都能有IP address了!

TTL和Hop Limit都是表示IP協議還能跳的路由器數量,如果爲零了,則數據報會被丟棄,並返回一個ICMP通知源主機。Traceroute程序就是這樣收集數據報被丟棄後發送的ICMP實現的。

IPv4用Identification唯一識別數據報(分片數據報相同),Fragment offset標識分片的起始位置。而在IPv6中都可以用更加靈活的Next Header表示,Next Header就像鏈表一樣,可以連接多個”Header”,拓展出多個Header。除了分片的起始位置、還可以表示同IPv4 protocol一樣能表示的上層協議。



參考:TCP/IP詳解 卷1:協議

原文地址:http://blog.mrriddler.com/2017/01/13/TCP:IP漫遊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章