無論面試 Java 、C/C++、Python 等開發崗位, TCP
的知識點能夠說是的必問的了。html
任 TCP 虐我千百遍,我仍待 TCP 如初戀。面試
遙想小林當年校招時常因 TCP
面試題被刷,真是又愛又狠....算法
過去不會不要緊,今天就讓咱們來消除這份恐懼,微笑着勇敢的面對它吧!編程
因此小林整理了關於 TCP 三次握手和四次揮手的面試題型,跟你們一塊兒探討探討。安全
PS:本次文章不涉及 TCP 流量控制、擁塞控制、可靠性傳輸等方面知識,這些留在下篇哈!
瞧瞧 TCP 頭格式
咱們先來看看 TCP 頭的格式,標註顏色的表示與本文關聯比較大的字段,其餘字段不作詳細闡述。服務器
序列號:在創建鏈接時由計算機生成的隨機數做爲其初始值,經過 SYN 包傳給接收端主機,每發送一次數據,就「累加」一次該「數據字節數」的大小。用來解決網絡包亂序問題。cookie
確認應答號:指下一次「指望」收到的數據的序列號,發送端收到這個確認應答之後能夠認爲在這個序號之前的數據都已經被正常接收。用來解決不丟包的問題。網絡
控制位:併發
1
時,「確認應答」的字段變爲有效,TCP 規定除了最初創建鏈接時的 SYN
包以外該位必須設置爲 1
。1
時,表示 TCP 鏈接中出現異常必須強制斷開鏈接。1
時,表示但願創建連,並在其「序列號」的字段進行序列號初始值的設定。1
時,表示從此不會再有數據發送,但願斷開鏈接。當通訊結束但願斷開鏈接時,通訊雙方的主機之間就能夠相互交換 FIN
位置爲 1 的 TCP 段。爲何須要 TCP 協議? TCP 工做在哪一層?
IP
層是「不可靠」的,它不保證網絡包的交付、不保證網絡包的按序交付、也不保證網絡包中的數據的完整性。socket
若是須要保障網絡數據包的可靠性,那麼就須要由上層(傳輸層)的 TCP
協議來負責。
由於 TCP 是一個工做在傳輸層的可靠數據傳輸的服務,它能確保接收端接收的網絡包是無損壞、無間隔、非冗餘和按序的。
什麼是 TCP ?
TCP 是面向鏈接的、可靠的、基於字節流的傳輸層通訊協議。
什麼是 TCP 鏈接?
咱們來看看 RFC 793 是如何定義「鏈接」的:
*Connections:
The reliability and flow control mechanisms described above require
that TCPs initialize and maintain certain status information for
each data stream. The combination of this information, including
sockets, sequence numbers, and window sizes, is called a connection.*
簡單來講就是,用於保證可靠性和流量控制維護的某些狀態信息,這些信息的組合,包括Socket、序列號和窗口大小稱爲鏈接。
因此咱們能夠知道,創建一個 TCP 鏈接是須要客戶端與服務器端達成上述三個信息的共識。
如何惟一肯定一個 TCP 鏈接呢?
TCP 四元組能夠惟一的肯定一個鏈接,四元組包括以下:
源地址和目的地址的字段(32位)是在 IP 頭部中,做用是經過 IP 協議發送報文給對方主機。
源端口和目的端口的字段(16位)是在 TCP 頭部中,做用是告訴 TCP 協議應該把報文發給哪一個進程。
有一個 IP 的服務器監聽了一個端口,它的 TCP 的最大鏈接數是多少?
服務器一般固定在某個本地端口上監聽,等待客戶端的鏈接請求。
所以,客戶端 IP 和 端口是可變的,其理論值計算公式以下:
對 IPv4,客戶端的 IP 數最多爲 2
的 32
次方,客戶端的端口數最多爲 2
的 16
次方,也就是服務端單機最大 TCP 鏈接數,約爲 2
的 48
次方。
固然,服務端最大併發 TCP 鏈接數遠不能達到理論上限。
ulimit
配置文件描述符的數目;UDP 和 TCP 有什麼區別呢?分別的應用場景是?
UDP 不提供複雜的控制機制,利用 IP 提供面向「無鏈接」的通訊服務。
UDP 協議真的很是簡,頭部只有 8
個字節( 64 位),UDP 的頭部格式以下:
TCP 和 UDP 區別:
1. 鏈接
2. 服務對象
3. 可靠性
4. 擁塞控制、流量控制
5. 首部開銷
20
個字節,若是使用了「選項」字段則會變長的。TCP 和 UDP 應用場景:
因爲 TCP 是面向鏈接,能保證數據的可靠性交付,所以常常用於:
FTP
文件傳輸HTTP
/ HTTPS
因爲 UDP 面向無鏈接,它能夠隨時發送數據,再加上UDP自己的處理既簡單又高效,所以常常用於:
DNS
、SNMP
等爲何 UDP 頭部沒有「首部長度」字段,而 TCP 頭部有「首部長度」字段呢?
緣由是 TCP 有可變長的「選項」字段,而 UDP 頭部長度則是不會變化的,無需多一個字段去記錄 UDP 的首部長度。
爲何 UDP 頭部有「包長度」字段,而 TCP 頭部則沒有「包長度」字段呢?
先說說 TCP 是如何計算負載數據長度:
其中 IP 總長度 和 IP 首部長度,在 IP 首部格式是已知的。TCP 首部長度,則是在 TCP 首部格式已知的,因此就能夠求得 TCP 數據的長度。
你們這時就奇怪了問:「 UDP 也是基於 IP 層的呀,那 UDP 的數據長度也能夠經過這個公式計算呀? 爲什麼還要有「包長度」呢?」
這麼一問,確實感受 UDP 「包長度」是冗餘的。
由於爲了網絡設備硬件設計和處理方便,首部長度須要是 4
字節的整數倍。
若是去掉 UDP 「包長度」字段,那 UDP 首部長度就不是 4
字節的整數倍了,因此小林以爲這多是爲了補全 UDP 首部長度是 4
字節的整數倍,才補充了「包長度」字段。
TCP 三次握手過程和狀態變遷
TCP 是面向鏈接的協議,因此使用 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
狀態。
ACK
標誌位置爲 1
,其次「確認應答號」字段填入 server_isn + 1
,最後把報文發送給服務端,此次報文能夠攜帶客戶到服務器的數據,以後客戶端處於 ESTABLISHED
狀態。ESTABLISHED
狀態。從上面的過程能夠發現第三次握手是能夠攜帶數據的,前兩次握手是不能夠攜帶數據的,這也是面試常問的題。
一旦完成三次握手,雙方都處於 ESTABLISHED
狀態,此致鏈接就已創建完成,客戶端和服務端就能夠相互發送數據了。
如何在 Linux 系統中查看 TCP 狀態?
TCP 的鏈接狀態查看,在 Linux 能夠經過 netstat -napt
命令查看。
爲何是三次握手?不是兩次、四次?
相信你們比較常回答的是:「由於三次握手才能保證雙方具備接收和發送的能力。」
這回答是沒問題,但這回答是片面的,並無說出主要的緣由。
在前面咱們知道了什麼是 TCP 鏈接:
因此,重要的是爲何三次握手才能夠初始化Socket、序列號和窗口大小並創建 TCP 鏈接。
接下來以三個方面分析三次握手的緣由:
緣由一:避免歷史鏈接
咱們來看看 RFC 793 指出的 TCP 鏈接使用三次握手的首要緣由:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
簡單來講,三次握手的首要緣由是爲了防止舊的重複鏈接初始化形成混亂。
網絡環境是錯綜複雜的,每每並非如咱們指望的同樣,先發送的數據包,就先到達目標主機,反而它很騷,可能會因爲網絡擁堵等亂七八糟的緣由,會使得舊的數據包,先到達目標主機,那麼這種狀況下 TCP 三次握手是如何避免的呢?
客戶端連續發送屢次 SYN 創建鏈接的報文,在網絡擁堵等狀況下:
SYN + ACK
報文給客戶端;RST
報文給服務端,表示停止這一次鏈接。若是是兩次握手鍊接,就不能判斷當前鏈接是不是歷史鏈接,三次握手則能夠在客戶端(發送方)準備發送第三次報文時,客戶端因有足夠的上下文來判斷當前鏈接是不是歷史鏈接:
RST
報文,以此停止歷史鏈接;ACK
報文,通訊雙方就會成功創建鏈接;因此, TCP 使用三次握手創建鏈接的最主要緣由是防止歷史鏈接初始化了鏈接。
緣由二:同步雙方初始序列號
TCP 協議的通訊雙方, 都必須維護一個「序列號」, 序列號是可靠傳輸的一個關鍵因素,它的做用:
可見,序列號在 TCP 鏈接中佔據着很是重要的做用,因此當客戶端發送攜帶「初始序列號」的 SYN
報文的時候,須要服務端回一個 ACK
應答報文,表示客戶端的 SVN 報文已被服務端成功接收,那當服務端發送「初始序列號」給客戶端的時候,依然也要獲得客戶端的應答迴應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。
四次握手其實也可以可靠的同步雙方的初始化序號,但因爲第二步和第三步能夠優化成一步,因此就成了「三次握手」。
而兩次握手只保證了一方的初始序列號能被對方成功接收,沒辦法保證雙方的初始序列號都能被確認接收。
緣由三:避免資源浪費
若是隻有「兩次握手」,當客戶端的 SYN
請求鏈接在網絡中阻塞,客戶端沒有接收到 ACK
報文,就會從新發送 SYN
,因爲沒有第三次握手,服務器不清楚客戶端是否收到了本身發送的創建鏈接的 ACK
確認信號,因此每收到一個 SYN
就只能先主動創建一個鏈接,這會形成什麼狀況呢?
若是客戶端的 SYN
阻塞了,重複發送屢次 SYN
報文,那麼服務器在收到請求後就會創建多個冗餘的無效連接,形成沒必要要的資源浪費。
即兩次握手會形成消息滯留狀況下,服務器重複接受無用的鏈接請求 SYN
報文,而形成重複分配資源。
小結
TCP 創建鏈接時,經過三次握手能防止歷史鏈接的創建,能減小雙方沒必要要的資源開銷,能幫助雙方同步初始化序列號。序列號可以保證數據包不重複、不丟棄和按序傳輸。
不使用「兩次握手」和「四次握手」的緣由:
爲何客戶端和服務端的初始序列號 ISN 是不相同的?
由於網絡中的報文會延遲、會複製重發、也有可能丟失,這樣會形成的不一樣鏈接之間產生互相影響,因此爲了不互相影響,客戶端和服務端的初始序列號是隨機且不一樣的。
初始序列號 ISN 是如何隨機產生的?
起始 ISN
是基於時鐘的,每 4 毫秒 + 1,轉一圈要 4.55 個小時。
RFC1948 中提出了一個較好的初始化序列號 ISN 隨機生成算法。
ISN = M + F (localhost, localport, remotehost, remoteport)
M
是一個計時器,這個計時器每隔 4 毫秒加 1。F
是一個 Hash 算法,根據源 IP、目的 IP、源端口、目的端口生成一個隨機數值。要保證 Hash 算法不能被外部輕易推算得出,用 MD5 算法是一個比較好的選擇。既然 IP 層會分片,爲何 TCP 層還須要 MSS 呢?
咱們先來認識下 MTU 和 MSS
MTU
:一個網絡包的最大長度,以太網中通常爲 1500
字節;MSS
:除去 IP 和 TCP 頭部以後,一個網絡包所能容納的 TCP 數據的最大長度;若是在 TCP 的整個報文(頭部 + 數據)交給 IP 層進行分片,會有什麼異常呢?
當 IP 層有一個超過 MTU
大小的數據(TCP 頭部 + TCP 數據)要發送,那麼 IP 層就要進行分片,把數據分片成若干片,保證每個分片都小於 MTU。把一份 IP 數據報進行分片之後,由目標主機的 IP 層來進行從新組裝後,在交給上一層 TCP 傳輸層。
這看起來井井有理,但這存在隱患的,那麼當若是一個 IP 分片丟失,整個 IP 報文的全部分片都得重傳。
由於 IP 層自己沒有超時重傳機制,它由傳輸層的 TCP 來負責超時和重傳。
當接收方發現 TCP 報文(頭部 + 數據)的某一片丟失後,則不會響應 ACK 給對方,那麼發送方的 TCP 在超時後,就會重發「整個 TCP 報文(頭部 + 數據)」。
所以,能夠得知由 IP 層進行分片傳輸,是很是沒有效率的。
因此,爲了達到最佳的傳輸效能 TCP 協議在創建鏈接的時候一般要協商雙方的 MSS 值,當 TCP 層發現數據超過 MSS 時,則就先會進行分片,固然由它造成的 IP 包的長度也就不會大於 MTU ,天然也就不用 IP 分片了。
通過 TCP 層分片後,若是一個 TCP 分片丟失後,進行重發時也是以 MSS 爲單位,而不用重傳全部的分片,大大增長了重傳的效率。
什麼是 SYN 攻擊?如何避免 SVN 攻擊?
SYN 攻擊
咱們都知道 TCP 鏈接創建是須要三次握手,假設攻擊者短期僞造不一樣 IP 地址的 SYN
報文,服務端每接收到一個 SVN
報文,就進入SYN_RCVD
狀態,但服務端發送出去的 ACK + SYN
報文,沒法獲得未知 IP 主機的 ACK
應答,長此以往就會佔滿服務端的 SYN 接收隊列(未鏈接隊列),使得服務器不能爲正經常使用戶服務。
避免 SVN 攻擊方式一
其中一種解決方式是經過修改 Linux 內核參數,控制隊列大小和當隊列滿時應作什麼處理。
net.core.netdev_max_backlog
net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_abort_on_overflow
避免 SVN 攻擊方式二
咱們先來看下Linux 內核的 SYN
(未完成鏈接創建)隊列與 Accpet
(已完成鏈接創建)隊列是如何工做的?
正常流程:
accpet()
socket 接口,從「 Accept 隊列」取出的鏈接。
應用程序過慢:
受到 SVN 攻擊:
tcp_syncookies
的方式能夠應對 SYN 攻擊的方法:
net.ipv4.tcp_syncookies = 1
cookie
值,再以 SYN + ACK 中的「序列號」返回客戶端,accpet()
socket 接口,從「 Accept 隊列」取出的鏈接。TCP 四次揮手過程和狀態變遷
天下沒有不散的宴席,對於 TCP 鏈接也是這樣, 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 和一個 ACK,所以一般被稱爲四次揮手。
這裏一點須要注意是:主動關閉鏈接的,纔有 TIME_WAIT 狀態。
爲何揮手須要四次?
再來回顧下四次揮手雙方發 FIN
包的過程,就能理解爲何須要四次了。
FIN
時,僅僅表示客戶端再也不發送數據了可是還能接收數據。FIN
報文時,先回一個 ACK
應答報文,而服務端可能還有數據須要處理和發送,等服務端再也不發送數據時,才發送 FIN
報文給客戶端來表示贊成如今關閉鏈接。從上面過程可知,服務端一般須要等待完成數據的發送和處理,因此服務端的 ACK
和 FIN
通常都會分開發送,從而比三次握手致使多了一次。
爲何 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 (60*HZ) /* how long to wait to destroy TIME-WAIT state, about 60 seconds */
若是要修改 TIME_WAIT 的時間長度,只能修改 Linux 內核代碼裏 TCP_TIMEWAIT_LEN 的值,並從新編譯 Linux 內核。
爲何須要 TIME_WAIT 狀態?
主動發起關閉鏈接的一方,纔會有 TIME-WAIT
狀態。
須要 TIME-WAIT 狀態,主要是兩個緣由:
緣由一:防止舊鏈接的數據包
假設 TIME-WAIT 沒有等待時間或時間太短,被延遲的數據包抵達後會發生什麼呢?
SEQ = 301
報文,被網絡延遲了。SEQ = 301
抵達了客戶端,那麼客戶端是有可能正常接收這個過時的報文,這就會產生數據錯亂等嚴重的問題。因此,TCP 就設計出了這麼一個機制,通過 2MSL
這個時間,足以讓兩個方向上的數據包都被丟棄,使得原來鏈接的數據包在網絡中都天然消失,再出現的數據包必定都是新創建鏈接所產生的。
緣由二:保證鏈接正確關閉
在 RFC 793 指出 TIME-WAIT 另外一個重要的做用是:
TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.
也就是說,TIME-WAIT 做用是等待足夠的時間以確保最後的 ACK 能讓被動關閉方接收,從而幫助其正常關閉。
假設 TIME-WAIT 沒有等待時間或時間太短,斷開鏈接會形成什麼問題呢?
ACK
報文若是在網絡中被丟失了,此時若是客戶端 TIME-WAIT
太短或沒有,則就直接進入了 CLOSE
狀態了,那麼服務端則會一直處在 LASE-ACK
狀態。SYN
請求報文後,服務端會發送 RST
報文給客戶端,鏈接創建的過程就會被終止。若是 TIME-WAIT 等待足夠長的狀況就會遇到兩種狀況:
ACK
報文,則服務端正常關閉鏈接。ACK
報文時,則會重發 FIN
關閉鏈接報文並等待新的 ACK
報文。因此客戶端在 TIME-WAIT
狀態等待 2MSL
時間後,就能夠保證雙方的鏈接均可以正常的關閉。
TIME_WAIT 過多有什麼危害?
若是服務器有處於 TIME-WAIT 狀態的 TCP,則說明是由服務器方主動發起的斷開請求。
過多的 TIME-WAIT 狀態主要的危害有兩種:
第二個危害是會形成嚴重的後果的,要知道,端口資源也是有限的,通常能夠開啓的端口爲 32768~61000
,也能夠經過以下參數設置指定
net.ipv4.ip_local_port_range
若是服務端 TIME_WAIT 狀態過多,佔滿了全部端口資源,則會致使沒法建立新鏈接。
如何優化 TIME_WAIT?
這裏給出優化 TIME-WAIT 的幾個方式,都是有利有弊:
方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps
以下的 Linux 內核參數開啓後,則能夠複用處於 TIME_WAIT 的 socket 爲新的鏈接所用。
net.ipv4.tcp_tw_reuse = 1
使用這個選項,還有一個前提,須要打開對 TCP 時間戳的支持,即
net.ipv4.tcp_timestamps=1(默認即爲 1)
這個時間戳的字段是在 TCP 頭部的「選項」裏,用於記錄 TCP 發送方的當前時間戳和從對端接收到的最新時間戳。
因爲引入了時間戳,咱們在前面提到的 2MSL
問題就不復存在了,由於重複的數據包會由於時間戳過時被天然丟棄。
舒適提醒:net.ipv4.tcp_tw_reuse
要慎用,由於使用了它就必然要打開時間戳的支持 net.ipv4.tcp_timestamps
,當客戶端與服務端主機時間不一樣步時,客戶端的發送的消息會被直接拒絕掉。小林在工做中就遇到過。。。排查了很是的久
方式二:net.ipv4.tcp_max_tw_buckets
這個值默認爲 18000,當系統中處於 TIME_WAIT 的鏈接一旦超過這個值時,系統就會將全部的 TIME_WAIT 鏈接狀態重置。
這個方法過於暴力,並且治標不治本,帶來的問題遠比解決的問題多,不推薦使用。
方式三:程序中使用 SO_LINGER
咱們能夠經過設置 socket 選項,來設置調用 close 關閉鏈接行爲。
struct linger so_linger; so_linger.l_onoff = 1; so_linger.l_linger = 0; setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));
若是l_onoff
爲非 0, 且l_linger
值爲 0,那麼調用close
後,會立該發送一個RST
標誌給對端,該 TCP 鏈接將跳過四次揮手,也就跳過了TIME_WAIT
狀態,直接關閉。
但這爲跨越TIME_WAIT
狀態提供了一個可能,不過是一個很是危險的行爲,不值得提倡。
若是已經創建了鏈接,可是客戶端忽然出現故障了怎麼辦?
TCP 有一個機制是保活機制。這個機制的原理是這樣的:
定義一個時間段,在這個時間段內,若是沒有任何鏈接相關的活動,TCP 保活機制會開始做用,每隔一個時間間隔,發送一個探測報文,該探測報文包含的數據很是少,若是連續幾個探測報文都沒有獲得響應,則認爲當前的 TCP 鏈接已經死亡,系統內核將錯誤信息通知給上層應用程序。
在 Linux 內核能夠有對應的參數能夠設置保活時間、保活探測的次數、保活探測的時間間隔,如下都爲默認值:
net.ipv4.tcp_keepalive_time=7200 net.ipv4.tcp_keepalive_intvl=75 net.ipv4.tcp_keepalive_probes=9
也就是說在 Linux 系統中,最少須要通過 2 小時 11 分 15 秒才能夠發現一個「死亡」鏈接。
這個時間是有點長的,咱們也能夠根據實際的需求,對以上的保活相關的參數進行設置。
若是開啓了 TCP 保活,須要考慮如下幾種狀況:
第一種,對端程序是正常工做的。當 TCP 保活的探測報文發送給對端, 對端會正常響應,這樣 TCP 保活時間會被重置,等待下一個 TCP 保活時間的到來。
第二種,對端程序崩潰並重啓。當 TCP 保活的探測報文發送給對端後,對端是能夠響應的,但因爲沒有該鏈接的有效信息,會產生一個 RST 報文,這樣很快就會發現 TCP 鏈接已經被重置。
第三種,是對端程序崩潰,或對端因爲其餘緣由致使報文不可達。當 TCP 保活的探測報文發送給對端後,石沉大海,沒有響應,連續幾回,達到保活探測次數後,TCP 會報告該 TCP 鏈接已經死亡。
針對 TCP 應該如何 Socket 編程?
socket
,獲得文件描述符;bind
,將綁定在 IP 地址和端口;listen
,進行監聽;accept
,等待客戶端鏈接;connect
,向服務器端的地址和端口發起鏈接請求;accept
返回用於傳輸的 socket
的文件描述符;write
寫入數據;服務端調用 read
讀取數據;close
,那麼服務端 read
讀取數據的時候,就會讀取到了 EOF
,待處理完數據後,服務端調用 close
,表示鏈接關閉。這裏須要注意的是,服務端調用 accept
時,鏈接成功了會返回一個已完成鏈接的 socket,後續用來傳輸數據。
因此,監聽的 socket 和真正用來傳送數據的 socket,是「兩個」 socket,一個叫做監聽 socket,一個叫做已完成鏈接 socket。
成功鏈接創建以後,雙方開始經過 read 和 write 函數來讀寫數據,就像往一個文件流裏面寫東西同樣。
listen 時候參數 backlog 的意義?
Linux內核中會維護兩個隊列:
int listen (int socketfd, int backlog)
在 早期 Linux 內核 backlog 是 SYN 隊列大小,也就是未完成的隊列大小。
在 Linux 內核 2.2 以後,backlog 變成 accept 隊列,也就是已完成鏈接創建的隊列長度,因此如今一般認爲 backlog 是 accept 隊列。
accept 發送在三次握手的哪一步?
咱們先看看客戶端鏈接服務端時,發送了什麼?
connect
調用返回,表示客戶端到服務器端的單向鏈接創建成功,客戶端的狀態爲 ESTABLISHED,同時客戶端協議棧也會對服務器端的 SYN 包進行應答,應答數據爲 server_isn+1;accept
阻塞調用返回,這個時候服務器端到客戶端的單向鏈接也創建成功,服務器端也進入 ESTABLISHED 狀態。從上面的描述過程,咱們能夠得知客戶端 connect 成功返回是在第二次握手,服務端 accept 成功返回是在三次握手成功以後。
客戶端調用 close 了,鏈接是斷開的流程是什麼?
咱們看看客戶端主動調用了 close
,會發生什麼?
close
,代表客戶端沒有數據須要發送了,則此時會向服務端發送 FIN 報文,進入 FIN_WAIT_1 狀態;EOF
到接收緩衝區中,應用程序能夠經過 read
調用來感知這個 FIN 包。這個 EOF
會被放在已排隊等候的其餘已接收的數據以後,這就意味着服務端須要處理這種異常狀況,由於 EOF 表示在該鏈接上再無額外數據到達。此時,服務端進入 CLOSE_WAIT 狀態;EOF
,因而也調用 close
關閉它的套接字,這會使得會發出一個 FIN 包,以後處於 LAST_ACK 狀態;2MSL
時間以後,也進入 CLOSED 狀態;[1] 趣談網絡協議專欄.劉超.極客時間.
[2] 網絡編程實戰專欄.盛延敏.極客時間.
[3] 計算機網絡-自頂向下方法.陳鳴 譯.機械工業出版社
[4] TCP/IP詳解 卷1:協議.範建華 譯.機械工業出版社
[5] 圖解TCP/IP.竹下隆史.人民郵電出版社
[6] https://www.rfc-editor.org/rf...
[7] https://draveness.me/whys-the...
[9] https://draveness.me/whys-the...
據說你 ping 用的很 6 ?給我圖解一下 ping 的工做原理!
小林爲寫此文重學了一篇 TCP,深感 TCP 真的是一個很是複雜的協議,要想輕易拿下,也不是一天兩天的事,因此小林花費了一個星期多才寫完此文章。
正所謂知道的越多,不知道的也越多。
下篇給你們帶來 TCP 滑動窗口、流量控制、擁塞控制的圖解文章!
本文只是拋磚引玉,若你有更好的想法或文章有誤的地方,歡迎留言討論!
小林是專爲你們圖解的工具人,Goodbye,咱們下次見!