天天看點

TCP/IP知識點及面試常考點總結

“ 本文主要包含兩個方面,一是從計算機網絡分層模型出發,分析每一個層的功能,以及TCP/IP協定棧的實作;二是介紹TCP相關面試常考點。 ”

開局一張圖,計算機網絡體系結構如下:

TCP/IP知識點及面試常考點總結

network-layers

本文将采用右側五層模型從下往上依次講解,詳細講述每一層的協定和作用,并且會層層介紹協定頭如何實作,其中資料鍊路層、網絡層和傳輸層屬于核心(TCP/IP協定棧屬于核心),也是重點介紹的内容。

功能:主要進行實體信号傳輸,負責A/D轉換,即實體信号和數字信号之間的轉換。

互動中間裝置:轉發器

網絡擴充卡工作在這一層,是以Mac位址屬于這一層,需要進行差錯控制等

資料傳輸機關:MAC幀

互動中間裝置:網橋/橋接器(bridge)

協定:以太網協定

封裝成幀(幀頭SOH,幀尾EOT,中間為IP資料報),透明傳輸(ESC位元組填充),差錯檢測(循環備援檢測CRC)

以太網MAC層的硬體位址,(計算機的硬體位址-MAC位址,在擴充卡的ROM中;軟體位址-IP位址在存儲器中)

擴充卡是什麼?為什麼要有擴充卡?

擴充卡(網絡接口卡NIC+ROM+RAM等),擴充卡與區域網路之間的通信通過電纜/雙絞線以串行傳輸方式進行,擴充卡與計算機之間的通信通過主機闆上IO以并行傳輸方式進行。兩者資料率不一緻,是以擴充卡需要緩存。

Mac幀格式如下圖:

TCP/IP知識點及面試常考點總結

mac_layer

前12個位元組表示目的位址和源位址(48位長);在不同網絡上傳輸時,mac位址會不斷變化

接下來2個位元組表示上層協定(0x0800 IP資料報)

接下來資料範圍(46位元組-1500位元組)

FCS,4個位元組,采用CRC檢驗

以太網協定頭實作:

互動中間裝置:路由器

本層主要介紹三個協定:IP協定,ICMP協定,以及ARP協定

IP協定;資料傳輸機關為IP資料報,向上為運輸層提供資料(解包),向下将運輸層資料封裝成包進行傳輸

不可靠(不能保證IP資料包成功到達目的地),無連接配接(每個資料報處理都是互相獨立的,也不保序)

網絡位元組序:(4個位元組的32bit值以大端位元組序傳輸:0~7bit,8~15,16~23,24~31,是以大端位元組序又稱為網絡位元組序,是以在傳輸資料前,如果主機采用的是小端序,需要先轉換成網絡/大端位元組序,再進行傳輸)

接下來介紹IP協定頭的實作,以下為IP資料報頭部固定20個位元組,如下圖:

TCP/IP知識點及面試常考點總結

ip_format

版本:4位,IPv4/6

首部長度:4位

區分服務:1個位元組服務類型TOS;4個bit,每一位分别表示最小時延,最大吞吐量,最高可靠性和最小費用,隻能置其中1個bit為1,其它的都為0;例如Telnet将第一位置為1,表示最小時延,主要用來傳輸少量的互動資料。FTP檔案傳輸要求最大吞吐量;

總長度:2個位元組,首部和資料合總長度(2^16)

辨別:2個位元組,一個資料報對應一個辨別(相同表示片組成同一資料包)

标志:3位,最低位MF(==1 還有分片,==0沒有分片);中間位DF(不能分片)

片偏移:13位,較長的分組分片後,某片在原分組中的位置

生存時間TTL:1個位元組,資料報在網絡中的壽命(最大跳數)

協定:1個位元組,

首部檢驗合:2個位元組,隻檢驗資料報首部;

源位址+目的位址:各4個位元組

IP協定頭實作:

ICMP協定(網際控制封包協定):為更有效轉發IP資料報和提高傳遞成功機會。主要功能:确認IP是否成功到達目的位址,報告發送過程中IP包被廢棄的原因和改善網絡設定等。

ICMP差錯封包報告

終點不可達消息,類型為3,分為網絡不可達,主機不可達,協定不可達,端口不可達,需要分片但設定了不分片

重定向消息,類型為5

逾時消息:類型11,

重定向

ICMP詢問封包類型

回送請求(Echo Request 類型8)和回送應答(Echo Reply 類型0)PING(直接隻用網絡層,沒有經過傳輸層)

時間戳請求和回答

traceroute(逐一增加ttl)是ICMP差錯封包類型的使用,

追蹤來去目的地時沿途經過的路由器:通過不斷增加TTL實作,當TTL減少到0時,會傳回ICMP差錯封包(類型為逾時)。直到到達目的IP

确認路徑MTU:将IP包首部的分片禁止标志位設定為1,路由器不會對大資料包分片,進而将包丢棄,并傳回一個ICMP不可達消息并攜帶資料鍊路上的MTU值。不可達消息類型為“需要分片但是設定了不分片。”

ping是ICMP詢問封包的使用,通過回送消息判斷所發送的資料包死否已經成功到達對端。

ARP協定(位址解析協定):每台主機都有一個ARP Cache(高速緩存),存有本區域網路上各主機/路由器的IP位址到MAC位址的映射表。通過使用ARP,找到IP對應的MAC位址,找不到則交給路由器處理(下一跳)。是以MAC位址隻在本區域網路有效。ARP的功能是在32bit的IP位址和采用不同網絡技術的硬體位址之間提供動态映射;IP位址和MAC位址的關聯儲存在ARP表中,由驅動程式和作業系統完成。

網絡層以上的互動中間裝置:網關(gateway)

IP網絡層角度:通信的端點是兩台主機;運輸層角度:通信端點是兩台主機中的程序。

運輸層向程序通信提供通用的資料傳輸服務,提供了TCP/UDP兩種協定

TCP傳輸控制協定:面向連接配接的、面向位元組流的,全雙工的,可靠的資料傳輸服務,資料傳輸機關為封包段(segment);TCP連接配接的端點叫做socket(IP+Port).

UDP使用者資料報協定:無連接配接的,盡最大努力資料傳輸服務(不可靠),資料傳輸機關為使用者資料報,一般用在對實時性和傳輸效率有一定要求的場景(下載下傳,遊戲等)

UDP協定頭:

TCP/IP知識點及面試常考點總結

udp_format

沒有擁塞控制,首部開銷小(8個位元組)

UDP協定頭和udp資料包

TCP協定頭

TCP/IP知識點及面試常考點總結

tcp_format

源端口+目的端口:各占2個位元組

序号(Seq):4個位元組,封包段序号

确認号(Ack):4個位元組,期望收到對方下一個封包段第一個資料位元組的序号

資料偏移:4位,

保留:4位

緊急URG:=1,表示緊急字段有效

ACK:=1,則Ack字段有效

PSH:推送,告知對方這些資料包收到以後應該馬上交給上層應用,不能緩存起來

RST:複位,TCP出現嚴重差錯,釋放連接配接,重建立立連接配接。

SYN:在建立連接配接時用來同步信号,=1表示連接配接請求或者接受封包。

FIN:用來釋放一個連接配接,=1表示資料發送完畢。

視窗大小:2個位元組,表示發送封包段一方的接受視窗。

校驗和:2個位元組,首部+資料

緊急指針:URG==1才生效

MSS,MSL等概念

TCP/IP知識點及面試常考點總結

internet_pakcet

MSS:TCP封包段中應用資料字段的最大長度,不是TCP封包總長度。

MTU(以太網資料幀長度46~1500位元組): 最大傳輸單元 (MTU = MSS + TCP頭20位元組+IP頭20位元組),當IP層資料長度大于MTU時,IP層需要對資料進行分片(Fragmentation)

路徑MTU:一個包從發送端傳輸到接收端,中間要跨越多個網絡,每條鍊路的MTU都可能不一樣,這個通信過程中最小的MTU稱為路徑MTU。

最高層,應用程式之間進行通信(程序間通信),互動資料單元為封包(Message)

TCP/IP知識點及面試常考點總結

network_proto

下面有兩張圖,第一張清楚展示了從建立連接配接到資料傳輸再到最後斷開連接配接的整個過程,第二張為狀态轉移圖。接下來會基于這兩張圖對TCP的三次握手和四次揮手進行講解。

TCP/IP知識點及面試常考點總結

tcp_shake

TCP/IP知識點及面試常考點總結

tcp_state

主動端調用connect發起連接配接,首先發送一個SYN包(将TCP首部SYN标記置為1),告訴被動端初始化序列号是x,這時主動端進入SYN_SENT狀态,被動端處在Listen狀态(調用listen後進入此狀态)

被動端收到SYN包後回複ACK表明已收到,并發送自己的初始化序列号y(将TCP首部SYN标記和ACK标記都置為1),被動端把這個連接配接資訊放入SYN隊列,進入SYN_RCVD狀态

主動端收到包後,回複一個ACK确認包,此時雙方進入ESATABLISED狀态,被動端把這個連接配接資訊從SYN隊列移除,并将其放入Accept隊列中。

主動端發送一個FIN包給被動端(TCP首部FIN标志置為1),表示沒有資料傳輸,要求斷開連接配接,這時主動端進入FIN_WAIT_1狀态

被動端收到FIN包後,回複一個ACK包,這時被動端進入CLOSE_WAIT狀态,主動端收到ACK後進入FIN_WAIT_2狀态

被動端沒有資料再發送後,也向主動端發送一個FIN包,被動端進入LAST_ACK狀态

主動端收到FIN包,并回複一個ACK包,主動端從FIN_WAIT_2狀态進入TIME_WAIT狀态。

握手不一定都是3次?

可能出現4次握手情況,比如雙方同時發起SYN隊列建立連接配接時,如下圖

TCP/IP知識點及面試常考點總結

tcp_ip_closing

初始序列号(ISN,Inital Sequence Number)能固定嗎?

不能。如果固定,雙方建立連接配接後主動方發送的資料包被路由器緩存了(路由器是會出現緩存甚至丢棄資料包的),這是主動方挂掉了,然後又采用同樣的端口連接配接到被動端,這時候如果上個連接配接被路由器緩存的資料包到了被動端,豈不是序列号完全錯亂了?(SO_REUSEADDR允許端口重用:收到一個包後不知道屬于新連接配接還是舊連接配接,也導緻串包。)

在RFC793中,初始化序列号是每4微秒加一,直到逾時2^32,又從0開始回繞(大概4.5個小時);這種方式遞增也容易讓攻擊者猜到ISN的值(進而僞造RST包将連接配接強制關掉),是以一般采用在一個基準值上随機加。

初始化連接配接時,如果主動方發送完SYN後就挂掉了,此時連接配接處于什麼狀态?如果大量這種連接配接出現會造成什麼危害?以及如何解決?

被動方收到SYN包後就會将這個連接配接放入SYN半連接配接隊列,一直占用伺服器資源,如果大量這種連接配接會把SYN半連接配接隊列的資源耗盡(這就是所謂的DDos攻擊和SYN Flood),進而讓正常連接配接無法得到處理。Linux提供了相應的逾時機制,進行5次重發SYN-ACK包,時間間隔以此為1s, 2s,4s,8s,16s,是以需要63s才能斷開連接配接。但是這個時間給了攻擊者可乘之機。

如何應對SYN Flood攻擊:

增加SYN連接配接數:tcp_max_syn_backlog

減少SYN+ACK重試次數:tcp_synack_retries

SYN Cookie機制:tcp_syncookies, 原理為最後階段才配置設定連接配接資源,服務端收到SYN包後,根據這個包計算一個Cookie值,作為握手第二步的序列号回複SYN+ACK,等對方回應ACK包時校驗回複的ACK值是否合法,合法才握手成功配置設定資源。具體實作linux/syncookies.c

什麼是SYN隊列,什麼是Accept隊列,listen中backlog參數是指啥?

syn隊列(半連接配接隊列):服務端收到用戶端的SYN包并回複SYN+ACK後,該連接配接的資訊會被放入一個隊列,即為SYN半連接配接隊列(此使TCP處于非同步狀态),SYN半連接配接隊列由tcp_max_syn_backlog這個核心參數決定,如果隊列滿了,服務端會丢棄新來的SYN包,用戶端在多次重發 SYN 包得不到響應而傳回(connection time out)錯誤。但是,當服務端開啟了 syncookies,那麼 SYN 半連接配接隊列就沒有邏輯上的最大值了,且tcp_max_syn_backlog 設定的值也會被忽略。

accept隊列(全連接配接隊列):Server端收到SYN+ACK包的ACK後,會将連接配接資訊從SYN半連接配接隊列移到另一個隊列,即為Accept全連接配接隊列(TCP連接配接建立,三次握手完成);accept 連接配接隊列的大小是由 backlog 參數和(/proc/sys/net/core/somaxconn)核心參數共同決定,取值為兩個中的最小值。當 accept 連接配接隊列滿了,協定棧的行為根據(/proc/sys/net/ipv4/tcp_abort_on_overflow)核心參數而定。

半連接配接隊列和全連接配接隊列如下圖:

TCP/IP知識點及面試常考點總結

tcp_syn_queue

listen, accept,send等系統調用:

從accept隊列中取出一個節點

為該節點配置設定一個fd,将節點與fd一一對應,(fd -- 節點 -- 五元組(sip, dip, sport, dport, proto),fd通過五元組判斷用戶端的唯一性)

當accept隊列為空,則阻塞直到有資料,通過條件變量實作

listen:listen(fd, backlog)中第二個參數backlog就表示accept隊列長度(LInux kernel 2.2之後 ),服務端調用listen後,TCP狀态從CLOSE狀态變成LISTEN狀态,同時在核心建立半連接配接隊列和全連接配接隊列。

accept:clientfd = accept(listenfd, addr)

send:send(fd)通過fd找到五元組并找到對應的用戶端

tcp_abort_on_overflow=1,全連接配接隊列滿後,服務端直接發送RST給用戶端,用戶端出現(connection reset by peer)錯誤。

tcp_abort_on_overflow=0,全連接配接隊列滿後,服務端會丢掉用戶端發過來的ACK,随後重傳SYN+ACK。

什麼是TFO(Tcp Fast Open)快速打開:

分為兩階段:請求Fast Open Cookie和TCP Fast Open

之後,用戶端有了緩存在本地的cookies值:

用戶端發送SYN資料包,裡面包含資料和之前緩存在本地的Fast Open Cookie(之前SYN包不含資料)

服務都那檢驗TFO Cookie和是否合法,合法則傳回SYN+ACK+資料

用戶端發送ACK包

進行資料傳輸...

用戶端發送一個SYN包,頭部包含Fast Open選項,且該選項的Cookie為空,這表明用戶端請求Fast Open Cookie

服務端收到SYN包後,生成一個cookie值

服務端發送SYN+ACK包,在Options的Fast Open選項中設定cookie的值

用戶端緩存服務端的IP和收到的cookie值

四次揮手能不能變成三次?

可以。被動端收到主動端的FIN包後,也沒有資料要發送了,就把對ACK包和自己的FIN包同時發送給主動端,這樣四次揮手變成三次。

如果雙端同時發起斷開連接配接的FIN包,TCP狀态如何轉移呢?

雙方同時發送FIN包後兩者都進入FIN_WAIT_1狀态,如果FIN_WAIT_1狀态收到FIN包,會直接進入CLOSING狀态,在CLOSING狀态下收到自己發送的FIN包的ACK包後,進入TIME_WAIT狀态,即雙方可能出現完全一樣的狀态,并同時進入TIME_WAIT狀态,如下圖:

TCP/IP知識點及面試常考點總結

tcp_same_wait

為什麼要有TIME_WAIT狀态?

如果沒有TIME_WAIT狀态,主動方發送對FIN的ACK包後就關掉,而ACK包在路由過程中丢掉了,被動方沒有收到,就會逾時重傳FIN資料包,此時主動方已經關閉,被動友善無法正常關閉連接配接;是以需要有TIME_WAIT狀态以便能夠重發丢掉的被動方的FIN的ACK包。且TIME_WAIT狀态需要保持2*MSL(Max Segment Life Time TCP封包在網絡中的最大生存時間)。

TIME_WAIT會帶來哪些問題?

由于TIME_WAIT狀态需要等待2*MSL,才能斷開連接配接釋放占用的資源。會造成以下問題:

可以進行TIME_WAIT的快速回收和重用來緩解。

作為服務端,短時間記憶體關閉了大量的client連接配接,會造成伺服器上出現大量的TIME_WAIT狀态的連接配接,占據大量的tuple,嚴重消耗伺服器資源

作為用戶端,短時間内大量的斷開連接配接,會大量消耗用戶端機器的端口,畢竟端口隻有65535個,端口被耗盡,便無法發起新的連接配接。

TIME_WAIT的快速回收和重用

新連接配接SYN告知的初始序列号比TIME_WAIT老連結的序列号大

新到來的連接配接的時間戳比老連接配接的時間戳大

來自同一對端的TCP包攜帶了時間戳

之前某一台機器的某個tcp資料在MSL時間内到過本伺服器

機器新連接配接的時間戳小于上次TCP到來的時間戳,且內插補點大于重放視窗戳(TCP_PAWS_WINDOW)。

TIME_WAIT快速回收

Linux同時打開tcp_tw_recycle和tcp_timestamps(預設打開)兩個選項開啟快速回收

Linux下快速回收的時間為3.5 * RTO(Retransmission Timeout)。但是,開啟快速回收TIME_WAIT,可能造成同時滿足以下三種情況導緻新連接配接被拒絕:

NAT(Network Address Translator)出現是為了緩解IP位址耗盡的臨時方案, NAT:允許一個整體機構以一個公用IP(Internet Protocol)位址出現在Internet上,内部私有網絡位址(IP位址)翻譯成合法網絡IP位址的技術 NAT分為三種:靜态NAT(Static NAT)、動态位址NAT(Pooled NAT)、網絡位址端口轉換NAPT(Port-Level NAT)

但是需要考慮NAT(Network Address Translation網絡位址轉換):在一個 NAT 後面的所有機器在服務端看來都是一個機器(同一個公網IP),NAT 後面的那麼多機器的系統時間戳很可能不一緻,有些快,有些慢,會導緻丢包無法連接配接的情況,尤其在上網高峰期,由于網絡的擁塞,可能會導緻先發出的 TCP 包後到達伺服器的情況,導緻伺服器不響應。

TIME_WAIT重用

Linux同時開啟tcp_tw_reuse選項和tcp_timestamps選項開啟TIME_WAIT重用,還有一個條件:重用 TIME_WAIT 的條件是收到最後一個包後超過 1s。隻要滿足下面兩個點中的一點,一個TW狀态的四元組(即一個socket連接配接)可以重新被新到來的SYN連接配接使用:

重用在NAT環境下,依然存在風險,因為時間戳重用 TIME_WAIT 連接配接的機制的前提是 IP 位址唯一性,而NAT環境下所有的機器都屬同一個公網IP。

重用其實沒有解決TIME_WAIT造成的資源消耗問題,Linux中可以通過修改tcp_max_tw_buckets這個值來控制并發的TIME_WAIT數量。

TCP可靠性表現在:

對每個包提供校驗和

包的序列号解決了亂序,重複問題

重傳機制

流量控制,擁塞控制機制

如果TCP對每個SYN包都進行确認,那麼網絡中會出現大量ACK包,消耗大量帶寬,降低網絡使用率。是以通過延時确認機制和累計式确認機制來減少ACK包數量,并且将ACK包和資料一起傳輸提高效率。

累計式确認機制:即确認号X的确認表明所有X之前但不包括X的資料已經收到,而不是對所有順序包進行ACK

采用延遲确認,ACK在收到資料後并不馬上回複,而是延遲一段時間再回複

逾時重傳:發送完SYN包之後會開啟一個timer,timer到了還沒有收到ACK的話,就重傳SYN。那麼這個timer如何設定呢?如果太短的話ACK可能還在路上,會造成重傳浪費,過多的重傳會造成網絡擁塞,進一步加劇資料丢失,太長的話,效率又太差。是以,合理的做法就是應當根據網絡實際情況進行調整,一般根據往返時間RTT(Round Trip Time)來設定RTO(Retransmission TimeOut),一般RTO稍微大于RTT。

快速重傳:如果連續收到3次相同确認号ACK的包,就立刻進行重傳,因為連續收到3個相同ACK,表明目前網絡狀态好。

SACK确認機制(Selective Acknowledement機制):這種重傳方法不會重傳丢失的第一個包後面的所有包,而是隻針對性的傳丢失的包,在TCP首部Option字段中加上SACK即可開啟。

TCP首部有一個2個位元組大小的Window字段,其最大為(2^16=65535)個位元組。還有一個TCP視窗擴大因子,來用擴大大Window大小。這個視窗是接收端告訴發送端自己還有多少緩沖區可以接收資料,發送端根據這個視窗來調整發送資料的速率,進而達到端對端的流量控制。

TCP把要發送的資料放入發送緩沖區(Send Buffer),接收到的資料放入接收緩沖區(Receive Buffer),應用程式會不停的讀取接收緩沖區的内容進行處理;

流量控制做的事情就是,如果接收緩沖區已滿,發送端應該停止發送資料,為了控制發送端速率,接收端會告知用戶端自己接收視窗rwnd,也就是接收緩沖區中的空閑部分。

滑動視窗工作原理

發送端維護一個跟接收端大小一樣的發送視窗,視窗内的可發,視窗外的不可,視窗在發送序列上不斷後移。如下圖:

TCP/IP知識點及面試常考點總結

tcp_window

從上圖可看出,TCP發送端資料可分為4類,其中2,3兩部分合起來稱之為發送視窗,從左往右依次為:

下圖示範視窗滑動情況,收到36的ACK後,視窗向後滑動5個Byte

TCP/IP知識點及面試常考點總結

tcp_slide

已經發送并得到接收端ACK的

已經發送但未收到接收端ACK的

未發送但允許發送的(接收方還有空間)

未發送且不允許發送的(接收方沒空間)

0視窗問題

如果發送端收到一個0視窗,發送端是不能再發送資料的,如下圖:

TCP/IP知識點及面試常考點總結

tcp_windows_control

但是如果接收端一直發送0視窗呢?發送端會一直等待嗎?答案是否定的。TCP采用Zero Window Probe(ZWP零視窗探測)技術,發送端視窗變成0後,會ZWP給接收端,來探測視窗大小,三次探測後如果還是0的話,會RST調這個連接配接。

DDos攻擊點:攻擊者與服務端建立連接配接後,向服務端通知一個0視窗,服務端隻能等待ZWP,如果攻擊者發送大量這樣的請求,會耗盡服務端資源。

糊塗視窗綜合征問題(Silly Window Syndrome)

也即小包問題,如果20個位元組TCP首部,20個位元組IP首部,隻傳4個位元組的資料,那未免太浪費資源了,造成這個問題主要有以下兩個原因:

針對上述問題,分别由兩種解決方案:

Nagle算法:接收端不通知小視窗,僅長度大于MSS,或包含FIN,或設定了TCP_NODELAY,或逾時等情況才會立即發送;(是以可以通過設定TCP_NODELAY來禁用Nagle算法setsockopt(..., TCP_NODELAY))

發送端積累一下資料再發送

接收端一直在通知一個小視窗

發送端本身一直在發送小包

既然有了流量控制,為啥還需要擁塞控制?因為流量控制針對的是端對端,隻能看到對端情況,無法知道整個鍊路上的狀況,隻要雙方處理能力強,就可以很大速率發包,造成鍊路擁堵,引起大量丢包,大量丢包會導緻大量重傳,進一步加劇鍊路擁塞。是以需要擁塞控制來維護整個網絡的交通,在之前接收視窗(rwnd)的基礎上,加入一個擁塞視窗(cwnd),發送端真正視窗=min(rwnd, cwnd).

擁塞控制主要包括四個部分:慢啟動算法,擁塞避免算法,快速重傳算法,快速恢複算法。

慢啟動算法

慢啟動展現了一個試探的過程,剛接入網絡的時候先發包慢點,探測一下網絡情況,然後在慢慢提速。不要一上來就拼命發包,這樣很容易造成鍊路的擁堵,出現擁堵了在想到要降速來緩解擁堵這就有點成本高了,畢竟無數的先例告誡我們先污染後治理的成本是很高的。步驟如下:

連接配接建好初始化cwnd = N

每接收一個ACK,++cwnd,線性上升

每過一個RTT,cwnd = cwnd * 2, 指數上升

慢啟動門限ssthresh(slow start threadhold),當cwnd >= ssthresh,進入擁塞避免算法

擁塞避免算法

每收到一個ACK,cwnd=(cwnd + 1 / cwnd) * MSS個位元組

每經過一個RTT時長,cwnd+=1

快速重傳算法

(正常重傳需要等幾百毫秒)當接收端收到一個不按序到達的資料段時,TCP立刻發送1個重複ACK,當發送端收到3個或以上重複ACK,就意識到之前發的包可能丢了,就立馬重傳,而不用等重傳定時器逾時再重傳。并進入快速恢複階段,步驟如下:

調整門限sthresh的值為目前cwnd的1/2

将cwnd設定為新的ssthresh

重新進入擁塞避免階段

快速恢複算法

解釋為網絡輕度擁塞,步驟如下:

擁塞門檻值ssthread降低為cwnd的一半;

擁塞視窗cwnd設定為ssthresh

擁塞視窗線性增加

假設 Client 向 Server 連續發送了兩個資料包,用 packet1 和 packet2 來表示,那麼服務端收到的資料可以分為三種情況,現列舉如下:

第一種情況,接收端正常收到兩個資料包,即沒有發生拆包和粘包的現象。

TCP/IP知識點及面試常考點總結

tcp_packet_1

第二種情況,接收端隻收到一個資料包,但是這一個資料包中包含了發送端發送的兩個資料包的資訊,這種現象即為粘包。這種情況由于接收端不知道這兩個資料包的界限,是以對于接收端來說很難處理。

TCP/IP知識點及面試常考點總結

tcp_packet_2

第三種情況,這種情況有兩種表現形式,如下圖。接收端收到了兩個資料包,但是這兩個資料包要麼是不完整的,要麼就是多出來一塊,這種情況即發生了拆包和粘包。這兩種情況如果不加特殊處理,對于接收端同樣是不好處理的。

TCP/IP知識點及面試常考點總結

tcp_packet_3

UDP 是基于封包發送的,UDP首部采用了 16bit 來訓示 UDP 資料封包的長度,是以在應用層能很好的将不同的資料封包區分開,進而避免粘包和拆包的問題。

而 TCP 是基于位元組流的,雖然應用層和 TCP 傳輸層之間的資料互動是大小不等的資料塊,但是 TCP 并沒有把這些資料塊區分邊界,僅僅是一連串沒有結構的位元組流;另外從 TCP 的幀結構也可以看出,在 TCP 的首部沒有表示資料長度的字段,基于上面兩點,在使用 TCP 傳輸資料時,才有粘包或者拆包現象發生的可能。

另外,TCP短連接配接不用處理分包,因為發送方主動關閉連接配接後,表示一條消息發送完畢,接收方read傳回0,進而知道消息的結尾;隻有長連接配接采用處理

為什麼會發生TCP粘包、拆包?

要發送的資料大于TCP發送緩沖區剩餘空間大小(由接收端視窗決定,流量控制),将會進行拆包

待發送資料大于MSS(最大封包長度),TCP在傳輸前将進行拆包

要發送的資料小于TCP發送緩沖區的大小,TCP将多次寫入緩沖區的資料一次發送出去,将會發生粘包

接收端應用層沒有及時讀取接收緩沖區中的資料,将會發生粘包

粘包、拆包解決辦法

由于 TCP 本身是面向位元組流的,無法了解上層的業務資料,是以在底層是無法保證資料包不被拆分和重組的,這個問題隻能通過上層的應用協定棧設計來解決,根據業界的主流協定的解決方案,歸納如下

消息定長:發送端将每個資料包封裝為固定長度(不夠的可以通過補 0 填充),這樣接收端每次接收緩沖區中讀取固定長度的資料就自然而然的把每個資料包拆分開來。

設定消息邊界:服務端從網絡流中按消息邊界分離出消息内容。在包尾增加回車換行符進行分割,例如 FTP 協定。

消息頭+消息體結構:消息頭中包含表示消息總長度(或者消息體長度)的字段;收到包時,先接收固定位元組數的頭部,解出這個包完整長度,按照此長度接收包體。(這是目前網絡應用中用的最多);比如,每個消息有4位元組長度,以網絡序存放字元串長度,比如"hello" 和 "world!"兩條消息,打包後的位元組流為0x00, 0x00, 0x00, 0x05,'h','e','l','l','o',0x00,0x00,0x00,0x06,'w','o','r','l','d','!'

利用消息本身的格式來分包:比如XML格式的消息中...的配對,JSON格式中的{...}配對。解析這種消息格式通常會用到狀态機。

短連接配接:Client向Serer發送消息,Server回應Client,一次讀寫完成,雙方都可發起close操作。适用于web網站

長連接配接:Client與Server完成一次讀寫後,雙方不會主動關閉連接配接。靠心跳維持,需要一定的系統開銷,适用于頻繁互動的場景,如即時通信工具:微信,遊戲等

使用者态程序調用描述符上的read,它會導緻核心從其接收緩沖區中删除資料,并将該資料複制到此程序調用所提供的緩沖區中。

使用者态調用write時,會将資料從使用者提供的緩沖區複制到核心寫入隊列,核心再将資料從寫入隊列複制到NIC中。

首先網卡将接收到的資料放到核心緩沖區,核心緩沖區存放的資料根據TCP資訊将資料移動到具體的某一個TCP連接配接上的接收緩沖區内,也就是TCP接收滑動視窗内,然後應用程式從TCP的接收緩沖區内讀取資料,如果應用程式一直不讀取,那麼滑動視窗會變小,直至為0

TCP發給對方的資料,對方在收到資料時必須給矛确認,隻有在收到對方的确認時,本方TCP才會把TCP發送緩沖區中的資料删除。

UDP因為是不可靠連接配接,不必儲存應用程序的資料拷貝,應用程序中的資料在沿協定棧向下傳遞時,以某種形式拷貝到核心緩沖區,當資料鍊路層把資料傳出後就把核心緩沖區中資料拷貝删除。是以它不需要一個發送緩沖區。

tcp socket的發送緩沖區實際上是一個結構體struct sk_buff的隊列,我們可以把它稱為發送緩沖隊列,配置設定一個struct sk_buff是用于存放一個tcp資料報

最後介紹一個學習TCP的工具packeetdrill。其它執行個體參考這裡

packetdrill原理:在執行腳本前建立了一個名為tun0的虛拟網卡,腳本執行完後,tun0會被銷毀。該虛拟網卡對應于作業系統中/dev/net/tun檔案,每次程式通過write等系統調用将資料寫入到這個檔案fd時,這些資料會經過tun0這個虛拟網卡,将資料寫入到核心協定棧,read系統調用讀取資料的過程類似。協定棧可以向操作普通網卡一樣操作虛拟網卡tun0.

使用packetetdrill構造一個SYN_SENT狀态

執行netstat指令可檢視相關狀态

SYN包重傳了6次後關閉連接配接,通過參數tcp_syn_retries可以看到

主要講了計算機網絡分層模型,TCP/IP協定頭實作,TCP面試常考點:三次握手,四次揮手問題;TIME_WAIT狀态問題;TCP的可靠性和流量控制問題;TCP粘包、拆包問題;以及packetdrill工具使用。

參考: 《計算機網絡》 《TCP/IP詳解 卷一》 《Tcp小冊》

—————END—————推薦閱讀:

Google 開源的依賴注入庫,比 Spring 更小更快!

GitHub 近兩萬 Star,無需編碼,可一鍵生成前後端代碼

Spring Boot 中引入 MyBatisPlus 的正常流程

免費版的 IDEA 為啥不能使用 Tomcat ?

給新手的 11 個 Docker 免費上手項目

TCP/IP知識點及面試常考點總結

繼續閱讀