TCP基礎知識總結

TCP 是互聯網核心協議之一,本文介紹它的基礎知識。

一、TCP 協議的作用

互聯網由一整套協議構成。TCP 只是其中的一層,有着自己的分工。

最底層的以太網協議(Ethernet)規定了電子信號如何組成數據包(packet),解決了子網內部的點對點通信。

但是,以太網協議不能解決多個局域網如何互通,這由 IP 協議解決。

IP 協議可以連接多個局域網。

IP 協議定義了一套自己的地址規則,稱爲 IP 地址。它實現了路由功能,允許某個局域網的 A 主機,向另一個局域網的 B 主機發送消息。

路由器就是基於 IP 協議。局域網之間要靠路由器連接。

路由的原理很簡單。市場上所有的路由器,背後都有很多網口,要接入多根網線。路由器內部有一張路由表,規定了 A 段 IP 地址走出口一,B 段地址走出口二,......通過這套"指路牌",實現了數據包的轉發。

(圖片說明:本機的路由表註明了不同 IP 目的地的數據包,要發送到哪一個網口(interface)。)

IP 協議只是一個地址協議,並不保證數據包的完整。如果路由器丟包(比如緩存滿了,新進來的數據包就會丟失),就需要發現丟了哪一個包,以及如何重新發送這個包。這就要依靠 TCP 協議。

簡單說,TCP 協議的作用是,保證數據通信的完整性和可靠性,防止丟包。

二、TCP 數據包的大小

以太網數據包(packet)的大小是固定的,最初是1518字節,後來增加到1522字節。其中, 1500 字節是負載(payload),22字節是頭信息(head)。

IP 數據包在以太網數據包的負載裏面,它也有自己的頭信息,最少需要20字節,所以 IP 數據包的負載最多爲1480字節。

(圖片說明:IP 數據包在以太網數據包裏面,TCP 數據包在 IP 數據包裏面。)

TCP 數據包在 IP 數據包的負載裏面。它的頭信息最少也需要20字節,因此 TCP 數據包的最大負載是 1480 - 20 = 1460 字節。由於 IP 和 TCP 協議往往有額外的頭信息,所以 TCP 負載實際爲1400字節左右。

因此,一條1500字節的信息需要兩個 TCP 數據包。HTTP/2 協議的一大改進, 就是壓縮 HTTP 協議的頭信息,使得一個 HTTP 請求可以放在一個 TCP 數據包裏面,而不是分成多個,這樣就提高了速度。

(圖片說明:以太網數據包的負載是1500字節,TCP 數據包的負載在1400字節左右。)

兩個概念

MTU(Maximum Transmission Unit)最大傳輸單元,在TCP/IP協議族中,指的是IP數據報能經過一個物理網絡的最大報文長度,其中包括了IP首部(從20個字節到60個字節不等),一般以太網的MTU設爲1500字節,加上以太幀首部的長度14字節,也就是一個以太幀不會超過1500+14 = 1514字節。

如上圖所示,MTU指的都是一個物理網絡之中的。在以太網中,如果上層協議交給IP協議的內容實在是太多,使得一個以太幀超過了1514字節,那麼IP報文就必須要分片傳輸,到達目的主機或目的路由器之後由其重組分片。
 

MSS(Maximum Segment Size,最大報文段大小,指的是TCP報文(一種IP協議的上層協議)的最大數據報長度,其中不包括TCP首部長度。MSS由TCP鏈接的過程中由雙方協商得出,其中SYN字段中的選項部分包括了這個信息。如果MSS+TCP首部+IP首部大於MTU,那麼IP報文就會存在分片,如果小於,那麼就可以不需要分片正常發送。
一般來說,MSS = MTU - IP首部大小 - TCP首部大小

 

MSS爲Maximum Segment Size,即最大報文段長度,其受MTU大小影響,這裏的MTU指的是三層的,二層的MTU固定爲1500,不能修改。

MSS最大傳輸大小的縮寫,是TCP協議裏面的一個概念。
MSS 就是TCP數據包每次能夠傳輸的最大數據分段。爲了達到最佳的傳輸效能TCP協議在建立連接的時候通常要協商雙方的MSS值,這個值TCP協議在實現的時 候往往用MTU值代替(需要減去IP數據包包頭的大小20Bytes和TCP數據段的包頭20Bytes)所以往往MSS爲1460。通訊雙方會根據雙方 提供的MSS值得最小值確定爲這次連接的最大MSS值。
 

MTU爲Maximum Transmission Unit,即最大傳輸單元,需要注意MTU如果太小會影響收到數據包的速度,表現爲下載過慢。

先 說說這MTU最大傳輸單元,這個最大傳輸單元實際上和鏈路層協議有着密切的關係,讓我們先仔細回憶一下EthernetII幀的結構 DMAC+SMAC+Type+Data+CRC由於以太網傳輸電氣方面的限制,每個以太網幀都有最小的大小64bytes最大不能超過 1518bytes,對於小於或者大於這個限制的以太網幀我們都可以視之爲錯誤的數據幀,一般的以太網轉發設備會丟棄這些數據幀。(注:小於 64Bytes的數據幀一般是由於以太網衝突產生的“碎片”或者線路干擾或者壞的以太網接口產生的,對於大於1518Bytes的數據幀我們一般把它叫做 Giant幀,這種一般是由於線路干擾或者壞的以太網口產生)

由於以太網EthernetII最大的數據幀是1518Bytes這樣,刨 去以太網幀的幀頭(DMAC目的MAC地址48bit=6Bytes+SMAC源MAC地址48bit=6Bytes+Type域 2bytes)14Bytes和幀尾CRC校驗部分4Bytes(這個部門有時候大家也把它叫做FCS),那麼剩下承載上層協議的地方也就是Data域最 大就只能有1500Bytes這個值我們就把它稱之爲MTU。這個就是網絡層協議非常關心的地方,因爲網絡層協議比如IP協議會根據這個值來決定是否把上 層傳下來的數據進行分片。就好比一個盒子沒法裝下一大塊麪包,我們需要把麪包切成片,裝在多個盒子裏面一樣的道理。

當兩臺遠程PC互聯的時候,它們的數據需要穿過很多的路由器和各種各樣的網絡媒介才能到達對端,網絡中不同媒介的MTU各不相同,就好比一長段的水管,由不同粗細的水管組成(MTU不同 )通過這段水管最大水量就要由中間最細的水管決定。

對 於網絡層的上層協議而言(我們以TCP/IP協議族爲例)它們對水管粗細不在意它們認爲這個是網絡層的事情。網絡層IP協議會檢查每個從上層協議下來的數 據包的大小,並根據本機MTU的大小決定是否作“分片”處理。分片最大的壞處就是降低了傳輸性能,本來一次可以搞定的事情,分成多次搞定,所以在網絡層更 高一層(就是傳輸層)的實現中往往會對此加以注意!有些高層因爲某些原因就會要求我這個麪包不能切片,我要完整地面包,所以會在IP數據包包頭裏面加上一 個標籤:DF(DonotFragment)。這樣當這個IP數據包在一大段網絡(水管裏面)傳輸的時候,如果遇到MTU小於IP數據包的情況,轉發設備 就會根據要求丟棄這個數據包。然後返回一個錯誤信息給發送者。這樣往往會造成某些通訊上的問題,不過幸運的是大部分網絡鏈路都是MTU1500或者大於 1500。

對於UDP協議而言,這個協議本身是無連接的協議,對數據包的到達順序以及是否正確到達不甚關心,所以一般UDP應用對分片沒有特殊要求。

對於TCP協議而言就不一樣了,這個協議是面向連接的協議,對於TCP協議而言它非常在意數據包的到達順序以及是否傳輸中有錯誤發生。所以有些TCP應用對分片有要求---不能分片(DF)。

 

三、TCP 數據包的編號(SEQ)

一個包1400字節,那麼一次性發送大量數據,就必須分成多個包。比如,一個 10MB 的文件,需要發送7100多個包。

發送的時候,TCP 協議爲每個包編號(sequence number,簡稱 SEQ),以便接收的一方按照順序還原。萬一發生丟包,也可以知道丟失的是哪一個包。

第一個包的編號是一個隨機數。爲了便於理解,這裏就把它稱爲1號包。假定這個包的負載長度是100字節,那麼可以推算出下一個包的編號應該是101。這就是說,每個數據包都可以得到兩個編號:自身的編號,以及下一個包的編號。接收方由此知道,應該按照什麼順序將它們還原成原始文件。

(圖片說明:當前包的編號是45943,下一個數據包的編號是46183,由此可知,這個包的負載是240字節。)

四、TCP 數據包的組裝

收到 TCP 數據包以後,組裝還原是操作系統完成的。應用程序不會直接處理 TCP 數據包。

對於應用程序來說,不用關心數據通信的細節。除非線路異常,收到的總是完整的數據。應用程序需要的數據放在 TCP 數據包裏面,有自己的格式(比如 HTTP 協議)。

TCP 並沒有提供任何機制,表示原始文件的大小,這由應用層的協議來規定。比如,HTTP 協議就有一個頭信息Content-Length,表示信息體的大小。對於操作系統來說,就是持續地接收 TCP 數據包,將它們按照順序組裝好,一個包都不少。

操作系統不會去處理 TCP 數據包裏面的數據。一旦組裝好 TCP 數據包,就把它們轉交給應用程序。TCP 數據包裏面有一個端口(port)參數,就是用來指定轉交給監聽該端口的應用程序。

(圖片說明:系統根據 TCP 數據包裏面的端口,將組裝好的數據轉交給相應的應用程序。上圖中,21端口是 FTP 服務器,25端口是 SMTP 服務,80端口是 Web 服務器。)

應用程序收到組裝好的原始數據,以瀏覽器爲例,就會根據 HTTP 協議的Content-Length字段正確讀出一段段的數據。這也意味着,一次 TCP 通信可以包括多個 HTTP 通信。

五、慢啓動和 ACK

服務器發送數據包,當然越快越好,最好一次性全發出去。但是,發得太快,就有可能丟包。帶寬小、路由器過熱、緩存溢出等許多因素都會導致丟包。線路不好的話,發得越快,丟得越多。

最理想的狀態是,在線路允許的情況下,達到最高速率。但是我們怎麼知道,對方線路的理想速率是多少呢?答案就是慢慢試。

TCP 協議爲了做到效率與可靠性的統一,設計了一個慢啓動(slow start)機制。開始的時候,發送得較慢,然後根據丟包的情況,調整速率:如果不丟包,就加快發送速度;如果丟包,就降低發送速度。

Linux 內核裏面設定了(常量TCP_INIT_CWND),剛開始通信的時候,發送方一次性發送10個數據包,即"發送窗口"的大小爲10。然後停下來,等待接收方的確認,再繼續發送。

默認情況下,接收方每收到兩個 TCP 數據包,就要發送一個確認消息。"確認"的英語是 acknowledgement,所以這個確認消息就簡稱 ACK。

ACK 攜帶兩個信息。

  • 期待要收到下一個數據包的編號
  • 接收方的接收窗口的剩餘容量

發送方有了這兩個信息,再加上自己已經發出的數據包的最新編號,就會推測出接收方大概的接收速度,從而降低或增加發送速率。這被稱爲"發送窗口",這個窗口的大小是可變的。

bg2017060809.pnguploading.4e448015.gif轉存失敗重新上傳取消bg2017060809.pnguploading.4e448015.gif轉存失敗重新上傳取消bg2017060809.pnguploading.4e448015.gif轉存失敗重新上傳取消

(圖片說明:每個 ACK 都帶有下一個數據包的編號,以及接收窗口的剩餘容量。雙方都會發送 ACK。)

注意,由於 TCP 通信是雙向的,所以雙方都需要發送 ACK。兩方的窗口大小,很可能是不一樣的。而且 ACK 只是很簡單的幾個字段,通常與數據合併在一個數據包裏面發送。

(圖片說明:上圖一共4次通信。第一次通信,A 主機發給B 主機的數據包編號是1,長度是100字節,因此第二次通信 B 主機的 ACK 編號是 1 + 100 = 101,第三次通信 A 主機的數據包編號也是 101。同理,第二次通信 B 主機發給 A 主機的數據包編號是1,長度是200字節,因此第三次通信 A 主機的 ACK 是201,第四次通信 B 主機的數據包編號也是201。)

即使對於帶寬很大、線路很好的連接,TCP 也總是從10個數據包開始慢慢試,過了一段時間以後,才達到最高的傳輸速率。這就是 TCP 的慢啓動。

六、數據包的遺失處理

TCP 協議可以保證數據通信的完整性,這是怎麼做到的?

前面說過,每一個數據包都帶有下一個數據包的編號。如果下一個數據包沒有收到,那麼 ACK 的編號就不會發生變化。

舉例來說,現在收到了4號包,但是沒有收到5號包。ACK 就會記錄,期待收到5號包。過了一段時間,5號包收到了,那麼下一輪 ACK 會更新編號。如果5號包還是沒收到,但是收到了6號包或7號包,那麼 ACK 裏面的編號不會變化,總是顯示5號包。這會導致大量重複內容的 ACK。

如果發送方發現收到三個連續的重複 ACK,或者超時了還沒有收到任何 ACK,就會確認丟包,即5號包遺失了,從而再次發送這個包。通過這種機制,TCP 保證了不會有數據包丟失。

(圖片說明:Host B 沒有收到100號數據包,會連續發出相同的 ACK,觸發 Host A 重發100號數據包。)

 

 

關於tcp你需要注意的幾點

轉載: https://coolshell.cn/articles/11564.html

TCP頭格式

å¨è¿éæå¥å¾çæè¿°

 

1、TCP的包是沒有IP地址的,那是IP層上的事。但是有源端口和目標端口。

2、一個TCP連接需要四個元組來表示是同一個連接(src_ip, src_port, dst_ip, dst_port)準確說是五元組,還有一個是協議。但因爲這裏只是說TCP協議,所以,這裏我只說四元組。

3、注意上圖中的四個非常重要的東西:

Sequence Number是包的序號,用來解決網絡包亂序(reordering)問題。
Acknowledgement Number就是ACK——用於確認收到,用來解決不丟包的問題。
Window又叫Advertised-Window,也就是著名的滑動窗口(Sliding Window),用於解決流控的。
TCP Flag ,也就是包的類型,主要是用於操控TCP的狀態機的。

4、關於建連接時SYN超時。試想一下,如果server端接到了clien發的SYN後回了SYN-ACK後client掉線了,server端沒有收到client回來的ACK,那麼,這個連接處於一箇中間狀態,即沒成功,也沒失敗。於是,server端如果在一定時間內沒有收到的TCP會重發SYN-ACK。在Linux下,默認重試次數爲5次,重試的間隔時間從1s開始每次都翻售,5次的重試時間間隔爲1s, 2s, 4s, 8s, 16s,總共31s,第5次發出後還要等32s都知道第5次也超時了,所以,總共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 2^6 -1 = 63s,TCP纔會把斷開這個連接。


5、關於SYN Flood攻擊。一些惡意的人就爲此製造了SYN Flood攻擊——給服務器發了一個SYN後,就下線了,於是服務器需要默認等63s纔會斷開連接,這樣,攻擊者就可以把服務器的syn連接的隊列耗盡,讓正常的連接請求不能處理。於是,Linux下給了一個叫tcp_syncookies的參數來應對這個事——當SYN隊列滿了後,TCP會通過源地址端口、目標地址端口和時間戳打造出一個特別的Sequence Number發回去(又叫cookie),如果是攻擊者則不會有響應,如果是正常連接,則會把這個 SYN Cookie發回來,然後服務端可以通過cookie建連接(即使你不在SYN隊列中)。請注意,請先千萬別用tcp_syncookies來處理正常的大負載的連接的情況。因爲,synccookies是妥協版的TCP協議,並不嚴謹。對於正常的請求,你應該調整三個TCP參數可供你選擇,第一個是:tcp_synack_retries 可以用他來減少重試次數;第二個是:tcp_max_syn_backlog,可以增大SYN連接數;第三個是:tcp_abort_on_overflow 處理不過來乾脆就直接拒絕連接了。


6、關於ISN的初始化。ISN是不能hard code的,不然會出問題的——比如:如果連接建好後始終用1來做ISN,如果client發了30個segment過去,但是網絡斷了,於是 client重連,又用了1做ISN,但是之前連接的那些包到了,於是就被當成了新連接的包,此時,client的Sequence Number 可能是3,而Server端認爲client端的這個號是30了。全亂了。RFC793中說,ISN會和一個假的時鐘綁在一起,這個時鐘會在每4微秒對ISN做加一操作,直到超過2^32,又從0開始。這樣,一個ISN的週期大約是4.55個小時。因爲,我們假設我們的TCP Segment在網絡上的存活時間不會超過Maximum Segment Lifetime(縮寫爲MSL – Wikipedia語條),所以,只要MSL的值小於4.55小時,那麼,我們就不會重用到ISN。


7、關於 MSL 和 TIME_WAIT。通過上面的ISN的描述,相信你也知道MSL是怎麼來的了。我們注意到,在TCP的狀態圖中,從TIME_WAIT狀態到CLOSED狀態,有一個超時設置,這個超時設置是 2*MSL(RFC793定義了MSL爲2分鐘,Linux設置成了30s)爲什麼要這有TIME_WAIT?爲什麼不直接給轉成CLOSED狀態呢?主要有兩個原因:1)TIME_WAIT確保有足夠的時間讓對端收到了ACK,如果被動關閉的那方沒有收到Ack,就會觸發被動端重發Fin,一來一去正好2個MSL,2)有足夠的時間讓這個連接不會跟後面的連接混在一起(你要知道,有些自做主張的路由器會緩存IP數據包,如果連接被重用了,那麼這些延遲收到的包就有可能會跟新連接混在一起)。你可以看看這篇文章《TIME_WAIT and its design implications for protocols and scalable client server systems》


8、關於TIME_WAIT數量太多。從上面的描述我們可以知道,TIME_WAIT是個很重要的狀態,但是如果在大併發的短鏈接下,TIME_WAIT 就會太多,這也會消耗很多系統資源。只要搜一下,你就會發現,十有八九的處理方式都是教你設置兩個參數,一個叫tcp_tw_reuse,另一個叫tcp_tw_recycle的參數,這兩個參數默認值都是被關閉的,後者recyle比前者resue更爲激進,resue要溫柔一些。另外,如果使用tcp_tw_reuse,必需設置tcp_timestamps=1,否則無效。這裏,你一定要注意,打開這兩個參數會有比較大的坑——可能會讓TCP連接出一些詭異的問題(因爲如上述一樣,如果不等待超時重用連接的話,新的連接可能會建不上。正如官方文檔上說的一樣“It should not be changed without advice/request of technical experts”)。


9、關於tcp_tw_reuse。官方文檔上說tcp_tw_reuse 加上tcp_timestamps(又叫PAWS, for Protection Against Wrapped Sequence Numbers)可以保證協議的角度上的安全,但是你需要tcp_timestamps在兩邊都被打開(你可以讀一下tcp_twsk_unique的源碼 )。我個人估計還是有一些場景會有問題。


10、關於tcp_tw_recycle。如果是tcp_tw_recycle被打開了話,會假設對端開啓了tcp_timestamps,然後會去比較時間戳,如果時間戳變大了,就可以重用。但是,如果對端是一個NAT網絡的話(如:一個公司只用一個IP出公網)或是對端的IP被另一臺重用了,這個事就複雜了。建鏈接的SYN可能就被直接丟掉了(你可能會看到connection time out的錯誤)(如果你想觀摩一下Linux的內核代碼,請參看源碼 tcp_timewait_state_process)。


11、關於tcp_max_tw_buckets。這個是控制併發的TIME_WAIT的數量,默認值是180000,如果超限,那麼,系統會把多的給destory掉,然後在日誌裏打一個警告(如:time wait bucket table overflow),官網文檔說這個參數是用來對抗DDoS攻擊的。也說的默認值180000並不小。這個還是需要根據實際情況考慮。


12、Again,使用tcp_tw_reuse和tcp_tw_recycle來解決TIME_WAIT的問題是非常非常危險的,因爲這兩個參數違反了TCP協議(RFC 1122)

13、其實,TIME_WAIT表示的是你主動斷連接,所以,這就是所謂的“不作死不會死”。試想,如果讓對端斷連接,那麼這個破問題就是對方的了,呵呵。另外,如果你的服務器是於HTTP服務器,那麼設置一個HTTP的KeepAlive有多重要(瀏覽器會重用一個TCP連接來處理多個HTTP請求),然後讓客戶端去斷鏈接(你要小心,瀏覽器可能會非常貪婪,他們不到萬不得已不會主動斷連接)。

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