A. TCP協議詳解及實戰解析【精心整理收藏】
TCP協議是在TCP/IP協議模型中的運輸層中很重要的一個協議、負責處理主機埠層面之間的數據傳輸。主要有以下特點:
1.TCP是面向鏈接的協議,在數據傳輸之前需要通過三次握手建立TCP鏈接,當數據傳遞完成之後,需要通過四次揮手進行連接釋放。
2.每一條TCP通信都是兩台主機和主機之間的,是點對點傳輸的協議。
3.TCP提供可靠的、無差錯、不丟失、不重復,按序到達的服務。
4.TCP的通信雙方在連接建立的任何時候都可以發送數據。TCP連接的兩端都設有發送緩存和接收緩存,用來臨時存放雙向通信的數據。
5.面向位元組流。在數據傳輸的過程中如果報文比較長的話TCP會進行數據分段傳輸,每一條分段的TCP傳輸信息都帶有分段的序號,每一段都包含一部分位元組流。接收方根據每段攜帶的的序號信息進行數據拼接,最終拼接出來初始的傳輸數據。但是在整個傳輸的過程中每一段TCP攜帶的都是被切割的位元組流數據。所以說TCP是面向位元組流的。
a.TCP和UDP在發送報文時所採用的方式完全不同。TCP並不關心應用程序一次把多長的報文發送到TCP緩存中,而是根據對方給出的窗口值和當前網路擁塞的程度來決定一個報文段應包含多少個位元組(UDP發送的報文長度是應用程序給出的)。
b.如果應用程序傳送到TCP緩存的數據塊太大,TCP就可以把它劃分短一些再傳。TCP也可以等待積累有足夠多的位元組後再構建成報文段發送出去。
各欄位含義:
源埠:發送端的埠號
目的埠:接收端的埠號
序號:TCP將發送報文分段傳輸的時候會給每一段加上序號,接收端也可以根據這個序號來判斷數據拼接的順序,主要用來解決網路報亂序的問題
確認號:確認號為接收端收到數據之後進行排序確認以及發送下一次期待接收到的序號,數值 = 接收到的發送號 + 1
數據偏移:佔4比特,表示數據開始的地方離TCP段的起始處有多遠。實際上就是TCP段首部的長度。由於首部長度不固定,因此數據偏移欄位是必要的。數據偏移以32位為長度單位,因此TCP首部的最大長度是60(15*4)個位元組。
控制位:
URG:此標志表示TCP包的緊急指針域有效,用來保證TCP連接不被中斷,並且督促 中間層設備要盡快處理這些數據;
ACK:此標志表示應答域有效,就是說前面所說的TCP應答號將會包含在TCP數據包中;有兩個取值:0和1, 為1的時候表示應答域有效,反之為0;
PSH:這個標志位表示Push操作。所謂Push操作就是指在數據包到達接收端以後,立即傳送給應用程序, 而不是在緩沖區中排隊;
RST:這個標志表示連接復位請求。用來復位那些產生錯誤的連接,也被用來拒絕錯誤和非法的數據包;
SYN:表示同步序號,用來建立連接。SYN標志位和ACK標志位搭配使用,當連接請求的時候,SYN=1, ACK=0;連接被響應的時候,SYN=1,ACK=1;這個標志的數據包經常被用來進行埠掃描。掃描者發送 一個只有SYN的數據包,如果對方主機響應了一個數據包回來 ,就表明這台主機存在這個埠;但是由於這 種掃描方式只是進行TCP三次握手的第一次握手,因此這種掃描的成功表示被掃描的機器不很安全,一台安全 的主機將會強制要求一個連接嚴格的進行TCP的三次握手;
FIN: 表示發送端已經達到數據末尾,也就是說雙方的數據傳送完成,沒有數據可以傳送了,發送FIN標志 位的TCP數據包後,連接將被斷開。這個標志的數據包也經常被用於進行埠掃描。
窗口:TCP里很重要的一個機制,佔2位元組,表示報文段發送方期望接收的位元組數,可接收的序號范圍是從接收方的確認號開始到確認號加上窗口大小之間的數據。後面會有實例講解。
校驗和:校驗和包含了偽首部、TCP首部和數據,校驗和是TCP強制要求的,由發送方計算,接收方驗證
緊急指針:URG標志為1時,緊急指針有效,表示數據需要優先處理。緊急指針指出在TCP段中的緊急數據的最後一個位元組的序號,使接收方可以知道緊急數據共有多長。
選項:最常用的選項是最大段大小(Maximum Segment Size,MSS),向對方通知本機可以接收的最大TCP段長度。MSS選項只在建立連接的請求中發送。
放在乙太網幀里看TCP的位置
TCP 數據包在 IP 數據包的負載裡面。它的頭信息最少也需要20位元組,因此 TCP 數據包的最大負載是 1480 - 20 = 1460 位元組。由於 IP 和 TCP 協議往往有額外的頭信息,所以 TCP 負載實際為1400位元組左右。
因此,一條1500位元組的信息需要兩個 TCP 數據包。HTTP/2 協議的一大改進, 就是壓縮 HTTP 協議的頭信息,使得一個 HTTP 請求可以放在一個 TCP 數據包裡面,而不是分成多個,這樣就提高了速度。
乙太網數據包的負載是1500位元組,TCP 數據包的負載在1400位元組左右
一個包1400位元組,那麼一次性發送大量數據,就必須分成多個包。比如,一個 10MB 的文件,需要發送7100多個包。
發送的時候,TCP 協議為每個包編號(sequence number,簡稱 SEQ),以便接收的一方按照順序還原。萬一發生丟包,也可以知道丟失的是哪一個包。
第一個包的編號是一個隨機數。為了便於理解,這里就把它稱為1號包。假定這個包的負載長度是100位元組,那麼可以推算出下一個包的編號應該是101。這就是說,每個數據包都可以得到兩個編號:自身的編號,以及下一個包的編號。接收方由此知道,應該按照什麼順序將它們還原成原始文件。
收到 TCP 數據包以後,組裝還原是操作系統完成的。應用程序不會直接處理 TCP 數據包。
對於應用程序來說,不用關心數據通信的細節。除非線路異常,否則收到的總是完整的數據。應用程序需要的數據放在 TCP 數據包裡面,有自己的格式(比如 HTTP 協議)。
TCP 並沒有提供任何機制,表示原始文件的大小,這由應用層的協議來規定。比如,HTTP 協議就有一個頭信息Content-Length,表示信息體的大小。對於操作系統來說,就是持續地接收 TCP 數據包,將它們按照順序組裝好,一個包都不少。
操作系統不會去處理 TCP 數據包裡面的數據。一旦組裝好 TCP 數據包,就把它們轉交給應用程序。TCP 數據包裡面有一個埠(port)參數,就是用來指定轉交給監聽該埠的應用程序。
應用程序收到組裝好的原始數據,以瀏覽器為例,就會根據 HTTP 協議的Content-Length欄位正確讀出一段段的數據。這也意味著,一次 TCP 通信可以包括多個 HTTP 通信。
伺服器發送數據包,當然越快越好,最好一次性全發出去。但是,發得太快,就有可能丟包。帶寬小、路由器過熱、緩存溢出等許多因素都會導致丟包。線路不好的話,發得越快,丟得越多。
最理想的狀態是,在線路允許的情況下,達到最高速率。但是我們怎麼知道,對方線路的理想速率是多少呢?答案就是慢慢試。
TCP 協議為了做到效率與可靠性的統一,設計了一個慢啟動(slow start)機制。開始的時候,發送得較慢,然後根據丟包的情況,調整速率:如果不丟包,就加快發送速度;如果丟包,就降低發送速度。
Linux 內核裡面 設定 了(常量TCP_INIT_CWND),剛開始通信的時候,發送方一次性發送10個數據包,即"發送窗口"的大小為10。然後停下來,等待接收方的確認,再繼續發送。
默認情況下,接收方每收到 兩個 TCP 數據包,就要 發送 一個確認消息。"確認"的英語是 acknowledgement,所以這個確認消息就簡稱 ACK。
ACK 攜帶兩個信息。
發送方有了這兩個信息,再加上自己已經發出的數據包的最新編號,就會推測出接收方大概的接收速度,從而降低或增加發送速率。這被稱為"發送窗口",這個窗口的大小是可變的。
注意,由於 TCP 通信是雙向的,所以雙方都需要發送 ACK。兩方的窗口大小,很可能是不一樣的。而且 ACK 只是很簡單的幾個欄位,通常與數據合並在一個數據包裡面發送。
即使對於帶寬很大、線路很好的連接,TCP 也總是從10個數據包開始慢慢試,過了一段時間以後,才達到最高的傳輸速率。這就是 TCP 的慢啟動。
TCP 協議可以保證數據通信的完整性,這是怎麼做到的?
前面說過,每一個數據包都帶有下一個數據包的編號。如果下一個數據包沒有收到,那麼 ACK 的編號就不會發生變化。
舉例來說,現在收到了4號包,但是沒有收到5號包。ACK 就會記錄,期待收到5號包。過了一段時間,5號包收到了,那麼下一輪 ACK 會更新編號。如果5號包還是沒收到,但是收到了6號包或7號包,那麼 ACK 裡面的編號不會變化,總是顯示5號包。這會導致大量重復內容的 ACK。
如果發送方發現收到 三個 連續的重復 ACK,或者超時了還沒有收到任何 ACK,就會確認丟包,即5號包遺失了,從而再次發送這個包。通過這種機制,TCP 保證了不會有數據包丟失。
TCP是一個滑動窗口協議,即一個TCP連接的發送端在某個時刻能發多少數據是由滑動窗口控制的,而滑動窗口的大小實際上是由兩個窗口共同決定的,一個是接收端的通告窗口,這個窗口值在TCP協議頭部信息中有,會隨著數據的ACK包發送給發送端,這個值表示的是在接收端的TCP協議緩存中還有多少剩餘空間,發送端必須保證發送的數據不超過這個剩餘空間以免造成緩沖區溢出,這個窗口是接收端用來進行流量限制的,在傳輸過程中,通告窗口大小與接收端的進程取出數據的快慢有關。另一個窗口是發送端的擁塞窗口(Congestion window),由發送端維護這個值,在協議頭部信息中沒有,滑動窗口的大小就是通告窗口和擁塞窗口的較小值,所以擁塞窗口也看做是發送端用來進行流量控制的窗口。滑動窗口的左邊沿向右移動稱為窗口合攏,發生在發送的數據被確認時(此時,表明數據已被接收端收到,不會再被需要重傳,可以從發送端的發送緩存中清除了),滑動窗口的右邊沿向右移動稱為窗口張開,發生在接收進程從接收端協議緩存中取出數據時。隨著發送端不斷收到的被發送數據的ACK包,根據ACK包中的確認序號和通告窗口大小使滑動窗口得以不斷的合攏和張開,形成滑動窗口的向前滑動。如果接收進程一直不取數據,則會出現0窗口現象,即滑動窗口左邊沿與右邊沿重合,此時窗口大小為0,就無法再發送數據。
在TCP里,接收端(B)會給發送端(A)報一個窗口的大小,叫Advertised window。
1.在沒有收到B的確認情況下,A可以連續把窗口內的數據都發送出去。凡是已經發送過的數據,在
未收到確認之前都必須暫時保留,以便在超時重傳時使用。
2.發送窗口裡面的序號表示允許發送的序號。顯然,窗口越大,發送方就可以在收到對方確認之前連續
發送更多數據,因而可能獲得更高的傳輸效率。但接收方必須來得及處理這些收到的數據。
3.發送窗口後沿的後面部分表示已發送且已收到確認。這些數據顯然不需要再保留了。
4.發送窗口前沿的前面部分表示不允許發送的,應為接收方都沒有為這部分數據保留臨時存放的緩存空間。
5.發送窗口後沿的變化情況有兩種:不動(沒有收到新的確認)和前移(收到了新的確認)
6.發送窗口前沿的變化情況有兩種:不斷向前移或可能不動(沒收到新的確認)
TCP的發送方在規定時間內沒有收到確認就要重傳已發送的報文段。這種重傳的概念很簡單,但重傳時間的選擇確是TCP最復雜的問題之一。TCP採用了一種自適應演算法,它記錄一個報文段發出的時間,以及收到響應的確認的時間
這兩個時間之差就是報文段的往返時間RTT。TCP保留了RTT的一個加權平均往返時間。超時重傳時間RTO略大於加權平均往返時間
RTT:
即Round Trip Time,表示從發送端到接收端的一去一回需要的時間,tcp在數據傳輸過程中會對RTT進行采樣(即對發送的數據包及其ACK的時間差進行測量,並根據測量值更新RTT值,具體的演算法TCPIP詳解裡面有),TCP根據得到的RTT值更新RTO值,即Retransmission TimeOut,就是重傳間隔,發送端對每個發出的數據包進行計時,如果在RTO時間內沒有收到所發出的數據包的對應ACK,則任務數據包丟失,將重傳數據。一般RTO值都比采樣得到的RTT值要大。
如果收到的報文段無差錯,只是未按序號,中間還缺少一些序號的數據,那麼能否設法只傳送缺少的數據而不重傳已經正確到達接收方的數據?
答案是可以的,選擇確認就是一種可行的處理方法。
如果要使用選項確認SACK,那麼在建立TCP連接時,就要在TCP首部的選項中加上「允許SACK」的選項,而雙方必須都事先商定好。如果使用選擇確認,
那麼原來首部中的「確認號欄位」的用法仍然不變。SACK文檔並沒有明確發送方應當怎麼響應SACK.因此大多數的實現還是重傳所有未被確認的數據塊。
一般說來,我們總是希望數據傳輸的更快一些,但如果發送方把數據發送的過快,接收方就可能來不及接收,這會造成數據的丟失。所謂流量控制就是讓發送方的發送速率不要太快,要讓接收方來得及接收。
在計算機網路中的鏈路容量,交換節點中的緩存和處理機等,都是網路的資源。在某段時間,若對網路中某一資源的需求超過了該資源所能提供的可用部分,網路的性能就要變壞。這種情況就叫做擁塞。
擁塞控制方法:
1.慢開始和擁塞避免
2.快重傳和快恢復
3.隨機早期檢測
1.一開始,客戶端和服務端都處於CLOSED狀態
2.先是服務端主動監聽某個埠,處於LISTEN狀態(比如服務端啟動,開始監聽)。
3.客戶端主動發起連接SYN,之後處於SYN-SENT狀態(第一次握手,發送 SYN = 1 ACK = 0 seq = x ack = 0)。
4.服務端收到發起的連接,返回SYN,並且ACK客戶端的SYN,之後處於SYN-RCVD狀態(第二次握手,發送 SYN = 1 ACK = 1 seq = y ack = x + 1)。
5.客戶端收到服務端發送的SYN和ACK之後,發送ACK的ACK,之後處於ESTABLISHED狀態(第三次握手,發送 SYN = 0 ACK = 1 seq = x + 1 ack = y + 1)。
6.服務端收到客戶端的ACK之後,處於ESTABLISHED狀態。
(需要注意的是,有可能X和Y是相等的,可能都是0,因為他們代表了各自發送報文段的序號。)
TCP連接釋放四次揮手
1.當前A和B都處於ESTAB-LISHED狀態。
2.A的應用進程先向其TCP發出連接釋放報文段,並停止再發送數據,主動關閉TCP連接。
3.B收到連接釋放報文段後即發出確認,然後B進入CLOSE-WAIT(關閉等待)狀態。TCP伺服器進程這時應通知高層應用進程,因而從A到B這個方向的連接就釋放了,這時TCP連接處於半關閉狀態,即A已經沒有數據發送了。
從B到A這個方向的連接並未關閉,這個狀態可能會持續一些時間。
4.A收到來自B的確認後,就進入FIN-WAIT-2(終止等待2)狀態,等待B發出的連接釋放報文端。
5.若B已經沒有向A發送的數據,B發出連接釋放信號,這時B進入LAST-ACK(最後確認)狀態等待A的確認。
6.A再收到B的連接釋放消息後,必須對此發出確認,然後進入TIME-WAIT(時間等待)狀態。請注意,現在TCP連接還沒有釋放掉,必須經過時間等待計時器(TIME-WAIT timer)設置的時間2MSL後,A才進入CLOSED狀態。
7。B收到A發出的確認消息後,進入CLOSED狀態。
以請求網路為例,看一下三次握手真實數據的TCP連接建立過程
我們再來看四次揮手。TCP斷開連接時,會有四次揮手過程,標志位是FIN,我們在封包列表中找到對應位置,理論上應該找到4個數據包,但我試了好幾次,實際只抓到3個數據包。查了相關資料,說是因為伺服器端在給客戶端傳回的過程中,將兩個連續發送的包進行了合並。因此下面會按照合並後的三次揮手解釋,若有錯誤之處請指出。
第一步,當主機A的應用程序通知TCP數據已經發送完畢時,TCP向主機B發送一個帶有FIN附加標記的報文段(FIN表示英文finish)。
第二步,主機B收到這個FIN報文段之後,並不立即用FIN報文段回復主機A,而是先向主機A發送一個確認序號ACK,同時通知自己相應的應用程序:對方要求關閉連接(先發送ACK的目的是為了防止在這段時間內,對方重傳FIN報文段)。
第三步,主機B的應用程序告訴TCP:我要徹底的關閉連接,TCP向主機A送一個FIN報文段。
第四步,主機A收到這個FIN報文段後,向主機B發送一個ACK表示連接徹底釋放。
這是因為服務端在LISTEN狀態下,收到建立連接請求的SYN報文後,把ACK和SYN放在一個報文里發送給客戶端。而關閉連接時,當收到對方的FIN報文時,僅僅表示對方不再發送數據了但是還能接收數據,己方也未必全部數據都發送給對方了,所以己方可以立即close,也可以發送一些數據給對方後,再發送FIN報文給對方來表示同意現在關閉連接,因此,己方ACK和FIN一般都會分開發送。
原因有二:
一、保證TCP協議的全雙工連接能夠可靠關閉
二、保證這次連接的重復數據段從網路中消失
先說第一點,如果Client直接CLOSED了,那麼由於IP協議的不可靠性或者是其它網路原因,導致Server沒有收到Client最後回復的ACK。那麼Server就會在超時之後繼續發送FIN,此時由於Client已經CLOSED了,就找不到與重發的FIN對應的連接,最後Server就會收到RST而不是ACK,Server就會以為是連接錯誤把問題報告給高層。這樣的情況雖然不會造成數據丟失,但是卻導致TCP協議不符合可靠連接的要求。所以,Client不是直接進入CLOSED,而是要保持TIME_WAIT,當再次收到FIN的時候,能夠保證對方收到ACK,最後正確的關閉連接。
再說第二點,如果Client直接CLOSED,然後又再向Server發起一個新連接,我們不能保證這個新連接與剛關閉的連接的埠號是不同的。也就是說有可能新連接和老連接的埠號是相同的。一般來說不會發生什麼問題,但是還是有特殊情況出現:假設新連接和已經關閉的老連接埠號是一樣的,如果前一次連接的某些數據仍然滯留在網路中,這些延遲數據在建立新連接之後才到達Server,由於新連接和老連接的埠號是一樣的,又因為TCP協議判斷不同連接的依據是socket pair,於是,TCP協議就認為那個延遲的數據是屬於新連接的,這樣就和真正的新連接的數據包發生混淆了。所以TCP連接還要在TIME_WAIT狀態等待2倍MSL,這樣可以保證本次連接的所有數據都從網路中消失。
硬體速度
網路和伺服器的負載
請求和響應報文的尺寸
客戶端和伺服器之間的距離
TCP 協議的技術復雜性
TCP 連接建立握手;
TCP 慢啟動擁塞控制;
數據聚集的 Nagle 演算法;
用於捎帶確認的 TCP 延遲確認演算法;
TIME_WAIT 時延和埠耗盡。
介紹完畢,就這?
是的,就這。
補充:
大部分內容為網路整理,方便自己學習回顧,參考文章:
TCP 協議簡介
TCP協議圖文詳解
什麼是TCP協議?
wireshark抓包分析——TCP/IP協議
TCP協議的三次握手和四次揮手
TCP協議詳解
TCP帶寬和時延的研究(1)
B. 接收方應用程序從tcp緩存讀取數據的時機
接收方應用程序從tcp緩存讀取數據的時機在接收時。TCP會在合適的時機將數據發送給接受方,接收方接收到數據會先把數據放到接收緩存中,應用程序會在合適的時機在緩存中獲取數據。
C. TCP-緩沖區和粘包、拆包有什麼關系
你了解TCP緩沖區嗎?它和TCP傳輸中的粘包和拆包有什麼關系呢?粘包和拆包分別發生在TCP的那個階段呢?
先簡單回顧下TCP概念:在網路傳輸中TCP是面向連接的、可靠的、雙通道、位元組流一對一傳輸。TCP雙方通信必須要先建立連接,然後分配必要的內核資源。雙方交換完畢數據之後必須都要斷開連接用來釋放系統資源,長鏈接可以不必斷開連接復用同一個通道。那麼什麼是TCP的緩沖區呢?
操作系統中有兩個空間:用戶空間和內核空間。每一個socket連接都是在內核空間,內核針對每一個socket都有一個發送緩沖區和接收緩沖區。TCP的雙工工作模式以及流量控制就是依賴這兩個緩沖區的填充來實現的。
我們之前用socket獲取「OutputStream」獲取一個輸出流進行位元組的寫出,其實是寫入到了「send buffer」發送緩沖區中,這個時候數據不一定會發送到對方機器上。「write()」方法僅僅是將用戶空間數據拷貝到了內核發送緩沖區中,具體什麼時候發送由TCP決定。
TCP會從發送緩沖區中把數據通過網卡發送到目標機器的內核緩沖區中。如果系統一直沒有調用"recv()"方法進行讀取的話,那麼數據將會一直擠壓在socket的recv buffer中。
TCP 粘包、拆包問題的由來:
如果你看懂了上面這幅圖的話,那麼對於粘包和拆包的問題就比較好理解了。在這里我想先問一個問題,粘包和拆包是發生在傳輸過程中嗎?
粘包和拆包問題究竟發生在什麼階段?首先我們需要清楚地了解TCP數據是可靠的,因此肯定不是傳輸的過程中!因為數據發送是從緩沖區->網卡,因此粘包問題是從緩沖區讀取數據的時候發生的。拆包則是從緩沖區到網卡的階段發生的。
這里先解釋下粘包:所謂的粘包就是發送方在同一時刻發出了兩個或者兩個以上的包到接收端。
假設發送端需要發送兩條數據「別緊張,你這樣沒事的!」和「好好看文章,你一定可以學會」。首先會把這兩條數據放到發送緩沖區中,然後在經過網卡進行數據的發送到接收方的接收緩沖區中。如果接收方沒有及時從接收緩沖區中獲取往外取數據,那麼數據就會在緩沖區擠壓,這樣兩條數據就會積壓在一塊,就成了一條數據,這就是粘包的問題!
那麼什麼是拆包問題呢?拆包問題是TCP每次發送的長度是有限制的,如果發送一個包的數據過大的話,TCP就會把這個包拆成兩個包來進行發送。
假設要發送的數據「別緊張,你這樣沒事的!」很大,TCP在發送的時候把它拆成了「別緊張,你這樣」和「沒事的!」進行發送,那麼在接收方就會收到兩個報文,這就是拆包的問題。
實際上過大的話,還有可能會被拆成三個或者更多的包進行發送。但是無論被拆成幾個包,TCP都能夠保證發送包的順序性和正確性。
那麼產生粘包和拆包的原因是什麼呢?這個和TCP的緩沖區與滑塊窗口、MSS/MTU限制、Nagle演算法有關。
有了粘包和拆包的問題,我們在實際的開發中應該怎麼避免或者處理這個問題呢?那就是定義我們的通訊協議。這樣如果粘包了就可以根據協議來區分不同的包,如果拆包了就等待數據構成一個完整的消息之後在進行處理。
第一種方式---定長協議:所謂的定長協議就是指定一個報文的固定長度,每次雙方按照約定截取固定的長度。假設我們需要發送「hello」和「very」兩個單詞,按照約定的5個位元組進行一次截取。那麼不足5個位元組的單詞可以添加0作為補充,則發生的規則如下。
由於不足約定長度的需要進行補0,因此定長協議會造成帶寬的浪費。
第二種方式---特殊字元分隔符:使用特殊字元分隔符就是在報文的結尾進行追加特殊字元分隔符,用次分隔符來標注這是一個完整的報文,例如遇到了「\n」。
這樣雖然可以對報文進行劃分,但是要求就是報文中不能包含特殊分隔符。
第三種方式---固定頭長度:發送數據之前,需要先獲取需要發送內容的二進制位元組大小,然後在需要發送的內容前面添加一個固定長度頭整數,表示消息體二進制位元組的長度。
這種方式避免了特殊字元帶來的問題,是生產中可以採取的一個方式,我在之前的文章中有介紹過這樣的使用方法。
其實對於java程序員來說,我們不必過分關心接收和發送緩沖區,需要了解其概念,因為底層已經為我們做了封裝。明白「粘包」和「拆包」發生的過程和原因。
通過觀察用戶空間和內核空間的數據交互,你也許會發現進行一次完整的交互需要進行四次的數據拷貝,這在性能上可能會有所影響。這也就有了面試官經常問的「零拷貝」的問題,嘗試著自己對本文的理解學習一下「零拷貝」,這是為後面學習Netty打下堅實的基礎。
D. TCP 的優化
整理自 CSDN 公眾號
1. 客戶端
TCP 三次握手的開始是客戶端發起 SYN,如果服務端沒有及時回復,那麼會重傳,重傳的間隔和次數是可控的,默認是五次,第一次間隔 1 秒,第二次 2 秒,第三次 4 秒,第四次 8 秒,第五次16 秒,最終超時時間是 63 秒,因此在優化時可以修改重傳次數和間隔,以盡快把錯誤暴露給應用程序。
2. 服務端的半連接隊列優化
服務端在第一次返回 SYN + ACK 時,就會把這次請求維護進一個半連接隊列,這個隊列用來維護尚未完成的握手信息(相對於全連接),如果這個隊列溢出了,服務端就無法繼續接受新的請求了,這也是 SYN Flood 攻擊的點。
通過一個命令 netstat -s 可以得到累計的、由於半連接隊列已滿引發的失敗次數,隔幾秒執行一次就可以知道這個次數是否有上升的趨勢以及分析是否正常。
這種 SYN Flood 攻擊之所以成立,是因為維護這個半連接隊列一定要分配一定的內存資源,那麼應對的方式之一 syncookies 就是如何不分配資源的前提下,可以確認是一次有效的連接並 establish。
syncookies 的工作原理是,伺服器使用一種演算法,計算出一個哈希值,它包含了客戶端發來請求的部分信息,再將這個哈希值和 SYN+ACK 一起返回給客戶端,客戶端也經過一些運算,再返回給服務端,那麼服務端根據這個返回值和之前的計算值比較,如果合法,就可以建立有效連接,從而不會占據半連接隊列的內存。應對 SYN 攻擊時,只需將 syncookies 的參數值調為 1(半連接隊列溢出時啟用),即可。
相當的,可以增大半連接隊列,但是要和 accept 的隊列同時增大才有效,(否則會導致 accept 隊列溢出同樣丟失 TCP 連接)
此時,對於客戶端來說已經是 established 狀態,但是還要再返回給服務端一個 ACK,服務端收到後,服務端才是 established 狀態並開始傳數據,如果網路不穩定,同樣的,服務端會重發 SYN+ACK,當網路不穩定時,應該增加服務端重發 SYN+ACK 的次數。
3. 服務端的 accept 隊列優化
當連接已經建立、應用程序尚未調用時,TCP 連接會被保存在一個 accept 隊列中,如果進程未能及時調用,就會導致 accept 隊列溢出,溢出部分連接將被默認丟棄。對此可以做的是,選擇向客戶端發送 RST 報文,告知關閉這個連接,丟棄握手過程。打開這一功能需要將 tcp_abort_on_overflow 參數設置為 1。如果想讓客戶端了解是由於 accept 隊列溢出造成連接失敗可以這樣做。當 tcp_abort_on_overflow 參數設置為 0 時,則如果 accept 隊列溢出,就會丟棄客戶端傳來的 ACK(用於最後一次握手)。
應對高並發流量時,更好的選擇是 tcp_abort_on_overflow 參數設置為 0,這樣對於客戶端它的狀態仍然是 established,客戶端會定時發送帶有 ack 報文的發送數據請求,一旦服務端的 accept 隊列有空位,那麼連接仍有可能建立成功。所以只有很確定在一段時間內 accept 都是將溢出的狀態,才推薦 tcp_abort_on_overflow 參數設置為 1。
同樣的,可以調整 accept 隊列長度,也可以查看累計的由於溢出導致丟失的連接總數,來判斷趨勢。
在 Linux 3.7 內核版本之後,提供了 TCP Fast Open 功能,這個功能如此生效:
初次建立 TCP 連接時,客戶端在第一個 SYN 包中傳入一個請求 cookie,表明打開 fast open 功能,服務端對應生成一個 cookie 給客戶端,除此之外,三次握手沒有不同,但是,在 cookie 沒有過期之前,下一次再連接的時候,客戶端發送帶有 cookie 的 SYN 包,服務端校驗了 cookie 有效以後,就可以開始傳輸數據了,從而節約了一個往返的時間消耗。
TCP Fast Open 功能需要服務端和客戶端同時打開才能生效。
(備注一個之前看到差點忘了的知識點。
當主動方收到被動方的 FIN 報文後,內核會回復 ACK 報文給被動方,同時主動方的連接狀態由 FIN_WAIT2 變為 TIME_WAIT,在 Linux 系統下大約等待 1 分鍾後,TIME_WAIT 狀態的連接才會徹底關閉。
1. 主動方的優化
關閉的方式有兩種 RST 和 FIN,RST 是暴力關閉連接的方式,安全關閉連接則必須四次揮手。
FIN 報文關閉則可以使用 close 和 shutdown 兩種函數來實現。close 相對來說是「不優雅」的,調用 close 的一方的連接叫做「孤兒連接」,會同時關閉讀和寫,而 shutdown 可以控制是讀還是寫。
關閉讀的時候,會丟棄接收緩沖區里的所有數據,如果後續再接受到數據,也會悄悄丟棄,並發送 ACK,對方不會知道被丟棄了。
關閉寫的時候,會把發送緩沖區的數據全部發送並發送 FIN。
(1)FIN_WAIT1 的優化
主動方發送 FIN 以後,進入 FIN_WAIT1 狀態,如果遲遲沒收到 ACK,會定時重發 FIN,重發次數由 tcp_orphan_retries 參數控制,默認為 8 次,如果處於 FIN_WAIT1 狀態的連接過多,應該考慮降低次數,重發次數超過參數時,連接會被直接關閉。
如果遇到惡意攻擊,可能無法發送出 FIN,因為 TCP 按順序發送所有包, FIN 也不能繞過,另外如果對方的接收窗口已經滿了,發送方也無法再發送數據。
此時應該做的是調整 tcp_max_orphans 參數,它定義了「孤兒連接」的最大數量,當系統中的孤兒連接超過參數值,新增的孤兒連接不會再處於 FIN_WAIT1 狀態,而是會被 RST 報文直接關閉。(只會影響 CLOSE 函數關閉的連接,不會影響 shutdown 關閉的,不會影響還有讀或寫的可能)
(2)FIN_WAIT2 的優化
主動方收到 ACK 後,會處於 FIN_WAIT2,因為被動方還可能有數據發送,如果是 shutdown 關閉,那它也可能還會發送數據,但是對於 close 關閉的連接,無法再發送和接收數據,保持在 FIN_WAIT2 的狀態已經沒有太大意義,tcp_fin_timeout 控制了這個狀態下連接的持續時長,默認值是 60 秒。這個時間和 TIME_WAIT 狀態時長是一致的。
(3)TIME_WAIT 的優化
TIME_WAIT 和 FIN_WAIT2 的時間是一致的,都是 2MSL,1MSL 表示一個報文在網路中存活的最長時間(報文每經過一次路由器的轉發,IP 頭部的 TTL 欄位就會減 1,減到 0 時報文就被丟棄,這就限制了報文的最長存活時間),那麼為什麼是等待 2MSL 呢,其實就是允許報文至少丟失一次、再發送一次,這樣第一個丟失了,等待的時間里第二個 ACK 還會到達,為什麼不是 4MSL 以上呢,這是一個概率的問題,如果一個網路丟包率達到 1%,那麼連續兩次丟包的概率是萬分之一,不必為了這種概率增加等待的時長。
TIME_WAIT 有存在的意義,但是太多保持在這種狀態的連接會佔用雙方資源,占據客戶端的埠資源和服務端的系統資源。
Linux 提供了 tcp_max_tw_buckets 參數,當 TIME_WAIT 的連接數量超過該參數時,新關閉的連接就不再經歷 TIME_WAIT 而直接關閉。這個參數的設定應該取一個平衡點,即既不會太少導致高並發時產生連接間數據錯亂的問題,也不會太多而導致耗盡埠和線程資源。
對於用戶端來講,還可以啟用 tcp_tw_reuse 參數來復用處於 TIME_WAIT 狀態的連接(來節約介面資源。)這個參數有幾個前提,一個是只有客戶端可以打開,一個是 TIME_WAIT 狀態也要保持 1 秒,另一個是要同步打開時間戳功能,報文帶上時間戳就可以避免沒有了 2MSL 時長以後的混亂情況,時間戳過期的報文就會被丟掉。
另外對於 TIME_WAIT,還可以調整 socket 選項,來達到調用 close 關閉連接時跳過四次揮手直接關閉的效果,但不推薦。
2. 被動方的優化
首先,被動方收到 FIN 時,會自動回復 ACK,接著等待應用程序調用 close/shutdown 來結束連接,再發送 FIN。如果系統中同時查看到多個連接處於 CLOSE_WAIT 狀態,則需要排查是否是應用程序出了故障。
然後,當被動方也發送了 FIN 以後,還需要等待主動方回復一個 ACK,如果遲遲沒收到,也會重發 FIN,重發次數也是 tcp_orphan_retries 參數控制,這點和主動方的優化一致,可以調整次數。(需確認被動方是否有 tcp_max_orphans 參數)
3. 如果雙方同時關閉?
1. ACK 延遲
目前在 TCP 中每傳輸一個報文都要求接收方進行確認,大量短而頻繁的確認報文給網路帶來了很多開銷。因此採取了延遲 ACK 策略來減少 ACK 的數量,就是接收方收到一個報文以後,不會立即發送 ACK,而是等待 1~200ms,這期間若有回送數據報文就捎帶確認,但收到兩個連續數據報文或者等待超時則發送一個獨立確認。有效減少了 ACK 的數量,改善了 TCP 的整體性能。
2. 滑動窗口
接收方的接收緩沖區不是不變的,接收到新的會變小,應用程序取出後又會變大,因此接收方會把自己當前的接收窗口大小放在 TCP 頭告知發送方,如果不考慮擁塞控制,發送方的窗口大小「約等於」接收方的窗口大小。
對於這一點,可以把 tcp_window_scaling 配置設為 1(默認打開)來擴大 TCP 通告窗口至 1G 大小。要使用這一選項,需要主動方在 SYN 中先告知,被動方在 SYN 中再反饋。
但是緩沖區並非越大越好,還要考慮網路吞吐的能力。如果緩沖區與網路傳輸能力匹配,那麼緩沖區的利用率就達到了最大化。
3. 調整緩沖區大小
這里需要說一個概念,就是帶寬時延積,它決定網路中飛行報文的大小,它的計算方式:
(1)發送緩沖區的調整
發送緩沖區是自行調節的,當發送方發送的數據被確認後,並且沒有新的數據要發送,就會把發送緩沖區的內存釋放掉。
接收緩沖區要復雜一些:
上面三個數字單位都是位元組,它們分別表示:
(2)接收緩沖區的調整
接收緩沖區可以根據系統空閑內存的大小來調節接收窗口:
(3)內存的判斷
那麼如何判斷內存緊張或充分呢?
上面三個數字單位不是位元組,而是「頁面大小」,1 頁表示 4KB,它們分別表示:
在實際的場景中,TCP 緩沖區最小值保持默認 4K 即可,來提高並發處理能力;最大值則盡可能靠近帶寬時延積,來最大化網路效率。
總結以上:為了提高並發能力、提高網路效率,我們要充分利用網路能力和自己的內存。網路這方面就是將緩沖區大小的極值盡可能靠近帶寬時延積,而同時對緩沖區的自動調節需要結合內存來判斷,這個 TCP 內存的判斷是通過系統內存計算出來的幾個值來劃分的,在不同區間會對分配給緩沖區的內存大小進行調整。
以上就是 TCP 在不同階段的優化策略和思路,有關擁塞控制和流量控制之後再補一篇筆記。
E. 如何修改TCP接收緩存大小
我寫了個TCP,和UDP類
發現TCP默認接收的最大緩沖 一次性好象最多隻能接收 12000左右個位元組
而UDP默認接收的最大緩沖 一次性好象最多隻能接收 28000左右個位元組
據說一般最好。65535個以下 ,否則路由器容易丟包
------解決方案--------------------------------------------------------private int _buf=8000;//標記一次傳輸文件數據塊的大小,不能超過MTU限制,否則在網際網路上的數據發送將不成功00
[Category( "全局設置 ")]
[Description( "設置UDP每一次傳輸數據包的大小 ")]
[DefaultValue(8000)]
public int buf{set{_buf=value;}
get{return _buf;}}然後使用 byte[] 發送數據,發送的時候限制每一次包大小。
至於接收:如果你發送的包已經限制好,接收就不會出問題。
byte[] buffer = new byte[buf];
F. linux為每個tcp分配多少內存還有Windows為每個tcp分配多少內存
下面這些是在網上找到的,可以參考一下:
操作系統:CentOS
查看TCP能使用的內存:
shell>cat /proc/sys/net/ipv4/tcp_mem
1528416 2037888 3056832
這三個值就是TCP可使用內存的大小,單位是頁,每個頁是4K的大小。
這三個值分別代表
Low:1528416 (1528416 *4/1024/1024大概6g)
Pressure:2037888 (2037888 *4/1024/1024大概8g)
High:3056832(3056832*4/1024/1024大概12g)
這個也是系統裝後的默認取值,也就是說最大有12個g(75%的內存)可以用作TCP連接,這三個量也同時代表了三個閥值,TCP的使用小於第二個值時kernel不會有任何提示操作,當大於第二個值時進入壓力模式,當高於第三個值時將不接受新的TCP連接,同時會報出「Out of socket memory」或者「TCP:too many of orphaned sockets」。
TCP讀緩存大小:
shell>cat /proc/sys/net/ipv4/tcp_rmem
4096 87380 4194304
單位是位元組:第一個是最小值4K,第二個是默認值85K,第三個是最大值4M,這個可以在sysctl.conf中net.ipv4.tcp_rmem中進行調整。
TCP寫緩存大小:
shell>cat /proc/sys/net/ipv4/tcp_wmem
4096 16384 4194304
單位是位元組:第一個是最小值4K,第二個是默認值16K,第三個是最大值4M,這個可以在sysctl.conf中net.ipv4.tcp_wmem中進行調整。
一個TCP在三次握手建立連接後,最小的內存消耗在8K(讀4K+寫4K)左右,默認的內存消耗在101K(讀85K+寫16K)左右,最大的內存消耗在(讀4M+寫4M)8M左右,按照系統TCP的全局控制,有12個g可用作內存緩存,假設按照最小的讀寫緩存計算,一個TCP連接佔用8K內存,那麼系統能承受最大的並發為 12*1024*1024/8 = 157萬,假設按照默認的讀寫緩存計算,一個TCP連接佔用101K內存,那麼系統能承受最大的並發為 12*1024*1024/101 = 12萬,假設按照最大的讀寫緩存計算,一個TCP連接佔用8M內存,那麼系統能承受最大的並發為 12*1024/8 = 1536。
G. 嵌入式tcp/ip利用靜態技術緩沖區避免數據處理的什麼
在《國產化某OS中輕量級網路協議棧的研究與應用》文中研究表明近年來計算機硬體能力得到不斷地提升,物聯網、人工智慧等不少領域也取得了長足的進步,其中嵌入式Internet技術研究也取得了一定創新突破。然而傳統的TCP/IP協議棧中協議眾多,佔用內存大,很多嵌入式設備無法滿足其苛刻的資源需求,現有的輕量級網路協議棧雖然佔用內存少,但是有著應用環境特定、缺少實現某些協議和忽視了對數據緩沖區設計等問題,這也意味著不能直接使用現有的輕量級網路協議棧。本文基於這個背景下,為國產某OS設計了輕量級網路協議棧架構並基於傳統TCP/IP協議棧提出了裁剪方案,同時重構了數據緩沖區並將其應用到了 UDP模塊中。本文的主要研究工作如下:(1)提出了一種適用於國產化某OS的TCP/IP協議棧裁剪方案。研究和分析了現有輕量級協議棧架構,針對現有輕量級協議棧存在的主要問題,設計了一種適用於國產化某OS的輕量級網路協議棧架構並在傳統TCP/IP協議棧基礎上提出了裁剪方案。保留將要實現的應用層協議和所有與之相關的下層協議,裁剪部分協議中用不到的功能並改進某些協議的性能。該裁剪方案不僅能很好的滿足國產某OS對嵌入式系統的高可靠性和強實時性要求,同時也能最大程度的減少對嵌入式設備存儲空間的佔用。(2)數據緩沖區的重構與實現。通過重構傳統的數據緩沖區來保存在進程和網路介面之間傳輸的用戶數據。重構的數據緩沖區dBuf借鑒了數據包緩沖區pbuf和內存緩存mbuf的思想,在繼承兩者優點的同時並對它們的缺點並進行了改進,使得重構的數據緩沖區具有結構簡單、內存設計合理、類型少和「零拷貝」等優點。(3)嵌入式UDP設計與實現。傳統嵌入式UDP的設計,在數據報上下行時主要的動作就是封裝首部和解封裝首部,缺乏對UDP報文的有效管理。因此,在設計UDP模塊時,設計了包括UDP控制塊、UDP功能模塊和UDP數據報的收發流程,並將重構後的數據緩沖區應用於嵌入式UDP。該設計有助於協議棧更好的管理UDP報文並便於UDP模塊後期的維護。(4)輕量級協議棧測試。在搭建好實驗環境後對數據緩沖區dBuf和UDP模塊分別進行了測試。dBuf測試後的實驗結果表明,重構的數據緩沖區佔用內存少並能很好的滿足分配和釋放緩沖區的需要,與未使用dBuf相比,使用dBuf可以提高協議棧的吞吐量、CPU利用率和減少對存儲空間的佔用;而UDP模塊測試後的實驗結果表明,UDP模塊不僅能滿足正常收發的基本功能,與普通UDP相比,其在高並發和高吞吐量指標方面表現更優。最後測試了協議棧整體的連通性,將UDP模塊應用到了課題組其他同學實現的TFTP模塊,實驗結果表明,能滿足文件上傳和下載的基本功能,進而驗證了本文設計的輕量級協議棧在整體連通性方面是沒有問題的。
H. TCP緩沖區大小限制
最小重組緩沖區大小:IPv4和IPv6的任何實現都必須保證支持的最小數據報大小,對於IPv4為576位元組,對於IPv6為1500位元組。比如,對於IPv4來說,目的主機小於576位元組的數據報都可以被接受
MSS:最大分節大小,用於想對端TCP通告在每個分節中能發送的最大TCP數據量。MSS的目的是告訴對端其重組緩沖區大小的實際值(576-IP首部-TCP首部=536),從而避免分片。MSS經常設置為MTU減去IP和TCP首部的固定長度,乙太網中的IPv4的MSS值為1460.