TCP 通信協議
TCP 是面向連接的、可靠的、基於字節流的傳輸層通信協議:
-
面向連接:一定是一對一才能連接,不能像 UDP 協議可以一個主機同時向多個主機發送消息,即一對多是無法做到的
-
可靠的:無論的網絡鏈路中出現了怎樣的鏈路變化,TCP 都可以保證一個報文一定能夠到達接收端
-
字節流:消息是沒有邊界的,所以無論消息有多大都可以進行傳輸。並且消息是有序的,當前一個消息沒有收到的時候,即使它先收到了後面的字節已經收到,那麼也不能扔給應用層去處理,同時對重複的報文會自動丟棄
TCP 頭部格式
-
序列號:在建立連接時由計算機生成的隨機數作爲其初始值,通過 SYN 包傳給接收端主機,每發送一次數據,就「累加」一次該「數據字節數」的大小。用來解決網絡包亂序問題
-
確認應答號:指下一次「期望」收到的數據的序列號,發送端收到這個確認應答以後可以認爲在這個序號以前的數據都已經被正常接收。用來解決不丟包的問題
-
控制位:ACK:該位爲 1 時,「確認應答」的字段變爲有效,TCP 規定除了最初建立連接時的 SYN 包之外該位必須設置爲 1RST:該位爲 1 時,表示 TCP 連接中出現異常必須強制斷開連接 SYN:該位爲 1 時,表示希望建立連接,並在其「序列號」的字段進行序列號初始值的設定 FIN:該位爲 1 時,表示今後不會再有數據發送,希望斷開連接。當通信結束希望斷開連接時,通信雙方的主機之間就可以相互交換 FIN 位置爲 1 的 TCP 段
網絡模型
談一談你對 TCP/IP 四層模型,OSI 七層模型的理解?
OSI 參考模型
OSI(Open System Interconnect),即開放式系統互聯。一般都叫 OSI 參考模型,是 ISO(國際標準化組織)組織在 1985 年研究的網絡互連模型。ISO 爲了更好的使網絡應用更爲普及,推出了 OSI 參考模型。其含義就是推薦所有公司使用這個規範來控制網絡。這樣所有公司都有相同的規範,就能互聯了。
微信搜索公衆號:架構師指南,回覆:架構師 領取資料 。
TCP/IP 五層模型
TCP/IP 五層協議和 OSI 的七層協議對應關係如下:
TCP 狀態
-
CLOSED:表示初始狀態
-
LISTEN:表示服務器端的某個 SOCKET 處於監聽狀態,可以接受連接了
-
SYN_RCVD:表示接收到了 SYN 報文
-
SYN_SENT:表示客戶端已發送 SYN 報文
-
ESTABLISHED:表示連接已經建立了
-
TIME_WAIT:表示收到了對方的 FIN 報文,併發送出了 ACK 報文,就等 2MSL 後即可回到 CLOSED 可用狀態了
-
CLOSING:表示你發送 FIN 報文後,並沒有收到對方的 ACK 報文,反而卻也收到了對方的 FIN 報文。如果雙方几乎在同時 close 一個 SOCKET 的話,那麼就出現了雙方同時發送 FIN 報 文的情況,也即會出現 CLOSING 狀態,表示雙方都正在關閉 SOCKET 連接
-
CLOSE_WAIT:表示在等待關閉
如何在 Linux 系統中查看 TCP 狀態?
TCP 的連接狀態查看,在 Linux 可以通過 netstat -napt 命令查看:
TIME_WAIT
① 爲什麼需要 TIME_WAIT 狀態?
主動發起關閉連接的一方,纔會有 TIME-WAIT 狀態。需要 TIME-WAIT 狀態,主要是兩個原因:
-
防止具有相同「四元組」的「舊」數據包被收到
-
保證「被動關閉連接」的一方能被正確的關閉,即保證最後的 ACK 能讓被動關閉方接收,從而幫助其正常關閉
② TIME_WAIT 過多有什麼危害?
如果服務器有處於 TIME-WAIT 狀態的 TCP,則說明是由服務器方主動發起的斷開請求。過多的 TIME-WAIT 狀態主要的危害有兩種:
-
第一是內存資源佔用
-
第二是對端口資源的佔用,一個 TCP 連接至少消耗一個本地端口
第二個危害是會造成嚴重的後果的,要知道,端口資源也是有限的,一般可以開啓的端口爲 32768~61000,也可以通過如下參數設置指定
net.ipv4.ip_local_port_range
如果發起連接一方的 TIME_WAIT 狀態過多,佔滿了所有端口資源,則會導致無法創建新連接。
客戶端受端口資源限制:
- 客戶端 TIME_WAIT 過多,就會導致端口資源被佔用,因爲端口就 65536 個,被佔滿就會導致無法創建新的連接
服務端受系統資源限制:
- 由於一個四元組表示 TCP 連接,理論上服務端可以建立很多連接,服務端確實只監聽一個端口 但是會把連接扔給處理線程,所以理論上監聽的端口可以繼續監聽。但是線程池處理不了那麼多一直不斷的連接了。所以當服務端出現大量 TIME_WAIT 時,系統資源被佔滿時,會導致處理不過來新的連接
③ 如何優化 TIME_WAIT?
這裏給出優化 TIME-WAIT 的幾個方式,都是有利有弊:
-
打開 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 選項
-
net.ipv4.tcp_max_tw_buckets
-
程序中使用 SO_LINGER,應用強制使用 RST 關閉
④ 爲什麼 TIME_WAIT 等待的時間是 2MSL?
MSL 是 Maximum Segment Lifetime,報文最大生存時間,它是任何報文在網絡上存在的最長時間,超過這個時間報文將被丟棄。因爲 TCP 報文基於是 IP 協議的,而 IP 頭中有一個 TTL 字段,是 IP 數據報可以經過的最大路由數,每經過一個處理他的路由器此值就減 1,當此值爲 0 則數據報將被丟棄,同時發送 ICMP 報文通知源主機。
MSL 與 TTL 的區別:MSL 的單位是時間,而 TTL 是經過路由跳數。所以 MSL 應該要大於等於 TTL 消耗爲 0 的時間,以確保報文已被自然消亡。
TIME_WAIT 等待 2 倍的 MSL,比較合理的解釋是:網絡中可能存在來自發送方的數據包,當這些發送方的數據包被接收方處理後又會向對方發送響應,所以一來一回需要等待 2 倍的時間。
比如如果被動關閉方沒有收到斷開連接的最後的 ACK 報文,就會觸發超時重發 Fin 報文,另一方接收到 FIN 後,會重發 ACK 給被動關閉方, 一來一去正好 2 個 MSL。
2MSL 的時間是從客戶端接收到 FIN 後發送 ACK 開始計時的。如果在 TIME-WAIT 時間內,因爲客戶端的 ACK 沒有傳輸到服務端,客戶端又接收到了服務端重發的 FIN 報文,那麼 2MSL 時間將重新計時。
在 Linux 系統裏 2MSL 默認是 60 秒,那麼一個 MSL 也就是 30 秒。Linux 系統停留在 TIME_WAIT 的時間爲固定的 60 秒。
其定義在 Linux 內核代碼裏的名稱爲 TCP_TIMEWAIT_LEN:
#define TCP_TIMEWAIT_LEN (60HZ) / how long to wait to destroy TIME-WAIT state, about 60 seconds */
如果要修改 TIME_WAIT 的時間長度,只能修改 Linux 內核代碼裏 TCP_TIMEWAIT_LEN 的值,並重新編譯 Linux 內核。
連接過程
TCP 三次握手
開始客戶端和服務器都處於 CLOSED 狀態,然後服務端開始監聽某個端口,進入 LISTEN 狀態:
-
第一次握手 (SYN=1, seq=x),發送完畢後,客戶端進入 SYN_SENT 狀態
-
第二次握手 (SYN=1, ACK=1, seq=y, ACKnum=x+1), 發送完畢後,服務器端進入 SYN_RCVD 狀態
-
第三次握手 (ACK=1,ACKnum=y+1),發送完畢後,客戶端進入 ESTABLISHED 狀態,當服務器端接收到這個包時, 也進入 ESTABLISHED 狀態,TCP 握手,即可以開始數據傳輸
-
假設一開始客戶端和服務端都處於 CLOSED 的狀態。然後先是服務端主動監聽某個端口,處於 LISTEN 狀態
-
【第一個報文】:客戶端會隨機初始化序號(client_isn),將此序號置於 TCP 首部的「序號」字段中,同時把 SYN 標誌位置爲 1 ,表示 SYN 報文。接着把第一個 SYN 報文發送給服務端,表示向服務端發起連接,該報文不包含應用層數據,之後客戶端處於 SYN-SENT 狀態
- 【第二個報文】:服務端收到客戶端的 SYN 報文後,首先服務端也隨機初始化自己的序號(server_isn),將此序號填入 TCP 首部的「序號」字段中,其次把 TCP 首部的「確認應答號」字段填入 client_isn + 1, 接着把 SYN 和 ACK 標誌位置爲 1。最後把該報文發給客戶端,該報文也不包含應用層數據,之後服務端處於 SYN-RCVD 狀態
- 【第三個報文】:客戶端收到服務端報文後,還要向服務端迴應最後一個應答報文,首先該應答報文 TCP 首部 ACK 標誌位置爲 1 ,其次「確認應答號」字段填入 server_isn + 1 ,最後把報文發送給服務端,這次報文可以攜帶客戶到服務器的數據,之後客戶端處於 ESTABLISHED 狀態
- 服務器收到客戶端的應答報文後,也進入 ESTABLISHED 狀態
第三次握手是否可以攜帶數據?
第三次握手是可以攜帶數據的,前兩次握手是不可以攜帶數據的。一旦完成三次握手,雙方都處於 ESTABLISHED 狀態,此時連接就已建立完成,客戶端和服務端就可以相互發送數據了。假設第三次握手的報文的 seq 是 x+1:
-
如果有攜帶數據:下次客戶端發送的報文,seq = 服務器發回的 ACK 號
-
如果沒有攜帶數據:第三次握手的報文不消耗 seq,下次客戶端發送的報文,seq 序列號爲 x+1
① 服務端 SYN-RECV 流程
② 客戶端 SYN-SEND 流程
-
場景 1:sk->sk_write_pending != 0
-
這個值默認是 0 的,那什麼情況會導致不爲 0 呢?答案是協議棧發送數據的函數遇到 socket 狀態不是 ESTABLISHED 的時候,會對這個變量做 ++ 操作,並等待一小會時間嘗試發送數據。
-
場景 2:icsk->icsk_accept_queue.rskq_defer_accept != 0
-
客戶端先 bind 到一個端口和 IP,然後 setsockopt(TCP_DEFER_ACCEPT),然後 connect 服務器,這個時候就會出現 rskq_defer_accept=1 的情況,這時候內核會設置定時器等待數據一起在回覆 ACK 包。
-
場景 3:icsk->icsk_ack.pingpong != 0
-
pingpong 這個屬性實際上也是一個套接字選項,用來表明當前鏈接是否爲交互數據流,如其值爲 1,則表明爲交互數據流,會使用延遲確認機制。
爲什麼是三次握手?不是兩次、四次?
TCP 建立連接時,通過三次握手能防止歷史連接的建立,能減少雙方不必要的資源開銷,能幫助雙方同步初始化序列號。序列號能夠保證數據包不重複、不丟棄和按序傳輸。不使用「兩次握手」和「四次握手」的原因:
-
兩次握手:無法防止歷史連接的建立,會造成雙方資源的浪費,也無法可靠的同步雙方序列號
-
四次握手:三次握手就已經理論上最少可靠連接建立,所以不需要使用更多的通信次數
接下來以三個方面分析三次握手的原因:
-
三次握手纔可以阻止重複歷史連接的初始化(主要原因)
-
三次握手纔可以同步雙方的初始序列號
-
三次握手纔可以避免資源浪費
原因一:避免歷史連接
客戶端連續發送多次 SYN 建立連接的報文,在網絡擁堵情況下:
-
一個「舊 SYN 報文」比「最新的 SYN 」 報文早到達了服務端
-
那麼此時服務端就會回一個 SYN + ACK 報文給客戶端
-
客戶端收到後可以根據自身的上下文,判斷這是一個歷史連接(序列號過期或超時),那麼客戶端就會發送 RST 報文給服務端,表示中止這一次連接
如果是兩次握手連接,就不能判斷當前連接是否是歷史連接,三次握手則可以在客戶端(發送方)準備發送第三次報文時,客戶端因有足夠的上下文來判斷當前連接是否是歷史連接:
-
如果是歷史連接(序列號過期或超時),則第三次握手發送的報文是 RST 報文,以此中止歷史連接
-
如果不是歷史連接,則第三次發送的報文是 ACK 報文,通信雙方就會成功建立連接
所以,TCP 使用三次握手建立連接的最主要原因是防止歷史連接初始化了連接。
原因二:同步雙方初始序列號
TCP 協議的通信雙方, 都必須維護一個「序列號」, 序列號是可靠傳輸的一個關鍵因素,它的作用:
-
接收方可以去除重複的數據
-
接收方可以根據數據包的序列號按序接收
-
可以標識發送出去的數據包中, 哪些是已經被對方收到的
可見,序列號在 TCP 連接中佔據着非常重要的作用,所以當客戶端發送攜帶「初始序列號」的 SYN 報文的時候,需要服務端回一個 ACK 應答報文,表示客戶端的 SYN 報文已被服務端成功接收,那當服務端發送「初始序列號」給客戶端的時候,依然也要得到客戶端的應答迴應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。
四次握手其實也能夠可靠的同步雙方的初始化序號,但由於第二步和第三步可以優化成一步,所以就成了「三次握手」。而兩次握手只保證了一方的初始序列號能被對方成功接收,沒辦法保證雙方的初始序列號都能被確認接收。
原因三:避免資源浪費
如果只有「兩次握手」,當客戶端的 SYN 請求連接在網絡中阻塞,客戶端沒有接收到 ACK 報文,就會重新發送 SYN ,由於沒有第三次握手,服務器不清楚客戶端是否收到了自己發送的建立連接的 ACK 確認信號,所以每收到一個 SYN 就只能先主動建立一個連接,這會造成什麼情況呢?如果客戶端的 SYN 阻塞了,重複發送多次 SYN 報文,那麼服務器在收到請求後就會建立多個冗餘的無效鏈接,造成不必要的資源浪費。
即兩次握手會造成消息滯留情況下,服務器重複接受無用的連接請求 SYN 報文,而造成重複分配資源。
TCP 四次揮手
-
第一次揮手:FIN=1,seq=u,發送完畢後客戶端進入 FIN_WAIT_1 狀態
-
第二次揮手:ACK=1,seq =v,ack=u+1,發送完畢後服務器端進入 CLOSE_WAIT 狀態,客戶端接收到後進入 FIN_WAIT_2 狀態
-
第三次揮手:FIN=1,ACK=1,seq=w,ack=u+1,發送完畢後服務器端進入 LAST_ACK 狀態,客戶端接收到後進入 TIME_WAIT 狀態
-
第四次揮手:ACK=1,seq=u+1,ack=w+1,客戶端接收到來自服務器端的關閉請求,發送一個確認包,並進入 TIME_WAIT 狀態,等待了某個固定時間(兩個最大段生命週期,2MSL,2 Maximum Segment Lifetime)之後,沒有收到服務器端的 ACK ,認爲服務器端已經正常關閉連接,於是自己也關閉連接,進入 CLOSED 狀態。服務器端接收到這個確認包之後,關閉連接,進入 CLOSED 狀態
四次揮手過程:
-
客戶端打算關閉連接,此時會發送一個 TCP 首部 FIN 標誌位被置爲 1 的報文,也即 FIN 報文,之後客戶端進入 FIN_WAIT_1 狀態
-
服務端收到該報文後,就向客戶端發送 ACK 應答報文,接着服務端進入 CLOSED_WAIT 狀態
-
客戶端收到服務端的 ACK 應答報文後,之後進入 FIN_WAIT_2 狀態
-
等待服務端處理完數據後,也向客戶端發送 FIN 報文,之後服務端進入 LAST_ACK 狀態
-
客戶端收到服務端的 FIN 報文後,回一個 ACK 應答報文,之後進入 TIME_WAIT 狀態
-
服務器收到了 ACK 應答報文後,就進入了 CLOSE 狀態,至此服務端已經完成連接的關閉
-
客戶端在經過 2MSL 一段時間後,自動進入 CLOSE 狀態,至此客戶端也完成連接的關閉
爲什麼揮手需要四次?
再來回顧下四次揮手雙方發 FIN 包的過程,就能理解爲什麼需要四次了。
-
關閉連接時,客戶端向服務端發送 FIN 時,僅僅表示客戶端不再發送數據了但是還能接收數據。
-
服務器收到客戶端的 FIN 報文時,先回一個 ACK 應答報文,而服務端可能還有數據需要處理和發送,等服務端不再發送數據時,才發送 FIN 報文給客戶端來表示同意現在關閉連接。
從上面過程可知,服務端通常需要等待完成數據的發送和處理,所以服務端的 ACK 和 FIN 一般都會分開發送,從而比三次握手導致多了一次。
TCP 優化
正確有效的使用 TCP 參數可以提高 TCP 性能。以下將從三個角度來闡述提升 TCP 的策略,分別是:
TCP 三次握手優化
TCP 四次揮手優化
TCP 數據傳輸優化
常見問題
TCP 和 UDP
TCP 和 UDP 的區別?
-
連接 TCP 是面向連接的傳輸層協議,傳輸數據前先要建立連接 UDP 是不需要連接,即刻傳輸數據
-
服務對象 TCP 是一對一的兩點服務,即一條連接只有兩個端點 UDP 支持一對一、一對多、多對多的交互通信
-
可靠性 TCP 是可靠交付數據的,數據可以無差錯、不丟失、不重複、按需到達 UDP 是盡最大努力交付,不保證可靠交付數據
-
擁塞控制、流量控制 TCP 有擁塞控制和流量控制機制,保證數據傳輸的安全性 UDP 則沒有,即使網絡非常擁堵了,也不會影響 UDP 的發送速率
-
首部開銷 TCP 首部長度較長,會有一定的開銷,首部在沒有使用「選項」字段時是 20 個字節,如果使用了「選項」字段則會變長的 UDP 首部只有 8 個字節,並且是固定不變的,開銷較小
-
傳輸方式 TCP 是流式傳輸,沒有邊界,但保證順序和可靠 UDP 是一個包一個包的發送,是有邊界的,但可能會丟包和亂序
-
分片不同 TCP 的數據大小如果大於 MSS 大小,則會在傳輸層進行分片,目標主機收到後,也同樣在傳輸層組裝 TCP 數據包,如果中途丟失了一個分片,只需要傳輸丟失的這個分片 UDP 的數據大小如果大於 MTU 大小,則會在 IP 層進行分片,目標主機收到後,在 IP 層組裝完數據,接着再傳給傳輸層,但是如果中途丟了一個分片,則就需要重傳所有的數據包,這樣傳輸效率非常差,所以通常 UDP 的報文應該小於 MTU
ISN
① 爲什麼客戶端和服務端的初始序列號 ISN 是不相同的?
如果一個已經失效的連接被重用了,但是該舊連接的歷史報文還殘留在網絡中,如果序列號相同,那麼就無法分辨出該報文是不是歷史報文,如果歷史報文被新的連接接收了,則會產生數據錯亂。所以,每次建立連接前重新初始化一個序列號主要是爲了通信雙方能夠根據序號將不屬於本連接的報文段丟棄。另一方面是爲了安全性,防止黑客僞造的相同序列號的 TCP 報文被對方接收。
② 初始序列號 ISN 是如何隨機產生的?
起始 ISN 是基於時鐘的,每 4 毫秒 + 1,轉一圈要 4.55 個小時。RFC1948 中提出了一個較好的初始化序列號 ISN 隨機生成算法。
ISN = M + F (localhost, localport, remotehost, remoteport)
-
M 是一個計時器,這個計時器每隔 4 毫秒加 1
-
F 是一個 Hash 算法,根據源 IP、目的 IP、源端口、目的端口生成一個隨機數值。要保證 Hash 算法不能被外部輕易推算得出,用 MD5 算法是一個比較好的選擇
UDP
總結
-
TCP 向上層提供面向連接的可靠服務 ,UDP 向上層提供無連接不可靠服務
-
UDP 沒有 TCP 傳輸可靠,但是可以在實時性要求搞的地方有所作爲
-
對數據準確性要求高,速度可以相對較慢的,可以選用 TCP
TCP 數據可靠性
一句話:通過校驗和、序列號、確認應答、超時重傳、連接管理、流量控制、擁塞控制等機制來保證可靠性。
(1)校驗和
在數據傳輸過程中,將發送的數據段都當做一個 16 位的整數,將這些整數加起來,並且前面的進位不能丟棄,補在最後,然後取反,得到校驗和。
發送方:在發送數據之前計算校驗和,並進行校驗和的填充。接收方:收到數據後,對數據以同樣的方式進行計算,求出校驗和,與發送方進行比較。
(2)序列號
TCP 傳輸時將每個字節的數據都進行了編號,這就是序列號。序列號的作用不僅僅是應答作用,有了序列號能夠將接收到的數據根據序列號進行排序,並且去掉重複的數據。
(3)確認應答
TCP 傳輸過程中,每次接收方接收到數據後,都會對傳輸方進行確認應答,也就是發送 ACK 報文,這個 ACK 報文中帶有對應的確認序列號,告訴發送方,接收了哪些數據,下一次數據從哪裏傳。
(4)超時重傳
在進行 TCP 傳輸時,由於存在確認應答與序列號機制,也就是說發送方發送一部分數據後,都會等待接收方發送的 ACK 報文,並解析 ACK 報文,判斷數據是否傳輸成功。如果發送方發送完數據後,遲遲都沒有接收到接收方傳來的 ACK 報文,那麼就對剛剛發送的數據進行重發。
(5)連接管理
就是指三次握手、四次揮手的過程。
(6)流量控制
如果發送方的發送速度太快,會導致接收方的接收緩衝區填充滿了,這時候繼續傳輸數據,就會造成大量丟包,進而引起丟包重傳等等一系列問題。TCP 支持根據接收端的處理能力來決定發送端的發送速度,這就是流量控制機制。
具體實現方式:接收端將自己的接收緩衝區大小放入 TCP 首部的『窗口大小』字段中,通過 ACK 通知發送端。
(7)擁塞控制
TCP 傳輸過程中一開始就發送大量數據,如果當時網絡非常擁堵,可能會造成擁堵加劇。所以 TCP 引入了慢啓動機制,在開始發送數據的時候,先發少量的數據探探路。
TCP 協議如何提高傳輸效率
一句話:TCP 協議提高效率的方式有滑動窗口、快重傳、延遲應答、捎帶應答等。
(1)滑動窗口
如果每一個發送的數據段,都要收到 ACK 應答之後再發送下一個數據段,這樣的話我們效率很低,大部分時間都用在了等待 ACK 應答上了。
爲了提高效率我們可以一次發送多條數據,這樣就能使等待時間大大減少,從而提高性能。窗口大小指的是無需等待確認應答而可以繼續發送數據的最大值。
(2)快重傳
快重傳也叫高速重發控制。
那麼如果出現了丟包,需要進行重傳。一般分爲兩種情況:
情況一:數據包已經抵達,ACK 被丟了。這種情況下,部分 ACK 丟了並不影響,因爲可以通過後續的 ACK 進行確認;
情況二:數據包直接丟了。發送端會連續收到多個相同的 ACK 確認,發送端立即將對應丟失的數據重傳。
(3)延遲應答
如果接收數據的主機立刻返回 ACK 應答,這時候返回的窗口大小可能比較小。
-
假設接收端緩衝區爲 1M,一次收到了 512K 的數據;如果立刻應答,返回的窗口就是 512K;
-
但實際上可能處理端處理速度很快,10ms 之內就把 512K 的數據從緩存區消費掉了;
-
在這種情況下,接收端處理還遠沒有達到自己的極限,即使窗口再放大一些,也能處理過來;
-
如果接收端稍微等一會在應答,比如等待 200ms 再應答,那麼這個時候返回的窗口大小就是 1M;
窗口越大,網絡吞吐量就越大,傳輸效率就越高;我們的目標是在保證網絡不擁塞的情況下儘量提高傳輸效率。
(4)捎帶應答
在延遲應答的基礎上,很多情況下,客戶端服務器在應用層也是一發一收的。這時候常常採用捎帶應答的方式來提高效率,而 ACK 響應常常伴隨着數據報文共同傳輸。如:三次握手。
TCP 如何處理擁塞
網絡擁塞現象是指到達通信網絡中某一部分的分組數量過多,使得該部分網絡來不及處理,以致引起這部分乃至整個網絡性能下降的現象,嚴重時甚至會導致網絡通信業務陷入停頓,即出現死鎖現象。擁塞控制是處理網絡擁塞現象的一種機制。
擁塞控制的四個階段:
-
慢啓動
-
擁塞避免
-
快速重傳
-
快速恢復
Socket
基於 TCP 協議的客戶端和服務器工作:
-
服務端和客戶端初始化 socket,得到文件描述符
-
服務端調用 bind,將綁定在 IP 地址和端口
-
服務端調用 listen,進行監聽
-
服務端調用 accept,等待客戶端連接
-
客戶端調用 connect,向服務器端的地址和端口發起連接請求
-
服務端 accept 返回用於傳輸的 socket 的文件描述符
-
客戶端調用 write 寫入數據;服務端調用 read 讀取數據
-
客戶端斷開連接時,會調用 close,那麼服務端 read 讀取數據的時候,就會讀取到了 EOF,待處理完數據後,服務端調用 close,表示連接關閉
listen 時候參數 backlog 的意義?
Linux 內核中會維護兩個隊列:
未完成連接隊列(SYN 隊列):接收到一個 SYN 建立連接請求,處於 SYN_RCVD 狀態;
已完成連接隊列(Accpet 隊列):已完成 TCP 三次握手過程,處於 ESTABLISHED 狀態;
SYN 隊列 與 Accpet 隊列
int listen (int socketfd, int backlog)
參數一 socketfd 爲 socketfd 文件描述符
參數二 backlog,這參數在歷史內環版本有一定的變化
在早期 Linux 內核 backlog 是 SYN 隊列大小,也就是未完成的隊列大小。在 Linux 內核 2.2 之後,backlog 變成 accept 隊列,也就是已完成連接建立的隊列長度,所以現在通常認爲 backlog 是 accept 隊列。但是上限值是內核參數 somaxconn 的大小,也就說 accpet 隊列長度 = min(backlog, somaxconn)。
accept 發送在三次握手的哪一步?
我們先看看客戶端連接服務端時,發送了什麼?
客戶端的協議棧向服務器端發送了 SYN 包,並告訴服務器端當前發送序列號 client_isn,客戶端進入 SYNC_SENT 狀態
服務器端的協議棧收到這個包之後,和客戶端進行 ACK 應答,應答的值爲 client_isn+1,表示對 SYN 包 client_isn 的確認,同時服務器也發送一個 SYN 包,告訴客戶端當前我的發送序列號爲 server_isn,服務器端進入 SYNC_RCVD 狀態
客戶端協議棧收到 ACK 之後,使得應用程序從 connect 調用返回,表示客戶端到服務器端的單向連接建立成功,客戶端的狀態爲 ESTABLISHED,同時客戶端協議棧也會對服務器端的 SYN 包進行應答,應答數據爲 server_isn+1
應答包到達服務器端後,服務器端協議棧使得 accept 阻塞調用返回,這個時候服務器端到客戶端的單向連接也建立成功,服務器端也進入 ESTABLISHED 狀態
從上面的描述過程,我們可以得知客戶端 connect 成功返回是在第二次握手,服務端 accept 成功返回是在三次握手成功之後。
客戶端調用 close 了,連接是斷開的流程是什麼?
我們看看客戶端主動調用了 close,會發生什麼?
客戶端調用 close,表明客戶端沒有數據需要發送了,則此時會向服務端發送 FIN 報文,進入 FIN_WAIT_1 狀態
服務端接收到了 FIN 報文,TCP 協議棧會爲 FIN 包插入一個文件結束符 EOF 到接收緩衝區中,應用程序可以通過 read 調用來感知這個 FIN 包。這個 EOF 會被放在已排隊等候的其他已接收的數據之後,這就意味着服務端需要處理這種異常情況,因爲 EOF 表示在該連接上再無額外數據到達。此時服務端進入 CLOSE_WAIT 狀態
接着,當處理完數據後,自然就會讀到 EOF,於是也調用 close 關閉它的套接字,這會使得會發出一個 FIN 包,之後處於 LAST_ACK 狀態
客戶端接收到服務端的 FIN 包,併發送 ACK 確認包給服務端,此時客戶端將進入 TIME_WAIT 狀態
服務端收到 ACK 確認包後,就進入了最後的 CLOSE 狀態
客戶端進過 2MSL 時間之後,也進入 CLOSE 狀態
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/ODKvISKeqZePocwhqUv6fQ