<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          40 張圖解 TCP 三次握手和四次揮手面試題

          共 16321字,需瀏覽 33分鐘

           ·

          2022-03-01 19:37

          不管面試 Java 、C/C++、Python 等開發(fā)崗位,?TCP?的知識點可以說是必問的了。

          任 TCP 虐我千百遍,我仍待 TCP 如初戀。

          所以,小林整理了關(guān)于?TCP 三次握手和四次揮手的面試題型,跟大家一起探討探討。

          1. TCP 基本認識
          1. TCP 連接建立
          1. TCP 連接斷開
          1. Socket 編程

          PS:本次文章不涉及 TCP 流量控制、擁塞控制、可靠性傳輸?shù)确矫嬷R,這些知識在這篇:你還在為 TCP 重傳、滑動窗口、流量控制、擁塞控制發(fā)愁嗎?看完圖解就不愁了


          01 TCP 基本認識

          瞧瞧 TCP 頭格式

          我們先來看看 TCP 頭的格式,標注顏色的表示與本文關(guān)聯(lián)比較大的字段,其他字段不做詳細闡述。

          TCP 頭格式

          序列號:在建立連接時由計算機生成的隨機數(shù)作為其初始值,通過 SYN 包傳給接收端主機,每發(fā)送一次數(shù)據(jù),就「累加」一次該「數(shù)據(jù)字節(jié)數(shù)」的大小。用來解決網(wǎng)絡(luò)包亂序問題。

          確認應(yīng)答號:指下一次「期望」收到的數(shù)據(jù)的序列號,發(fā)送端收到這個確認應(yīng)答以后可以認為在這個序號以前的數(shù)據(jù)都已經(jīng)被正常接收。用來解決不丟包的問題。

          控制位:

          • ACK:該位為?1?時,「確認應(yīng)答」的字段變?yōu)橛行?,TCP 規(guī)定除了最初建立連接時的?SYN?包之外該位必須設(shè)置為?1?。
          • RST:該位為?1?時,表示 TCP 連接中出現(xiàn)異常必須強制斷開連接。
          • SYN:該位為?1?時,表示希望建立連接,并在其「序列號」的字段進行序列號初始值的設(shè)定。
          • FIN:該位為?1?時,表示今后不會再有數(shù)據(jù)發(fā)送,希望斷開連接。當通信結(jié)束希望斷開連接時,通信雙方的主機之間就可以相互交換?FIN?位為 1 的 TCP 段。

          為什么需要 TCP 協(xié)議?TCP 工作在哪一層?

          IP?層是「不可靠」的,它不保證網(wǎng)絡(luò)包的交付、不保證網(wǎng)絡(luò)包的按序交付、也不保證網(wǎng)絡(luò)包中的數(shù)據(jù)的完整性。

          OSI 參考模型與 TCP/IP 的關(guān)系

          如果需要保障網(wǎng)絡(luò)數(shù)據(jù)包的可靠性,那么就需要由上層(傳輸層)的?TCP?協(xié)議來負責(zé)。

          因為 TCP 是一個工作在傳輸層可靠數(shù)據(jù)傳輸?shù)姆?wù),它能確保接收端接收的網(wǎng)絡(luò)包是無損壞、無間隔、非冗余和按序的。

          什么是 TCP ?

          TCP 是面向連接的、可靠的、基于字節(jié)流的傳輸層通信協(xié)議。

          • 面向連接:一定是「一對一」才能連接,不能像 UDP 協(xié)議可以一個主機同時向多個主機發(fā)送消息,也就是一對多是無法做到的;

          • 可靠的:無論的網(wǎng)絡(luò)鏈路中出現(xiàn)了怎樣的鏈路變化,TCP 都可以保證一個報文一定能夠到達接收端;

          • 字節(jié)流:消息是「沒有邊界」的,所以無論我們消息有多大都可以進行傳輸。并且消息是「有序的」,當「前一個」消息沒有收到的時候,即使它先收到了后面的字節(jié),那么也不能扔給應(yīng)用層去處理,同時對「重復(fù)」的報文會自動丟棄。

          什么是 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.

          簡單來說就是,用于保證可靠性和流量控制維護的某些狀態(tài)信息,這些信息的組合,包括Socket、序列號和窗口大小稱為連接。

          所以我們可以知道,建立一個 TCP 連接是需要客戶端與服務(wù)器端達成上述三個信息的共識。

          • Socket:由 IP 地址和端口號組成
          • 序列號:用來解決亂序問題等
          • 窗口大小:用來做流量控制

          如何唯一確定一個 TCP 連接呢?

          TCP 四元組可以唯一的確定一個連接,四元組包括如下:

          • 源地址
          • 源端口
          • 目的地址
          • 目的端口
          TCP 四元組

          源地址和目的地址的字段(32位)是在 IP 頭部中,作用是通過 IP 協(xié)議發(fā)送報文給對方主機。

          源端口和目的端口的字段(16位)是在 TCP 頭部中,作用是告訴 TCP 協(xié)議應(yīng)該把報文發(fā)給哪個進程。

          有一個 IP 的服務(wù)器監(jiān)聽了一個端口,它的 TCP 的最大連接數(shù)是多少?

          服務(wù)器通常固定在某個本地端口上監(jiān)聽,等待客戶端的連接請求。

          因此,客戶端 IP 和 端口是可變的,其理論值計算公式如下:

          對 IPv4,客戶端的 IP 數(shù)最多為?2?的?32?次方,客戶端的端口數(shù)最多為?2?的?16?次方,也就是服務(wù)端單機最大 TCP 連接數(shù),約為?2?的?48?次方。

          當然,服務(wù)端最大并發(fā) TCP 連接數(shù)遠不能達到理論上限。

          • 首先主要是文件描述符限制,Socket 都是文件,所以首先要通過?ulimit?配置文件描述符的數(shù)目;
          • 另一個是內(nèi)存限制,每個 TCP 連接都要占用一定內(nèi)存,操作系統(tǒng)的內(nèi)存是有限的。

          UDP 和 TCP 有什么區(qū)別呢?分別的應(yīng)用場景是?

          UDP 不提供復(fù)雜的控制機制,利用 IP 提供面向「無連接」的通信服務(wù)。

          UDP 協(xié)議真的非常簡,頭部只有?8?個字節(jié)( 64 位),UDP 的頭部格式如下:

          UDP 頭部格式
          • 目標和源端口:主要是告訴 UDP 協(xié)議應(yīng)該把報文發(fā)給哪個進程。
          • 包長度:該字段保存了 UDP 首部的長度跟數(shù)據(jù)的長度之和。
          • 校驗和:校驗和是為了提供可靠的 UDP 首部和數(shù)據(jù)而設(shè)計。

          TCP 和 UDP 區(qū)別:

          1. 連接

          • TCP 是面向連接的傳輸層協(xié)議,傳輸數(shù)據(jù)前先要建立連接。
          • UDP 是不需要連接,即刻傳輸數(shù)據(jù)。

          2. 服務(wù)對象

          • TCP 是一對一的兩點服務(wù),即一條連接只有兩個端點。
          • UDP 支持一對一、一對多、多對多的交互通信

          3. 可靠性

          • TCP 是可靠交付數(shù)據(jù)的,數(shù)據(jù)可以無差錯、不丟失、不重復(fù)、按需到達。
          • UDP 是盡最大努力交付,不保證可靠交付數(shù)據(jù)。

          4. 擁塞控制、流量控制

          • TCP 有擁塞控制和流量控制機制,保證數(shù)據(jù)傳輸?shù)陌踩浴?/section>
          • UDP 則沒有,即使網(wǎng)絡(luò)非常擁堵了,也不會影響 UDP 的發(fā)送速率。

          5. 首部開銷

          • TCP 首部長度較長,會有一定的開銷,首部在沒有使用「選項」字段時是?20?個字節(jié),如果使用了「選項」字段則會變長的。
          • UDP 首部只有 8 個字節(jié),并且是固定不變的,開銷較小。

          6. 傳輸方式

          • TCP 是流式傳輸,沒有邊界,但保證順序和可靠。
          • UDP 是一個包一個包的發(fā)送,是有邊界的,但可能會丟包和亂序。

          7. 分片不同

          • TCP 的數(shù)據(jù)大小如果大于 MSS 大小,則會在傳輸層進行分片,目標主機收到后,也同樣在傳輸層組裝 TCP 數(shù)據(jù)包,如果中途丟失了一個分片,只需要傳輸丟失的這個分片。
          • UDP 的數(shù)據(jù)大小如果大于 MTU 大小,則會在 IP 層進行分片,目標主機收到后,在 IP 層組裝完數(shù)據(jù),接著再傳給傳輸層,但是如果中途丟了一個分片,則就需要重傳所有的數(shù)據(jù)包,這樣傳輸效率非常差,所以通常 UDP 的報文應(yīng)該小于 MTU。

          TCP 和 UDP 應(yīng)用場景:

          由于 TCP 是面向連接,能保證數(shù)據(jù)的可靠性交付,因此經(jīng)常用于:

          • FTP?文件傳輸
          • HTTP?/?HTTPS

          由于 UDP 面向無連接,它可以隨時發(fā)送數(shù)據(jù),再加上UDP本身的處理既簡單又高效,因此經(jīng)常用于:

          • 包總量較少的通信,如?DNS?、SNMP?等
          • 視頻、音頻等多媒體通信
          • 廣播通信

          為什么 UDP 頭部沒有「首部長度」字段,而 TCP 頭部有「首部長度」字段呢?

          原因是 TCP 有可變長的「選項」字段,而 UDP 頭部長度則是不會變化的,無需多一個字段去記錄 UDP 的首部長度。

          為什么 UDP 頭部有「包長度」字段,而 TCP 頭部則沒有「包長度」字段呢?

          先說說 TCP 是如何計算負載數(shù)據(jù)長度:

          其中 IP 總長度 和 IP 首部長度,在 IP 首部格式是已知的。TCP 首部長度,則是在 TCP 首部格式已知的,所以就可以求得 TCP 數(shù)據(jù)的長度。

          大家這時就奇怪了問:“ UDP 也是基于 IP 層的呀,那 UDP 的數(shù)據(jù)長度也可以通過這個公式計算呀?為何還要有「包長度」呢?”

          這么一問,確實感覺 UDP 「包長度」是冗余的。

          因為為了網(wǎng)絡(luò)設(shè)備硬件設(shè)計和處理方便,首部長度需要是?4字節(jié)的整數(shù)倍。

          如果去掉 UDP 「包長度」字段,那 UDP 首部長度就不是?4?字節(jié)的整數(shù)倍了,所以小林覺得這可能是為了補全 UDP 首部長度是?4?字節(jié)的整數(shù)倍,才補充了「包長度」字段。

          02 TCP 連接建立

          TCP 三次握手過程和狀態(tài)變遷

          TCP 是面向連接的協(xié)議,所以使用 TCP 前必須先建立連接,而建立連接是通過三次握手來進行的。

          TCP 三次握手
          • 一開始,客戶端和服務(wù)端都處于?CLOSED?狀態(tài)。先是服務(wù)端主動監(jiān)聽某個端口,處于?LISTEN?狀態(tài)
          第一個報文—— SYN 報文
          • 客戶端會隨機初始化序號(client_isn),將此序號置于 TCP 首部的「序號」字段中,同時把?SYN?標志位置為?1?,表示?SYN?報文。接著把第一個 SYN 報文發(fā)送給服務(wù)端,表示向服務(wù)端發(fā)起連接,該報文不包含應(yīng)用層數(shù)據(jù),之后客戶端處于?SYN-SENT?狀態(tài)。
          第二個報文 —— SYN + ACK 報文
          • 服務(wù)端收到客戶端的?SYN?報文后,首先服務(wù)端也隨機初始化自己的序號(server_isn),將此序號填入 TCP 首部的「序號」字段中,其次把 TCP 首部的「確認應(yīng)答號」字段填入?client_isn + 1, 接著把?SYN?和?ACK?標志位置為?1。最后把該報文發(fā)給客戶端,該報文也不包含應(yīng)用層數(shù)據(jù),之后服務(wù)端處于?SYN-RCVD?狀態(tài)。
          第三個報文 —— ACK 報文
          • 客戶端收到服務(wù)端報文后,還要向服務(wù)端回應(yīng)最后一個應(yīng)答報文,首先該應(yīng)答報文 TCP 首部?ACK?標志位置為?1?,其次「確認應(yīng)答號」字段填入?server_isn + 1?,最后把報文發(fā)送給服務(wù)端,這次報文可以攜帶客戶到服務(wù)器的數(shù)據(jù),之后客戶端處于?ESTABLISHED?狀態(tài)。

          • 服務(wù)器收到客戶端的應(yīng)答報文后,也進入?ESTABLISHED?狀態(tài)。

          從上面的過程可以發(fā)現(xiàn)第三次握手是可以攜帶數(shù)據(jù)的,前兩次握手是不可以攜帶數(shù)據(jù)的,這也是面試常問的題。

          一旦完成三次握手,雙方都處于?ESTABLISHED?狀態(tài),此時連接就已建立完成,客戶端和服務(wù)端就可以相互發(fā)送數(shù)據(jù)了。

          如何在 Linux 系統(tǒng)中查看 TCP 狀態(tài)?

          TCP 的連接狀態(tài)查看,在 Linux 可以通過?netstat -napt?命令查看。

          TCP 連接狀態(tài)查看

          為什么是三次握手?不是兩次、四次?

          相信大家比較?;卮鸬氖牵骸耙驗槿挝帐植拍鼙WC雙方具有接收和發(fā)送的能力?!?/p>

          這回答是沒問題,但這回答是片面的,并沒有說出主要的原因。

          在前面我們知道了什么是?TCP 連接

          • 用于保證可靠性和流量控制維護的某些狀態(tài)信息,這些信息的組合,包括Socket、序列號和窗口大小稱為連接。

          所以,重要的是為什么三次握手才可以初始化Socket、序列號和窗口大小并建立 TCP 連接。

          接下來以三個方面分析三次握手的原因:

          • 三次握手才可以阻止重復(fù)歷史連接的初始化(主要原因)
          • 三次握手才可以同步雙方的初始序列號
          • 三次握手才可以避免資源浪費

          原因一:避免歷史連接

          我們來看看 RFC 793 指出的 TCP 連接使用三次握手的首要原因

          The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

          簡單來說,三次握手的首要原因是為了防止舊的重復(fù)連接初始化造成混亂。

          網(wǎng)絡(luò)環(huán)境是錯綜復(fù)雜的,往往并不是如我們期望的一樣,先發(fā)送的數(shù)據(jù)包,就先到達目標主機,反而它很騷,可能會由于網(wǎng)絡(luò)擁堵等亂七八糟的原因,會使得舊的數(shù)據(jù)包,先到達目標主機,那么這種情況下 TCP 三次握手是如何避免的呢?

          三次握手避免歷史連接

          客戶端連續(xù)發(fā)送多次 SYN 建立連接的報文,在網(wǎng)絡(luò)擁堵情況下:

          • 一個「舊 SYN 報文」比「最新的 SYN 」 報文早到達了服務(wù)端;
          • 那么此時服務(wù)端就會回一個?SYN + ACK?報文給客戶端;
          • 客戶端收到后可以根據(jù)自身的上下文,判斷這是一個歷史連接(序列號過期或超時),那么客戶端就會發(fā)送?RST?報文給服務(wù)端,表示中止這一次連接。

          如果是兩次握手連接,就無法阻止歷史連接,那為什么 TCP 兩次握手為什么無法阻止歷史連接呢?

          我先直接說結(jié)論,主要是因為在兩次握手的情況下,「被動發(fā)起方」沒有中間狀態(tài)給「主動發(fā)起方」來阻止歷史連接,導(dǎo)致「被動發(fā)起方」可能建立一個歷史連接,造成資源浪費。

          你想想,兩次握手的情況下,「被動發(fā)起方」在收到 SYN 報文后,就進入 ESTABLISHED 狀態(tài),意味著這時可以給對方發(fā)送數(shù)據(jù)給,但是「主動發(fā)」起方此時還沒有進入 ESTABLISHED 狀態(tài),假設(shè)這次是歷史連接,主動發(fā)起方判斷到此次連接為歷史連接,那么就會回 RST 報文來斷開連接,而「被動發(fā)起方」在第一次握手的時候就進入 ESTABLISHED 狀態(tài),所以它可以發(fā)送數(shù)據(jù)的,但是它并不知道這個是歷史連接,它只有在收到 RST 報文后,才會斷開連接。

          兩次握手無法阻止歷史連接

          可以看到,上面這種場景下,「被動發(fā)起方」在向「主動發(fā)起方」發(fā)送數(shù)據(jù)前,并沒有阻止掉歷史連接,導(dǎo)致「被動發(fā)起方」建立了一個歷史連接,又白白發(fā)送了數(shù)據(jù),妥妥地浪費了「被動發(fā)起方」的資源。

          因此,要解決這種現(xiàn)象,最好就是在「被動發(fā)起方」發(fā)送數(shù)據(jù)前,也就是建立連接之前,要阻止掉歷史連接,這樣就不會造成資源浪費,而要實現(xiàn)這個功能,就需要三次握手

          所以,TCP 使用三次握手建立連接的最主要原因是防止「歷史連接」初始化了連接。

          原因二:同步雙方初始序列號

          TCP 協(xié)議的通信雙方, 都必須維護一個「序列號」, 序列號是可靠傳輸?shù)囊粋€關(guān)鍵因素,它的作用:

          • 接收方可以去除重復(fù)的數(shù)據(jù);
          • 接收方可以根據(jù)數(shù)據(jù)包的序列號按序接收;
          • 可以標識發(fā)送出去的數(shù)據(jù)包中, 哪些是已經(jīng)被對方收到的;

          可見,序列號在 TCP 連接中占據(jù)著非常重要的作用,所以當客戶端發(fā)送攜帶「初始序列號」的?SYN?報文的時候,需要服務(wù)端回一個?ACK?應(yīng)答報文,表示客戶端的 SYN 報文已被服務(wù)端成功接收,那當服務(wù)端發(fā)送「初始序列號」給客戶端的時候,依然也要得到客戶端的應(yīng)答回應(yīng),這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。

          四次握手與三次握手

          四次握手其實也能夠可靠的同步雙方的初始化序號,但由于第二步和第三步可以優(yōu)化成一步,所以就成了「三次握手」。

          而兩次握手只保證了一方的初始序列號能被對方成功接收,沒辦法保證雙方的初始序列號都能被確認接收。

          原因三:避免資源浪費

          如果只有「兩次握手」,當客戶端的?SYN?請求連接在網(wǎng)絡(luò)中阻塞,客戶端沒有接收到?ACK?報文,就會重新發(fā)送?SYN?,由于沒有第三次握手,服務(wù)器不清楚客戶端是否收到了自己發(fā)送的建立連接的?ACK?確認信號,所以每收到一個?SYN?就只能先主動建立一個連接,這會造成什么情況呢?

          如果客戶端的?SYN?阻塞了,重復(fù)發(fā)送多次?SYN?報文,那么服務(wù)器在收到請求后就會建立多個冗余的無效鏈接,造成不必要的資源浪費。

          兩次握手會造成資源浪費

          即兩次握手會造成消息滯留情況下,服務(wù)器重復(fù)接受無用的連接請求?SYN?報文,而造成重復(fù)分配資源。

          小結(jié)

          TCP 建立連接時,通過三次握手能防止歷史連接的建立,能減少雙方不必要的資源開銷,能幫助雙方同步初始化序列號。序列號能夠保證數(shù)據(jù)包不重復(fù)、不丟棄和按序傳輸。

          不使用「兩次握手」和「四次握手」的原因:

          • 「兩次握手」:無法防止歷史連接的建立,會造成雙方資源的浪費,也無法可靠的同步雙方序列號;
          • 「四次握手」:三次握手就已經(jīng)理論上最少可靠連接建立,所以不需要使用更多的通信次數(shù)。

          為什么每次建立 TCP 連接時,初始化的序列號都要求不一樣呢?

          主要原因是為了防止歷史報文被下一個相同四元組的連接接收。

          假設(shè)每次建立連接,客戶端和服務(wù)端的初始化序列號都是從 0 開始:

          過程如下:

          • 客戶端和服務(wù)端建立一個 TCP 連接,在客戶端發(fā)送數(shù)據(jù)包被網(wǎng)絡(luò)阻塞了,而此時服務(wù)端的進程重啟了,于是就會發(fā)送 RST 報文來斷開連接。
          • 緊接著,客戶端又與服務(wù)端建立了與上一個連接相同四元組的連接;
          • 在新連接建立完成后,上一個連接中被網(wǎng)絡(luò)阻塞的數(shù)據(jù)包正好抵達了服務(wù)端,剛好該數(shù)據(jù)包的序列號正好是在服務(wù)端的接收窗口內(nèi),所以該數(shù)據(jù)包會被服務(wù)端正常接收,就會造成數(shù)據(jù)錯亂。

          可以看到,如果每次建立連接,客戶端和服務(wù)端的初始化序列號都是一樣的話,很容易出現(xiàn)歷史報文被下一個相同四元組的連接接收的問題。

          如果每次建立連接客戶端和服務(wù)端的初始化序列號都「不一樣」,就有大概率因為歷史報文的序列號「不在」對方接收窗口,從而很大程度上避免了歷史報文,比如下圖:

          相反,如果每次建立連接客戶端和服務(wù)端的初始化序列號都「一樣」,就有大概率遇到歷史報文的序列號剛「好在」對方的接收窗口內(nèi),從而導(dǎo)致歷史報文被新連接成功接收。

          所以,每次初始化序列號不一樣能夠很大程度上避免歷史報文被下一個相同四元組的連接接收,注意是很大程度上,并不是完全避免了。

          另一方面是為了安全性,防止黑客偽造的相同序列號的 TCP 報文被對方接收。

          初始序列號 ISN 是如何隨機產(chǎn)生的?

          起始?ISN?是基于時鐘的,每 4 毫秒 + 1,轉(zhuǎn)一圈要 4.55 個小時。

          RFC793 提到初始化序列號 ISN 隨機生成算法:ISN = M + F(localhost, localport, remotehost, remoteport)。

          • M?是一個計時器,這個計時器每隔 4 毫秒加 1。
          • F?是一個 Hash 算法,根據(jù)源 IP、目的 IP、源端口、目的端口生成一個隨機數(shù)值。要保證 Hash 算法不能被外部輕易推算得出,用 MD5 算法是一個比較好的選擇。

          可以看到,隨機數(shù)是會基于時鐘計時器遞增的,基本不可能會隨機成一樣的初始化序列號。

          既然 IP 層會分片,為什么 TCP 層還需要 MSS 呢?

          我們先來認識下 MTU 和 MSS

          MTU 與 MSS
          • MTU:一個網(wǎng)絡(luò)包的最大長度,以太網(wǎng)中一般為?1500?字節(jié);
          • MSS:除去 IP 和 TCP 頭部之后,一個網(wǎng)絡(luò)包所能容納的 TCP 數(shù)據(jù)的最大長度;

          如果在 TCP 的整個報文(頭部 + 數(shù)據(jù))交給 IP 層進行分片,會有什么異常呢?

          當 IP 層有一個超過?MTU?大小的數(shù)據(jù)(TCP 頭部 + TCP 數(shù)據(jù))要發(fā)送,那么 IP 層就要進行分片,把數(shù)據(jù)分片成若干片,保證每一個分片都小于 MTU。把一份 IP 數(shù)據(jù)報進行分片以后,由目標主機的 IP 層來進行重新組裝后,再交給上一層 TCP 傳輸層。

          這看起來井然有序,但這存在隱患的,那么當如果一個 IP 分片丟失,整個 IP 報文的所有分片都得重傳。

          因為 IP 層本身沒有超時重傳機制,它由傳輸層的 TCP 來負責(zé)超時和重傳。

          當接收方發(fā)現(xiàn) TCP 報文(頭部 + 數(shù)據(jù))的某一片丟失后,則不會響應(yīng) ACK 給對方,那么發(fā)送方的 TCP 在超時后,就會重發(fā)「整個 TCP 報文(頭部 + 數(shù)據(jù))」。

          因此,可以得知由 IP 層進行分片傳輸,是非常沒有效率的。

          所以,為了達到最佳的傳輸效能 TCP 協(xié)議在建立連接的時候通常要協(xié)商雙方的 MSS 值,當 TCP 層發(fā)現(xiàn)數(shù)據(jù)超過 MSS 時,則就先會進行分片,當然由它形成的 IP 包的長度也就不會大于 MTU ,自然也就不用 IP 分片了。

          握手階段協(xié)商 MSS

          經(jīng)過 TCP 層分片后,如果一個 TCP 分片丟失后,進行重發(fā)時也是以 MSS 為單位,而不用重傳所有的分片,大大增加了重傳的效率。

          什么是 SYN 攻擊?如何避免 SYN 攻擊?

          SYN 攻擊

          我們都知道 TCP 連接建立是需要三次握手,假設(shè)攻擊者短時間偽造不同 IP 地址的?SYN?報文,服務(wù)端每接收到一個?SYN?報文,就進入SYN_RCVD?狀態(tài),但服務(wù)端發(fā)送出去的?ACK + SYN?報文,無法得到未知 IP 主機的?ACK?應(yīng)答,久而久之就會占滿服務(wù)端的 SYN 接收隊列(未連接隊列),使得服務(wù)器不能為正常用戶服務(wù)。

          SYN 攻擊

          避免 SYN 攻擊方式一

          其中一種解決方式是通過修改 Linux 內(nèi)核參數(shù),控制隊列大小和當隊列滿時應(yīng)做什么處理。

          • 當網(wǎng)卡接收數(shù)據(jù)包的速度大于內(nèi)核處理的速度時,會有一個隊列保存這些數(shù)據(jù)包??刂圃撽犃械淖畲笾等缦聟?shù):
          net.core.netdev_max_backlog
          • SYN_RCVD 狀態(tài)連接的最大個數(shù):
          net.ipv4.tcp_max_syn_backlog
          • 超出處理能時,對新的 SYN 直接回報 RST,丟棄連接:
          net.ipv4.tcp_abort_on_overflow

          避免 SYN 攻擊方式二

          我們先來看下 Linux 內(nèi)核的?SYN?(未完成連接建立)隊列與?Accpet?(已完成連接建立)隊列是如何工作的?

          正常流程

          正常流程:

          • 當服務(wù)端接收到客戶端的 SYN 報文時,會將其加入到內(nèi)核的「 SYN 隊列」;
          • 接著發(fā)送 SYN + ACK 給客戶端,等待客戶端回應(yīng) ACK 報文;
          • 服務(wù)端接收到 ACK 報文后,從「 SYN 隊列」移除放入到「 Accept 隊列」;
          • 應(yīng)用通過調(diào)用?accpet()?socket 接口,從「 Accept 隊列」取出連接。
          應(yīng)用程序過慢

          應(yīng)用程序過慢:

          • 如果應(yīng)用程序過慢時,就會導(dǎo)致「 Accept 隊列」被占滿。
          受到 SYN 攻擊

          受到 SYN 攻擊:

          • 如果不斷受到 SYN 攻擊,就會導(dǎo)致「 SYN 隊列」被占滿。

          tcp_syncookies?的方式可以應(yīng)對 SYN 攻擊的方法:

          net.ipv4.tcp_syncookies?=?1
          tcp_syncookies 應(yīng)對 SYN 攻擊
          • 當 「 SYN 隊列」?jié)M之后,后續(xù)服務(wù)器收到 SYN 包,不進入「 SYN 隊列」;
          • 計算出一個?cookie?值,再以 SYN + ACK 中的「序列號」返回客戶端,
          • 服務(wù)端接收到客戶端的應(yīng)答報文時,服務(wù)器會檢查這個 ACK 包的合法性。如果合法,直接放入到「 Accept 隊列」。
          • 最后應(yīng)用通過調(diào)用?accpet()?socket 接口,從「 Accept 隊列」取出的連接。

          03 TCP 連接斷開

          TCP 四次揮手過程和狀態(tài)變遷

          天下沒有不散的宴席,對于 TCP 連接也是這樣, TCP 斷開連接是通過四次揮手方式。

          雙方都可以主動斷開連接,斷開連接后主機中的「資源」將被釋放。

          客戶端主動關(guān)閉連接 —— TCP 四次揮手
          • 客戶端打算關(guān)閉連接,此時會發(fā)送一個 TCP 首部?FIN?標志位被置為?1?的報文,也即?FIN?報文,之后客戶端進入?FIN_WAIT_1?狀態(tài)。
          • 服務(wù)端收到該報文后,就向客戶端發(fā)送?ACK?應(yīng)答報文,接著服務(wù)端進入?CLOSED_WAIT?狀態(tài)。
          • 客戶端收到服務(wù)端的?ACK?應(yīng)答報文后,之后進入?FIN_WAIT_2?狀態(tài)。
          • 等待服務(wù)端處理完數(shù)據(jù)后,也向客戶端發(fā)送?FIN?報文,之后服務(wù)端進入?LAST_ACK?狀態(tài)。
          • 客戶端收到服務(wù)端的?FIN?報文后,回一個?ACK?應(yīng)答報文,之后進入?TIME_WAIT?狀態(tài)
          • 服務(wù)器收到了?ACK?應(yīng)答報文后,就進入了?CLOSED?狀態(tài),至此服務(wù)端已經(jīng)完成連接的關(guān)閉。
          • 客戶端在經(jīng)過?2MSL?一段時間后,自動進入?CLOSED?狀態(tài),至此客戶端也完成連接的關(guān)閉。

          你可以看到,每個方向都需要一個 FIN 和一個 ACK,因此通常被稱為四次揮手

          這里一點需要注意是:主動關(guān)閉連接的,才有 TIME_WAIT 狀態(tài)。

          為什么揮手需要四次?

          再來回顧下四次揮手雙方發(fā)?FIN?包的過程,就能理解為什么需要四次了。

          • 關(guān)閉連接時,客戶端向服務(wù)端發(fā)送?FIN?時,僅僅表示客戶端不再發(fā)送數(shù)據(jù)了但是還能接收數(shù)據(jù)。
          • 服務(wù)器收到客戶端的?FIN?報文時,先回一個?ACK?應(yīng)答報文,而服務(wù)端可能還有數(shù)據(jù)需要處理和發(fā)送,等服務(wù)端不再發(fā)送數(shù)據(jù)時,才發(fā)送?FIN?報文給客戶端來表示同意現(xiàn)在關(guān)閉連接。

          從上面過程可知,服務(wù)端通常需要等待完成數(shù)據(jù)的發(fā)送和處理,所以服務(wù)端的?ACK?和?FIN?一般都會分開發(fā)送,從而比三次握手導(dǎo)致多了一次。

          為什么 TIME_WAIT 等待的時間是 2MSL?

          MSL?是 Maximum Segment Lifetime,報文最大生存時間,它是任何報文在網(wǎng)絡(luò)上存在的最長時間,超過這個時間報文將被丟棄。因為 TCP 報文基于是 IP 協(xié)議的,而 IP 頭中有一個?TTL?字段,是 IP 數(shù)據(jù)報可以經(jīng)過的最大路由數(shù),每經(jīng)過一個處理他的路由器此值就減 1,當此值為 0 則數(shù)據(jù)報將被丟棄,同時發(fā)送 ICMP 報文通知源主機。

          MSL 與 TTL 的區(qū)別:MSL 的單位是時間,而 TTL 是經(jīng)過路由跳數(shù)。所以?MSL 應(yīng)該要大于等于 TTL 消耗為 0 的時間,以確保報文已被自然消亡。

          TIME_WAIT 等待 2 倍的 MSL,比較合理的解釋是:網(wǎng)絡(luò)中可能存在來自發(fā)送方的數(shù)據(jù)包,當這些發(fā)送方的數(shù)據(jù)包被接收方處理后又會向?qū)Ψ桨l(fā)送響應(yīng),所以一來一回需要等待 2 倍的時間。

          比如如果被動關(guān)閉方?jīng)]有收到斷開連接的最后的 ACK 報文,就會觸發(fā)超時重發(fā) Fin 報文,另一方接收到 FIN 后,會重發(fā) ACK 給被動關(guān)閉方, 一來一去正好 2 個 MSL。

          2MSL?的時間是從客戶端接收到 FIN 后發(fā)送 ACK 開始計時的。如果在 TIME-WAIT 時間內(nèi),因為客戶端的 ACK 沒有傳輸?shù)椒?wù)端,客戶端又接收到了服務(wù)端重發(fā)的 FIN 報文,那么?2MSL 時間將重新計時。

          在 Linux 系統(tǒng)里?2MSL?默認是?60?秒,那么一個?MSL?也就是?30?秒。Linux 系統(tǒng)停留在 TIME_WAIT 的時間為固定的 60 秒。

          其定義在 Linux 內(nèi)核代碼里的名稱為 TCP_TIMEWAIT_LEN:

          #define?TCP_TIMEWAIT_LEN?(60*HZ)?/*?how?long?to?wait?to?destroy?TIME-WAIT?
          ????????????????????????????????????state,?about?60?seconds??*/

          如果要修改 TIME_WAIT 的時間長度,只能修改 Linux 內(nèi)核代碼里 TCP_TIMEWAIT_LEN 的值,并重新編譯 Linux 內(nèi)核。

          為什么需要 TIME_WAIT 狀態(tài)?

          主動發(fā)起關(guān)閉連接的一方,才會有?TIME-WAIT?狀態(tài)。

          需要 TIME-WAIT 狀態(tài),主要是兩個原因:

          • 防止具有相同「四元組」的「舊」數(shù)據(jù)包被收到;
          • 保證「被動關(guān)閉連接」的一方能被正確的關(guān)閉,即保證最后的 ACK 能讓被動關(guān)閉方接收,從而幫助其正常關(guān)閉;

          原因一:防止舊連接的數(shù)據(jù)包

          假設(shè) TIME-WAIT 沒有等待時間或時間過短,被延遲的數(shù)據(jù)包抵達后會發(fā)生什么呢?

          接收到歷史數(shù)據(jù)的異常
          • 如上圖黃色框框服務(wù)端在關(guān)閉連接之前發(fā)送的?SEQ = 301?報文,被網(wǎng)絡(luò)延遲了。
          • 這時有相同端口的 TCP 連接被復(fù)用后,被延遲的?SEQ = 301?抵達了客戶端,那么客戶端是有可能正常接收這個過期的報文,這就會產(chǎn)生數(shù)據(jù)錯亂等嚴重的問題。

          所以,TCP 就設(shè)計出了這么一個機制,經(jīng)過?2MSL?這個時間,足以讓兩個方向上的數(shù)據(jù)包都被丟棄,使得原來連接的數(shù)據(jù)包在網(wǎng)絡(luò)中都自然消失,再出現(xiàn)的數(shù)據(jù)包一定都是新建立連接所產(chǎn)生的。

          原因二:保證連接正確關(guān)閉

          在 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 能讓被動關(guān)閉方接收,從而幫助其正常關(guān)閉。

          假設(shè) TIME-WAIT 沒有等待時間或時間過短,斷開連接會造成什么問題呢?

          沒有確保正常斷開的異常
          • 如上圖紅色框框客戶端四次揮手的最后一個?ACK?報文如果在網(wǎng)絡(luò)中被丟失了,此時如果客戶端?TIME-WAIT?過短或沒有,則就直接進入了?CLOSED?狀態(tài)了,那么服務(wù)端則會一直處在?LASE_ACK?狀態(tài)。
          • 當客戶端發(fā)起建立連接的?SYN?請求報文后,服務(wù)端會發(fā)送?RST?報文給客戶端,連接建立的過程就會被終止。

          如果 TIME-WAIT 等待足夠長的情況就會遇到兩種情況:

          • 服務(wù)端正常收到四次揮手的最后一個?ACK?報文,則服務(wù)端正常關(guān)閉連接。
          • 服務(wù)端沒有收到四次揮手的最后一個?ACK?報文時,則會重發(fā)?FIN?關(guān)閉連接報文并等待新的?ACK?報文。

          所以客戶端在?TIME-WAIT?狀態(tài)等待?2MSL?時間后,就可以保證雙方的連接都可以正常的關(guān)閉。

          TIME_WAIT 過多有什么危害?

          如果服務(wù)器有處于 TIME-WAIT 狀態(tài)的 TCP,則說明是由服務(wù)器方主動發(fā)起的斷開請求。

          過多的 TIME-WAIT 狀態(tài)主要的危害有兩種:

          • 第一是內(nèi)存資源占用;
          • 第二是對端口資源的占用,一個 TCP 連接至少消耗一個本地端口;

          第二個危害是會造成嚴重的后果的,要知道,端口資源也是有限的,一般可以開啟的端口為?32768~61000,也可以通過如下參數(shù)設(shè)置指定:

          net.ipv4.ip_local_port_range

          如果發(fā)起連接一方的 TIME_WAIT 狀態(tài)過多,占滿了所有端口資源,則會導(dǎo)致無法創(chuàng)建新連接。

          客戶端(發(fā)起連接方)受端口資源限制:

          • 客戶端TIME_WAIT過多,就會導(dǎo)致端口資源被占用,因為端口就65536個,被占滿就會導(dǎo)致無法創(chuàng)建新的連接。

          服務(wù)端受系統(tǒng)資源限制:

          • 由于一個四元組表示 TCP 連接,理論上服務(wù)端可以建立很多連接,服務(wù)端確實只監(jiān)聽一個端口。但是連接過多,會占用系統(tǒng)資源。

          如何優(yōu)化 TIME_WAIT?

          這里給出優(yōu)化 TIME-WAIT 的幾個方式,都是有利有弊:

          • 打開 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 選項;
          • net.ipv4.tcp_max_tw_buckets
          • 程序中使用 SO_LINGER ,應(yīng)用強制使用 RST 關(guān)閉。

          方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps

          如下的 Linux 內(nèi)核參數(shù)開啟后,則可以復(fù)用處于 TIME_WAIT 的 socket 為新的連接所用。

          有一點需要注意的是,tcp_tw_reuse 功能只能用客戶端(連接發(fā)起方),因為開啟了該功能,在調(diào)用 connect() 函數(shù)時,內(nèi)核會隨機找一個 time_wait 狀態(tài)超過 1 秒的連接給新的連接復(fù)用。

          net.ipv4.tcp_tw_reuse?=?1

          使用這個選項,還有一個前提,需要打開對 TCP 時間戳的支持,即

          net.ipv4.tcp_timestamps=1(默認即為?1)

          這個時間戳的字段是在 TCP 頭部的「選項」里,用于記錄 TCP 發(fā)送方的當前時間戳和從對端接收到的最新時間戳。

          由于引入了時間戳,我們在前面提到的?2MSL?問題就不復(fù)存在了,因為重復(fù)的數(shù)據(jù)包會因為時間戳過期被自然丟棄。

          方式二:net.ipv4.tcp_max_tw_buckets

          這個值默認為 18000,當系統(tǒng)中處于 TIME_WAIT 的連接一旦超過這個值時,系統(tǒng)就會將后面的 TIME_WAIT 連接狀態(tài)重置,這個方法比較暴力。

          方式三:程序中使用 SO_LINGER

          我們可以通過設(shè)置 socket 選項,來設(shè)置調(diào)用 close 關(guān)閉連接行為。

          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,那么調(diào)用close后,會立該發(fā)送一個RST標志給對端,該 TCP 連接將跳過四次揮手,也就跳過了TIME_WAIT狀態(tài),直接關(guān)閉。

          但這為跨越TIME_WAIT狀態(tài)提供了一個可能,不過是一個非常危險的行為,不值得提倡。

          如果已經(jīng)建立了連接,但是客戶端突然出現(xiàn)故障了怎么辦?

          TCP 有一個機制是?;顧C制。這個機制的原理是這樣的:

          定義一個時間段,在這個時間段內(nèi),如果沒有任何連接相關(guān)的活動,TCP ?;顧C制會開始作用,每隔一個時間間隔,發(fā)送一個探測報文,該探測報文包含的數(shù)據(jù)非常少,如果連續(xù)幾個探測報文都沒有得到響應(yīng),則認為當前的 TCP 連接已經(jīng)死亡,系統(tǒng)內(nèi)核將錯誤信息通知給上層應(yīng)用程序。

          在 Linux 內(nèi)核可以有對應(yīng)的參數(shù)可以設(shè)置?;顣r間、?;钐綔y的次數(shù)、?;钐綔y的時間間隔,以下都為默認值:

          net.ipv4.tcp_keepalive_time=7200
          net.ipv4.tcp_keepalive_intvl=75??
          net.ipv4.tcp_keepalive_probes=9
          • tcp_keepalive_time=7200:表示保活時間是 7200 秒(2小時),也就 2 小時內(nèi)如果沒有任何連接相關(guān)的活動,則會啟動保活機制
          • tcp_keepalive_intvl=75:表示每次檢測間隔 75 秒;
          • tcp_keepalive_probes=9:表示檢測 9 次無響應(yīng),認為對方是不可達的,從而中斷本次的連接。

          也就是說在 Linux 系統(tǒng)中,最少需要經(jīng)過 2 小時 11 分 15 秒才可以發(fā)現(xiàn)一個「死亡」連接。

          這個時間是有點長的,我們也可以根據(jù)實際的需求,對以上的保活相關(guān)的參數(shù)進行設(shè)置。

          如果開啟了 TCP ?;?,需要考慮以下幾種情況:

          第一種,對端程序是正常工作的。當 TCP 保活的探測報文發(fā)送給對端, 對端會正常響應(yīng),這樣?TCP 保活時間會被重置,等待下一個 TCP ?;顣r間的到來。

          第二種,對端程序崩潰并重啟。當 TCP ?;畹奶綔y報文發(fā)送給對端后,對端是可以響應(yīng)的,但由于沒有該連接的有效信息,會產(chǎn)生一個 RST 報文,這樣很快就會發(fā)現(xiàn) TCP 連接已經(jīng)被重置。

          第三種,是對端程序崩潰,或?qū)Χ擞捎谄渌驅(qū)е聢笪牟豢蛇_。當 TCP ?;畹奶綔y報文發(fā)送給對端后,石沉大海,沒有響應(yīng),連續(xù)幾次,達到保活探測次數(shù)后,TCP 會報告該 TCP 連接已經(jīng)死亡。


          04 Socket 編程

          針對 TCP 應(yīng)該如何 Socket 編程?

          基于 TCP 協(xié)議的客戶端和服務(wù)器工作
          • 服務(wù)端和客戶端初始化?socket,得到文件描述符;
          • 服務(wù)端調(diào)用?bind,將綁定在 IP 地址和端口;
          • 服務(wù)端調(diào)用?listen,進行監(jiān)聽;
          • 服務(wù)端調(diào)用?accept,等待客戶端連接;
          • 客戶端調(diào)用?connect,向服務(wù)器端的地址和端口發(fā)起連接請求;
          • 服務(wù)端?accept?返回用于傳輸?shù)?socket?的文件描述符;
          • 客戶端調(diào)用?write?寫入數(shù)據(jù);服務(wù)端調(diào)用?read?讀取數(shù)據(jù);
          • 客戶端斷開連接時,會調(diào)用?close,那么服務(wù)端?read?讀取數(shù)據(jù)的時候,就會讀取到了?EOF,待處理完數(shù)據(jù)后,服務(wù)端調(diào)用?close,表示連接關(guān)閉。

          這里需要注意的是,服務(wù)端調(diào)用?accept?時,連接成功了會返回一個已完成連接的 socket,后續(xù)用來傳輸數(shù)據(jù)。

          所以,監(jiān)聽的 socket 和真正用來傳送數(shù)據(jù)的 socket,是「兩個」 socket,一個叫作監(jiān)聽 socket,一個叫作已完成連接 socket。

          成功連接建立之后,雙方開始通過 read 和 write 函數(shù)來讀寫數(shù)據(jù),就像往一個文件流里面寫東西一樣。

          listen 時候參數(shù) backlog 的意義?

          Linux內(nèi)核中會維護兩個隊列:

          • 未完成連接隊列(SYN 隊列):接收到一個 SYN 建立連接請求,處于 SYN_RCVD 狀態(tài);
          • 已完成連接隊列(Accpet 隊列):已完成 TCP 三次握手過程,處于 ESTABLISHED 狀態(tài);
          SYN 隊列 與 Accpet 隊列
          int?listen?(int?socketfd,?int?backlog)
          • 參數(shù)一 socketfd 為 socketfd 文件描述符
          • 參數(shù)二 backlog,這參數(shù)在歷史版本有一定的變化

          在早期 Linux 內(nèi)核 backlog 是 SYN 隊列大小,也就是未完成的隊列大小。

          在 Linux 內(nèi)核 2.2 之后,backlog 變成 accept 隊列,也就是已完成連接建立的隊列長度,所以現(xiàn)在通常認為 backlog 是 accept 隊列。

          但是上限值是內(nèi)核參數(shù) somaxconn 的大小,也就說 accpet 隊列長度 = min(backlog, somaxconn)。

          accept 發(fā)生在三次握手的哪一步?

          我們先看看客戶端連接服務(wù)端時,發(fā)送了什么?

          客戶端連接服務(wù)端
          • 客戶端的協(xié)議棧向服務(wù)器端發(fā)送了 SYN 包,并告訴服務(wù)器端當前發(fā)送序列號 client_isn,客戶端進入 SYN_SENT 狀態(tài);
          • 服務(wù)器端的協(xié)議棧收到這個包之后,和客戶端進行 ACK 應(yīng)答,應(yīng)答的值為 client_isn+1,表示對 SYN 包 client_isn 的確認,同時服務(wù)器也發(fā)送一個 SYN 包,告訴客戶端當前我的發(fā)送序列號為 server_isn,服務(wù)器端進入 SYN_RCVD 狀態(tài);
          • 客戶端協(xié)議棧收到 ACK 之后,使得應(yīng)用程序從?connect?調(diào)用返回,表示客戶端到服務(wù)器端的單向連接建立成功,客戶端的狀態(tài)為 ESTABLISHED,同時客戶端協(xié)議棧也會對服務(wù)器端的 SYN 包進行應(yīng)答,應(yīng)答數(shù)據(jù)為 server_isn+1;
          • 應(yīng)答包到達服務(wù)器端后,服務(wù)器端協(xié)議棧使得?accept?阻塞調(diào)用返回,這個時候服務(wù)器端到客戶端的單向連接也建立成功,服務(wù)器端也進入 ESTABLISHED 狀態(tài)。

          從上面的描述過程,我們可以得知客戶端 connect 成功返回是在第二次握手,服務(wù)端 accept 成功返回是在三次握手成功之后。

          客戶端調(diào)用 close 了,連接是斷開的流程是什么?

          我們看看客戶端主動調(diào)用了?close,會發(fā)生什么?

          客戶端調(diào)用 close 過程
          • 客戶端調(diào)用?close,表明客戶端沒有數(shù)據(jù)需要發(fā)送了,則此時會向服務(wù)端發(fā)送 FIN 報文,進入 FIN_WAIT_1 狀態(tài);
          • 服務(wù)端接收到了 FIN 報文,TCP 協(xié)議棧會為 FIN 包插入一個文件結(jié)束符?EOF?到接收緩沖區(qū)中,應(yīng)用程序可以通過?read?調(diào)用來感知這個 FIN 包。這個?EOF?會被放在已排隊等候的其他已接收的數(shù)據(jù)之后,這就意味著服務(wù)端需要處理這種異常情況,因為 EOF 表示在該連接上再無額外數(shù)據(jù)到達。此時,服務(wù)端進入 CLOSE_WAIT 狀態(tài);
          • 接著,當處理完數(shù)據(jù)后,自然就會讀到?EOF,于是也調(diào)用?close?關(guān)閉它的套接字,這會使得服務(wù)端發(fā)出一個 FIN 包,之后處于 LAST_ACK 狀態(tài);
          • 客戶端接收到服務(wù)端的 FIN 包,并發(fā)送 ACK 確認包給服務(wù)端,此時客戶端將進入 TIME_WAIT 狀態(tài);
          • 服務(wù)端收到 ACK 確認包后,就進入了最后的 CLOSE 狀態(tài);
          • 客戶端經(jīng)過?2MSL?時間之后,也進入 CLOSE 狀態(tài);

          嘮叨

          小林為寫此文重學(xué)了一邊 TCP,深感 TCP 真的是一個非常復(fù)雜的協(xié)議,要想輕易拿下,也不是一天兩天的事,所以小林花費了一個星期多才寫完此文章。

          正所謂知道的越多,不知道的也越多。


          瀏覽 29
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久久偷拍 | 老鸭窝在线视频狂综合 | 色秘 乱码一区二区三区男奴-百度 | 美国 日本 韩国三级三级三级黄色A在线播放 | 91欧美在线视频 |