前端須要瞭解的TCP,UDP,IP

TCP

什麼是 TCP ?

TCP 是面向鏈接的、可靠的、基於字節流的傳輸層通訊協議。javascript

  • 面向鏈接:必定是「一對一」才能鏈接,不能像 UDP 協議能夠一個主機同時向多個主機發送消息,也就是一對可能是沒法作到的;
  • 可靠的:不管網絡鏈路中出現了怎樣的鏈路變化,TCP 均可以保證一個報文必定可以到達接收端;
  • 字節流:消息是「沒有邊界」的,因此不管咱們消息有多大均可以進行傳輸。而且消息是「有序的」,當「前一個」消息沒有收到的時候,即便它先收到了後面的字節,那麼也不能扔給應用層去處理,同時對「重複」的報文會自動丟棄。
什麼是 TCP 鏈接?

簡單來講就是,用於保證可靠性和流量控制維護的某些狀態信息,這些信息的組合,包括Socket、序列號和窗口大小稱爲鏈接。html

因此咱們能夠知道,創建一個 TCP 鏈接是須要客戶端與服務器端達成上述三個信息的共識。java

  • Socket:由 IP 地址和端口號組成
  • 序列號:用來解決亂序問題等
  • 窗口大小:用來作流量控制
如何肯定惟一的一個 TCP 鏈接呢?

TCP 四元組能夠肯定惟一的一個鏈接,四元組包括以下:linux

  • 源地址
  • 源端口
  • 目標地址
  • 目標端口

源地址和目標地址的字段(32位)是在 IP 頭部中,做用是經過 IP 協議發送報文給對方主機。面試

源端口和目標端口的字段(16位)是在 TCP 頭部中,做用是告訴 TCP 協議應該把報文發給哪一個進程。算法

TCP頭格式

字段名稱 長度(比特) 含 義
源端口號 16 發送網絡包的程序的端口號
目標端口號 16 接收網絡包的程序的端口號
序號(Sequence Number 32 Sequence Number是包的序號,用來解決網絡包亂序(reordering)問題。
確認序號(Acknowledgement Number 32 Acknowledgement Number就是ACK——用於確認收到,用來解決不丟包的問題
首部長度 4 表示數據部分的起始位置,也能夠認爲表示頭部的長度
保留 6 該字段爲保留,如今未使用
控制位 6 該字段中的每一個比特分別表示如下通訊控制含義。<br/>URG:爲1表示高優先級數據包,緊急指針字段有效。<br/>ACK:爲1表示確認序號字段有效,通常表示數據已被接收方收到。<br/>PSH:爲1表示是帶有PUSH標誌的數據,指示接收方應該儘快將這個報文段交給應用層而不用等待緩衝區裝滿。<br/>RST:爲1表示出現嚴重差錯。可能須要重現建立TCP鏈接。還能夠用於拒絕非法的報文段和拒絕鏈接請求。<br/>SYN:爲1表示這是鏈接請求或是鏈接接受請求,用於建立鏈接和使順序號同步。<br/>FIN:爲1表示發送方沒有數據要傳輸了,要求釋放鏈接。
窗口大小 16 接收方告知發送方窗口大小(即無需等待確承認一塊兒發送的數據量)
校驗和 16 用來檢查是否出現錯誤
緊急指針 16 表示應緊急處理的數據位置
選項 可變長度 除了上面的固定頭部字段以外,還能夠添加可選字段,但除了鏈接操做以外,不多使用可選字段

TCP 三次握手和四次揮手

三次握手

  1. 第一個報文—— SYN 報文shell

    • 客戶端會隨機初始化序號(client_isn,在這裏是seq = x),將此序號置於 TCP 頭字段的「序號」字段中,同時把 SYN 標誌位置爲 1 ,表示 SYN 報文。接着把第一個 SYN 報文發送給服務端,表示向服務端發起鏈接,該報文不包含應用層數據,以後客戶端處於 SYN-SENT 狀態。
  2. 第二個報文 —— SYN + ACK 報文瀏覽器

    • 服務端收到客戶端的 SYN 報文後,首先服務端也隨機初始化本身的序號(server_isn,在這裏是seq = y),將此序號填入 TCP 頭字段的「序號」字段中,其次把 TCP 頭字段的「確認序號」字段填入 client_isn + 1, 接着把 SYNACK 標誌位置爲 1。最後把該報文發給客戶端,該報文也不包含應用層數據,以後服務端處於 SYN-RCVD 狀態。
  3. 第三個報文 —— ACK 報文緩存

    • 客戶端收到服務端報文後,還要向服務端迴應最後一個應答報文,首先該應答報文 TCP 頭字段 ACK 標誌位置爲 1 ,其次「確認序號」字段填入 server_isn + 1 ,最後把報文發送給服務端,此次報文能夠攜帶客戶到服務器的數據,以後客戶端處於 ESTABLISHED 狀態。
    • 服務器收到客戶端的應答報文後,也進入 ESTABLISHED 狀態。

注意:安全

咱們看到有兩個中間狀態,syn_sentsyn_rcvd,這兩個狀態叫着「半打開」狀態,就是向對方發送了,可是還沒來得及看到對方的迴應。

syn_sent 是主動打開方的「半打開」狀態,syn_rcvd 是被動打開方的「半打開」狀態。客戶端是主動打開方,服務器是被動打開方。

  • syn_sent: syn package has been sent 已發送 syn 包
  • syn_rcvd: syn package has been received 已收到 syn 包

四次揮手

  • 客戶端打算關閉鏈接,此時會發送一個 TCP 頭字段 FIN 標誌位被置爲 1 的報文,也即 FIN 報文,以後客戶端進入 FIN_WAIT_1 狀態。
  • 服務端收到該報文後,就向客戶端發送 ACK 應答報文,接着服務端進入 CLOSED_WAIT 狀態。
  • 客戶端收到服務端的 ACK 應答報文後,以後進入 FIN_WAIT_2 狀態。
  • 等待服務端處理完數據後,也向客戶端發送 FIN 報文,以後服務端進入 LAST_ACK 狀態。
  • 客戶端收到服務端的 FIN 報文後,回一個 ACK 應答報文,以後進入 TIME_WAIT 狀態。
  • 服務器收到了 ACK 應答報文後,就進入了 CLOSED 狀態,至此服務端已經完成鏈接的關閉。
  • 客戶端在通過 2MSL(4分鐘)時間後,自動進入 CLOSED 狀態,至此客戶端也完成鏈接的關閉。

你能夠看到,每一個方向都須要一個 FIN 和一個 ACK,所以一般被稱爲四次揮手

這裏一點須要注意是:主動關閉鏈接的,纔有 TIME_WAIT 狀態。

四次揮手也並不老是四次揮手,中間的兩個動做有時候是能夠合併一塊兒進行的,這個時候就成了三次揮手,主動關閉方就會從fin_wait_1狀態直接進入到time_wait狀態,跳過了fin_wait_2狀態。

數據傳輸

重傳機制

超時重傳

重傳機制的一種方式,就是在發送數據時,設定一個定時器,當超過指定的時間後,沒有收到對方的 ACK 確認應答報文,就會重發該數據。

TCP 會在如下兩種狀況發生超時重傳:

  • 數據包丟失
  • 確認應答(ACK)丟失

快速重傳

TCP 還有另一種快速重傳(Fast Retransmit)機制,它不以時間爲驅動,而是以數據驅動重傳。

在上圖,發送方發出了 1,2,3,4,5 份數據:

  • 第一份 Seq1 先送到了,因而就 Ack 回 2;
  • 結果 Seq2 由於某些緣由沒收到,Seq3 到達了,因而仍是 Ack 回 2;
  • 後面的 Seq4 和 Seq5 都到了,但仍是 Ack 回 2,由於 Seq2 仍是沒有收到;
  • 發送端收到了三個 Ack = 2 的確認,知道了 Seq2 尚未收到,就會在定時器過時以前,重傳丟失的 Seq2。
  • 最後,收到了 Seq2,此時由於 Seq3,Seq4,Seq5 都收到了,因而 Ack 回 6 。

快速重傳機制只解決了一個問題,就是超時時間的問題,可是它依然面臨着另一個問題。就是重傳的時候,是重傳以前的一個,仍是重傳全部的問題。

SACK 方法

SACK( Selective Acknowledgment 選擇性確認),這種方式須要在 TCP 頭部「選項」字段里加一個 SACK ,它能夠將緩存的地圖發送給發送方,這樣發送方就能夠知道哪些數據收到了,哪些數據沒收到,知道了這些信息,就能夠只重傳丟失的數據

以下圖,發送方收到了三次一樣的 ACK 確認報文,因而就會觸發快速重發機制,經過 SACK 信息發現只有 200~299 這段數據丟失,則重發時,就只選擇了這個 TCP 段進行重複。

Duplicate SACK

Duplicate SACK 又稱 D-SACK,其主要使用了 SACK 來告訴「發送方」有哪些數據被重複接收了。

  1. ACK 丟包

  • 「接收方」發給「發送方」的兩個 ACK 確認應答都丟失了,因此發送方超時後,重傳第一個數據包(3000 ~ 3499)
  • 因而「接收方」發現數據是重複收到的,因而回了一個 SACK = 3000~3500,告訴「發送方」 3000~3500 的數據早已被接收了,由於 ACK 都到了 4000 了,已經意味着 4000 以前的全部數據都已收到,因此這個 SACK 就表明着 D-SACK
  • 這樣「發送方」就知道了,數據沒有丟,是「接收方」的 ACK 確認報文丟了。
  1. 網絡延時

可見,D-SACK 有這麼幾個好處:

  1. 可讓「發送方」知道,是發出去的包丟了,仍是接收方迴應的 ACK 包丟了;
  2. 能夠知道是否是「發送方」的數據包被網絡延遲了;
  3. 能夠知道網絡中是否是把「發送方」的數據包給複製了。

滑動窗口

爲解決這個問題,TCP 引入了窗口這個概念。即便在往返時間較長的狀況下,它也不會下降網絡通訊的效率。

那麼有了窗口,就能夠指定窗口大小,窗口大小就是指無需等待確認應答,而能夠繼續發送數據的最大值

窗口的實現其實是操做系統開闢的一個緩存空間,發送方主機在等到確認應答返回以前,必須在緩衝區中保留已發送的數據。若是定期收到確認應答,此時數據就能夠從緩存區清除。

假設窗口大小爲 3 個 TCP 段,那麼發送方就能夠「連續發送」 3 個 TCP 段,而且中途如有 ACK 丟失,能夠經過「下一個確認應答進行確認」。以下圖:

只要發送方收到了 ACK 700 確認應答,就意味着 700 以前的全部數據「接收方」都收到了。這個模式就叫累計確認或者累計應答

窗口大小由哪一方決定?

TCP 頭裏有一個字段叫 Window,也就是窗口大小。

這個字段是接收端告訴發送端本身還有多少緩衝區能夠接收數據。因而發送端就能夠根據這個接收端的處理能力來發送數據,而不會致使接收端處理不過來。

因此,一般窗口的大小是由接收方的窗口大小來決定的。

發送方發送的數據大小不能超過接收方的窗口大小,不然接收方就沒法正常接收到數據。

發送方的滑動窗口

咱們先來看看發送方的窗口,下圖就是發送方緩存的數據,根據處理的狀況分紅四個部分,其中深藍色方框是發送窗口,紫色方框是可用窗口:

  • \#1 是已發送並收到 ACK確認的數據:1~31 字節;
  • \#2 是已發送但未收到 ACK確認的數據:32~45 字節;
  • \#3 是未發送但總大小在接收方處理範圍內(接收方還有空間):46~51字節;
  • \#4 是未發送但總大小超過接收方處理範圍(接收方沒有空間):52字節之後。

在下圖,當發送方把數據「所有」都一下發送出去後,可用窗口的大小就爲 0 了,代表可用窗口耗盡,在沒收到 ACK 確認以前是沒法繼續發送數據了。

在下圖,當收到以前發送的數據 32~36 字節的 ACK 確認應答後,若是發送窗口的大小沒有變化,則滑動窗口往右邊移動 5 個字節,由於有 5 個字節的數據被應答確認,接下來 52~56 字節又變成了可用窗口,那麼後續也就能夠發送 52~56 這 5 個字節的數據了。

程序是如何表示發送方的四個部分的呢?

TCP 滑動窗口方案使用三個指針來跟蹤在四個傳輸類別中的每個類別中的字節。其中兩個指針是絕對指針(指特定的序列號),一個是相對指針(須要作偏移)。

  • SND.WND:表示發送窗口的大小(大小是由接收方指定的);
  • SND.UNA:是一個絕對指針,它指向的是已發送但未收到確認的第一個字節的序列號,也就是 #2 的第一個字節;
  • SND.NXT:也是一個絕對指針,它指向未發送但可發送範圍的第一個字節的序列號,也就是 #3 的第一個字節;
  • 指向 #4 的第一個字節是個相對指針,它須要 SND.UNA 指針加上 SND.WND 大小的偏移量,就能夠指向 #4 的第一個字節了。

那麼可用窗口大小的計算就能夠是:

可用窗口大 = SND.WND -(SND.NXT - SND.UNA)

接收方的滑動窗口

接下來咱們看看接收方的窗口,接收窗口相對簡單一些,根據處理的狀況劃分紅三個部分:

  • \#1 + #2 是已成功接收並確認的數據(等待應用進程讀取);
  • \#3 是未收到數據但能夠接收的數據;
  • \#4 未收到數據並不能夠接收的數據。

其中三個接收部分,使用兩個指針進行劃分:

  • RCV.WND:表示接收窗口的大小,它會通告給發送方。
  • RCV.NXT:是一個指針,它指向指望從發送方發送來的下一個數據字節的序列號,也就是 #3 的第一個字節。
  • 指向 #4 的第一個字節是個相對指針,它須要 RCV.NXT 指針加上 RCV.WND 大小的偏移量,就能夠指向 #4 的第一個字節了。
接收窗口和發送窗口的大小是相等的嗎?

並非徹底相等,接收窗口的大小是約等於發送窗口的大小的。

由於滑動窗口並非一成不變的。好比,當接收方的應用進程讀取數據的速度很是快的話,這樣的話接收窗口能夠很快的就空缺出來。那麼新的接收窗口大小,是經過 TCP 報文中的 Windows 字段來告訴發送方。那麼這個傳輸過程是存在時延的,因此接收窗口和發送窗口是約等於的關係。

流量控制

發送方不能無腦的發數據給接收方,要考慮接收方處理能力。

若是一直無腦的發數據給對方,但對方處理不過來,那麼就會致使觸發重發機制,從而致使網絡流量的無故的浪費。

爲了解決這種現象發生,TCP 提供一種機制可讓「發送方」根據「接收方」的實際接收能力控制發送的數據量,這就是所謂的流量控制。

根據上圖的流量控制,說明下每一個過程:

  1. 客戶端向服務端發送請求數據報文。這裏要說明下,本次例子是把服務端做爲發送方,因此沒有畫出服務端的接收窗口。
  2. 服務端收到請求報文後,發送確認報文和 80 字節的數據,因而可用窗口 Usable 減小爲 120 字節,同時 SND.NXT 指針也向右偏移 80 字節後,指向 321,這意味着下次發送數據的時候,序列號是 321。
  3. 客戶端收到 80 字節數據後,因而接收窗口往右移動 80 字節,RCV.NXT 也就指向 321,這意味着客戶端指望的下一個報文的序列號是 321,接着發送確認報文給服務端。
  4. 服務端再次發送了 120 字節數據,因而可用窗口耗盡爲 0,服務端沒法再繼續發送數據。
  5. 客戶端收到 120 字節的數據後,因而接收窗口往右移動 120 字節,RCV.NXT 也就指向 441,接着發送確認報文給服務端。
  6. 服務端收到對 80 字節數據的確認報文後,SND.UNA 指針往右偏移後指向 321,因而可用窗口 Usable 增大到 80。
  7. 服務端收到對 120 字節數據的確認報文後,SND.UNA 指針往右偏移後指向 441,因而可用窗口 Usable 增大到 200。
  8. 服務端能夠繼續發送了,因而發送了 160 字節的數據後,SND.NXT 指向 601,因而可用窗口 Usable 減小到 40。
  9. 客戶端收到 160 字節後,接收窗口往右移動了 160 字節,RCV.NXT 也就是指向了 601,接着發送確認報文給服務端。
  10. 服務端收到對 160 字節數據的確認報文後,發送窗口往右移動了 160 字節,因而 SND.UNA 指針偏移了 160 後指向 601,可用窗口 Usable 也就增大至了 200。
擁塞控制
爲何要有擁塞控制呀,不是有流量控制了嗎?

前面的流量控制是避免「發送方」的數據填滿「接收方」的緩存,可是並不知道網絡的中發生了什麼。

通常來講,計算機網絡都處在一個共享的環境。所以也有可能會由於其餘主機之間的通訊使得網絡擁堵。

在網絡出現擁堵時,若是繼續發送大量數據包,可能會致使數據包時延、丟失等,這時 TCP 就會重傳數據,可是一重傳就會致使網絡的負擔更重,因而會致使更大的延遲以及更多的丟包,這個狀況就會進入惡性循環被不斷地放大….

因此,TCP 不能忽略網絡上發生的事,它被設計成一個無私的協議,當網絡發送擁塞時,TCP 會自我犧牲,下降發送的數據量。

因而,就有了擁塞控制,控制的目的就是避免「發送方」的數據填滿整個網絡。

爲了在「發送方」調節所要發送數據的量,定義了一個叫作「擁塞窗口」的概念。

什麼是擁塞窗口?和發送窗口有什麼關係呢?

擁塞窗口 cwnd是發送方維護的一個的狀態變量,它會根據網絡的擁塞程度動態變化的

咱們在前面提到過發送窗口 swnd 和接收窗口 rwnd 是約等於的關係,那麼因爲加入了擁塞窗口的概念後,此時發送窗口的值是swnd = min(cwnd, rwnd),也就是擁塞窗口和接收窗口中的最小值。

擁塞窗口 cwnd 變化的規則:

  • 只要網絡中沒有出現擁塞,cwnd 就會增大;
  • 但網絡中出現了擁塞,cwnd 就減小;
那麼怎麼知道當前網絡是否出現了擁塞呢?

其實只要「發送方」沒有在規定時間內接收到 ACK 應答報文,也就是發生了超時重傳,就會認爲網絡出現了用擁塞。

擁塞控制有哪些控制算法?

擁塞控制主要是四個算法:

  • 慢啓動
  • 擁塞避免
  • 擁塞發生
  • 快速恢復

慢啓動

TCP 在剛創建鏈接完成後,首先是有個慢啓動的過程,這個慢啓動的意思就是一點一點的提升發送數據包的數量,若是一上來就發大量的數據,這不是給網絡添堵嗎?

慢啓動的算法記住一個規則就行:當發送方每收到一個 ACK,擁塞窗口 cwnd 的大小就會加 1。

這裏假定擁塞窗口 cwnd 和發送窗口 swnd 相等,下面舉個栗子:

  • 鏈接創建完成後,一開始初始化 cwnd = 1,表示能夠傳一個 MSS 大小的數據。
  • 當收到一個 ACK 確認應答後,cwnd 增長 1,因而一次可以發送 2 個
  • 當收到 2 個的 ACK 確認應答後, cwnd 增長 2,因而就能夠比以前多發2 個,因此這一次可以發送 4 個
  • 當這 4 個的 ACK 確認到來的時候,每一個確認 cwnd 增長 1, 4 個確認 cwnd 增長 4,因而就能夠比以前多發 4 個,因此這一次可以發送 8 個。

能夠看出慢啓動算法,發包的個數是指數性的增加

那慢啓動漲到何時是個頭呢?

有一個叫慢啓動門限 ssthresh (slow start threshold)狀態變量。

  • cwnd < ssthresh 時,使用慢啓動算法。
  • cwnd >= ssthresh 時,就會使用「擁塞避免算法」。

擁塞避免

前面說道,當擁塞窗口 cwnd 「超過」慢啓動門限 ssthresh 就會進入擁塞避免算法。

通常來講 ssthresh 的大小是 65535 字節。

那麼進入擁塞避免算法後,它的規則是:每當收到一個 ACK 時,cwnd 增長 1/cwnd。

接上前面的慢啓動的栗子,現假定 ssthresh8

  • 當 8 個 ACK 應答確認到來時,每一個確認增長 1/8,8 個 ACK 確認 cwnd 一共增長 1,因而這一次可以發送 9 個 MSS 大小的數據,變成了線性增加。

因此,咱們能夠發現,擁塞避免算法就是將本來慢啓動算法的指數增加變成了線性增加,仍是增加階段,可是增加速度緩慢了一些。

就這麼一直增加着後,網絡就會慢慢進入了擁塞的情況了,因而就會出現丟包現象,這時就須要對丟失的數據包進行重傳。

當觸發了重傳機制,也就進入了「擁塞發生算法」。

擁塞發生

當網絡出現擁塞,也就是會發生數據包重傳,重傳機制主要有兩種:

  • 超時重傳
  • 快速重傳

這兩種使用的擁塞發送算法是不一樣的,接下來分別來講說。

發生超時重傳的擁塞發生算法

這個時候,ssthresh 和 cwnd 的值會發生變化:

  • ssthresh 設爲 cwnd/2
  • cwnd 重置爲 1

接着,就從新開始慢啓動,慢啓動是會忽然減小數據流的。這真是一旦「超時重傳」,立刻回到解放前。可是這種方式太激進了,反應也很強烈,會形成網絡卡頓。

發生快速重傳的擁塞發生算法

還有更好的方式,前面咱們講過「快速重傳算法」。當接收方發現丟了一箇中間包的時候,發送三次前一個包的 ACK,因而發送端就會快速地重傳,沒必要等待超時再重傳。

TCP 認爲這種狀況不嚴重,由於大部分沒丟,只丟了一小部分,則 ssthreshcwnd 變化以下:

  • cwnd = cwnd/2 ,也就是設置爲原來的一半;
  • ssthresh = cwnd;
  • 進入快速恢復算法

快速恢復

快速重傳和快速恢復算法通常同時使用,快速恢復算法是認爲,你還能收到 3 個重複 ACK 說明網絡也不那麼糟糕,因此沒有必要像 RTO 超時那麼強烈。

正如前面所說,進入快速恢復以前,cwndssthresh 已被更新了:

  • cwnd = cwnd/2 ,也就是設置爲原來的一半;
  • ssthresh = cwnd;

而後,進入快速恢復算法以下:

  • 擁塞窗口 cwnd = ssthresh + 3 ( 3 的意思是確認有 3 個數據包被收到了);
  • 重傳丟失的數據包;
  • 若是再收到重複的 ACK,那麼 cwnd 增長 1;
  • 若是收到新數據的 ACK 後,把 cwnd 設置爲第一步中的 ssthresh 的值,緣由是該 ACK 確認了新的數據,說明從 duplicated ACK 時的數據都已收到,該恢復過程已經結束,能夠回到恢復以前的狀態了,也即再次進入擁塞避免狀態。

也就是沒有像「超時重傳」一晚上回到解放前,而是還在比較高的值,後續呈線性增加。

擁塞算法示意圖

握手相關問題

爲何是三次握手,不是兩次?不是四次?

緣由一:避免歷史鏈接

簡單來講,三次握手的 首要緣由是爲了防止舊的重複鏈接初始化形成混亂。

  • 客戶端連續發送屢次 SYN 創建鏈接的報文,在網絡擁堵狀況下:

    • 一個「舊 SYN 報文」比「最新的 SYN 」 報文早到達了服務端;
    • 那麼此時服務端就會回一個 SYN + ACK 報文給客戶端;
    • 客戶端收到後能夠根據自身的上下文,判斷這是一個歷史鏈接(序列號過時或超時),那麼客戶端就會發送 RST 報文給服務端,表示停止這一次鏈接。

    若是是兩次握手鍊接,就不能判斷當前鏈接是不是歷史鏈接,三次握手則能夠在客戶端(發送方)準備發送第三次報文時,客戶端因有足夠的上下文來判斷當前鏈接是不是歷史鏈接:

    • 若是是歷史鏈接(序列號過時或超時),則第三次握手發送的報文是 RST 報文,以此停止歷史鏈接;
    • 若是不是歷史鏈接,則第三次發送的報文是 ACK 報文,通訊雙方就會成功創建鏈接;

    因此,TCP 使用三次握手創建鏈接的最主要緣由是防止歷史鏈接初始化了鏈接。

緣由二:同步雙方初始序號

TCP 協議的通訊雙方, 都必須維護一個「序號」, 序列號是可靠傳輸的一個關鍵因素,它的做用:

  • 接收方能夠去除重複的數據;
  • 接收方能夠根據數據包的序號按序接收;
  • 能夠標識發送出去的數據包中, 哪些是已經被對方收到的。

可見,序號在 TCP 鏈接中佔據着很是重要的做用,因此當客戶端發送攜帶「初始序號」的 SYN 報文的時候,須要服務端回一個 ACK 應答報文,表示客戶端的 SYN 報文已被服務端成功接收,那當服務端發送「初始序號」給客戶端的時候,依然也要獲得客戶端的應答迴應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。

四次握手其實也可以可靠的同步雙方的初始化序號,但因爲第二步和第三步能夠優化成一步,因此就成了「三次握手」。

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

緣由三:避免資源浪費

若是隻有「兩次握手」,當客戶端的 SYN 請求鏈接在網絡中阻塞,客戶端沒有接收到 ACK 報文,就會從新發送 SYN ,因爲沒有第三次握手,服務器不清楚客戶端是否收到了本身發送的創建鏈接的 ACK 確認信號,因此每收到一個 SYN 就只能先主動創建一個鏈接,這會形成什麼狀況呢?

若是客戶端的 SYN 阻塞了,重複發送屢次 SYN 報文,那麼服務器在收到請求後就會創建多個冗餘的無效連接,形成沒必要要的資源浪費。

即兩次握手會形成消息滯留狀況下,服務器重複接受無用的鏈接請求 SYN 報文,而形成重複分配資源。

小結

TCP 創建鏈接時,經過三次握手能防止歷史鏈接的創建,能減小雙方沒必要要的資源開銷,能幫助雙方同步初始化序列號。序列號可以保證數據包不重複、不丟棄和按序傳輸。

不使用「兩次握手」和「四次握手」的緣由:

  • 「兩次握手」:沒法防止歷史鏈接的創建,會形成雙方資源的浪費,也沒法可靠的同步雙方序列號;
  • 「四次握手」:三次握手就已經理論上最少可靠鏈接創建,因此不須要使用更多的通訊次數。
什麼是 SYN 攻擊?如何避免 SYN 攻擊?

SYN 攻擊

咱們都知道 TCP 鏈接創建是須要三次握手,假設攻擊者短期僞造不一樣 IP 地址的 SYN 報文,服務端每接收到一個 SYN 報文,就進入SYN_RCVD 狀態,但服務端發送出去的 ACK + SYN 報文,沒法獲得未知 IP 主機的 ACK 應答,長此以往就會佔滿服務端的 SYN 接收隊列(未鏈接隊列),使得服務器不能爲正經常使用戶服務。

避免 SYN 攻擊方式一

其中一種解決方式是經過修改 Linux 內核參數,控制隊列大小和當隊列滿時應作什麼處理。

  • 當網卡接收數據包的速度大於內核處理的速度時,會有一個隊列保存這些數據包。控制該隊列的最大值以下參數:

    net.core.netdev_max_backlog
  • SYN_RCVD 狀態鏈接的最大個數:

    net.ipv4.tcp_max_syn_backlog
  • 超出處理能時,對新的 SYN 直接回報 RST,丟棄鏈接:

    net.ipv4.tcp_abort_on_overflow

避免 SYN 攻擊方式二

咱們先來看下 Linux 內核的 SYN (未完成鏈接創建)隊列與 Accpet (已完成鏈接創建)隊列是如何工做的?

正常流程:

  • 當服務端接收到客戶端的 SYN 報文時,會將其加入到內核的「 SYN 隊列」;
  • 接着發送 SYN + ACK 給客戶端,等待客戶端迴應 ACK 報文;
  • 服務端接收到 ACK 報文後,從「 SYN 隊列」移除放入到「 Accept 隊列」;
  • 應用經過調用 accpet() socket 接口,從「 Accept 隊列」取出鏈接。

應用程序過慢:

  • 若是應用程序過慢時,就會致使「 Accept 隊列」被佔滿。

受到 SYN 攻擊:

  • 若是不斷受到 SYN 攻擊,就會致使「 SYN 隊列」被佔滿。

tcp_syncookies 的方式能夠應對 SYN 攻擊的方法:

net.ipv4.tcp_syncookies = 1

tcp_syncookies 應對 SYN 攻擊

  • 當 「 SYN 隊列」滿以後,後續服務器收到 SYN 包,不進入「 SYN 隊列」;
  • 計算出一個 cookie 值,再以 SYN + ACK 中的「序列號」返回客戶端;
  • 服務端接收到客戶端的應答報文時,服務器會檢查這個 ACK 包的合法性。若是合法,直接放入到「 Accept 隊列」;
  • 最後應用經過調用 accpet() socket 接口,從「 Accept 隊列」取出的鏈接。

揮手相關問題

爲何揮手須要四次?
  • 關閉鏈接時,客戶端向服務端發送 FIN 時,僅僅表示客戶端再也不發送數據了可是還能接收數據。
  • 服務器收到客戶端的 FIN 報文時,先回一個 ACK 應答報文,而服務端可能還有數據須要處理和發送,等服務端再也不發送數據時,才發送 FIN 報文給客戶端來表示贊成如今關閉鏈接。

從上面過程可知,服務端一般須要等待完成數據的發送和處理,因此服務端的 ACKFIN 通常都會分開發送,從而比三次握手致使多了一次。

爲何 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 狀態,主要是兩個緣由:

  • 防止具備相同「四元組」的「舊」數據包被收到;
  • 保證「被動關閉鏈接」的一方能被正確的關閉,即保證最後的 ACK 能讓被動關閉方接收,從而幫助其正常關閉;

緣由一:防止舊鏈接的數據包

接收到歷史數據的異常

  • 如上圖黃色框框服務端在關閉鏈接以前發送的 SEQ = 301 報文,被網絡延遲了。
  • 這時有相同端口的 TCP 鏈接被複用後,被延遲的 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 太短或沒有,則就直接進入了 CLOSED 狀態了,那麼服務端則會一直處在 LASE_ACK 狀態。
  • 當客戶端發起創建鏈接的 SYN 請求報文後,服務端會發送 RST 報文給客戶端,鏈接創建的過程就會被終止。

若是 TIME-WAIT 等待足夠長的狀況就會遇到兩種狀況:

  • 服務端正常收到四次揮手的最後一個 ACK 報文,則服務端正常關閉鏈接。
  • 服務端沒有收到四次揮手的最後一個 ACK 報文時,則會重發 FIN 關閉鏈接報文並等待新的 ACK 報文。

因此客戶端在 TIME-WAIT 狀態等待 2MSL 時間後,就能夠保證雙方的鏈接均可以正常的關閉。

若是已經創建了鏈接,可是客戶端忽然出現故障了怎麼辦?

TCP 有一個機制是保活機制。這個機制的原理是這樣的:

定義一個時間段,在這個時間段內,若是沒有任何鏈接相關的活動,TCP 保活機制會開始做用,每隔一個時間間隔,發送一個探測報文,該探測報文包含的數據很是少,若是連續幾個探測報文都沒有獲得響應,則認爲當前的 TCP 鏈接已經死亡,系統內核將錯誤信息通知給上層應用程序。

在 Linux 內核能夠有對應的參數能夠設置保活時間、保活探測的次數、保活探測的時間間隔,如下都爲默認值:

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 小時內若是沒有任何鏈接相關的活動,則會啓動保活機制
  • tcp_keepalive_intvl=75:表示每次檢測間隔 75 秒;
  • tcp_keepalive_probes=9:表示檢測 9 次無響應,認爲對方是不可達的,從而中斷本次的鏈接。

也就是說在 Linux 系統中,最少須要通過 2 小時 11 分 15 秒才能夠發現一個「死亡」鏈接。

這個時間是有點長的,咱們也能夠根據實際的需求,對以上的保活相關的參數進行設置。

若是開啓了 TCP 保活,須要考慮如下幾種狀況:

第一種,對端程序是正常工做的。當 TCP 保活的探測報文發送給對端, 對端會正常響應,這樣 TCP 保活時間會被重置,等待下一個 TCP 保活時間的到來。

第二種,對端程序崩潰並重啓。當 TCP 保活的探測報文發送給對端後,對端是能夠響應的,但因爲沒有該鏈接的有效信息,會產生一個 RST 報文,這樣很快就會發現 TCP 鏈接已經被重置。

第三種,是對端程序崩潰,或對端因爲其餘緣由致使報文不可達。當 TCP 保活的探測報文發送給對端後,石沉大海,沒有響應,連續幾回,達到保活探測次數後,TCP 會報告該 TCP 鏈接已經死亡

UDP

UDP頭部格式

UDP 頭部格式

  • 目標和源端口:主要是告訴 UDP 協議應該把報文發給哪一個進程。
  • 包長度:該字段保存了 UDP 首部的長度跟數據的長度之和。
  • 校驗和:校驗和是爲了提供可靠的 UDP 首部和數據而設計。
UDP 和 TCP 有什麼區別呢?

1. 鏈接

  • TCP 是面向鏈接的傳輸層協議,傳輸數據前先要創建鏈接。
  • UDP 是不須要鏈接,即刻傳輸數據。

2. 服務對象

  • TCP 是一對一的兩點服務,即一條鏈接只有兩個端點。
  • UDP 支持一對1、一對多、多對多的交互通訊。

3. 可靠性

  • TCP 是可靠交付數據的,數據能夠無差錯、不丟失、不重複、按需到達。
  • UDP 是盡最大努力交付,不保證可靠交付數據。

4. 擁塞控制、流量控制

  • TCP 有擁塞控制和流量控制機制,保證數據傳輸的安全性。
  • UDP 則沒有,即便網絡很是擁堵了,也不會影響 UDP 的發送速率。

5. 首部開銷

  • TCP 首部長度較長,會有必定的開銷,首部在沒有使用「選項」字段時是 20 個字節,若是使用了「選項」字段則會變長的。
  • UDP 首部只有 8 個字節,而且是固定不變的,開銷較小。

6. 傳輸方式

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

7. 分片不一樣

  • TCP 的數據大小若是大於 MSS 大小,則會在傳輸層進行分片,目標主機收到後,也一樣在傳輸層組裝 TCP 數據包,若是中途丟失了一個分片,只須要傳輸丟失的這個分片。
  • UDP 的數據大小若是大於 MTU 大小,則會在 IP 層進行分片,目標主機收到後,在 IP 層組裝完數據,接着再傳給傳輸層,可是若是中途丟了一個分片,則就須要重傳全部的數據包,這樣傳輸效率很是差,因此一般 UDP 的報文應該小於 MTU。
UDP 和 TCP 應用場景

因爲 TCP 是面向鏈接,能保證數據的可靠性交付,所以常常用於:

  • FTP 文件傳輸
  • HTTP / HTTPS

因爲 UDP 面向無鏈接,它能夠隨時發送數據,再加上UDP自己的處理既簡單又高效,所以常常用於:

  • 包總量較少的通訊,如 DNSSNMP
  • 視頻、音頻等多媒體通訊
  • 廣播通訊

IP

IP的做用

IP 在 TCP/IP 參考模型中處於第三層,也就是網絡層

網絡層的主要做用是:實現主機與主機之間的通訊,也叫點對點(end to end)通訊。

IPv4表示

IP 地址(IPv4 地址)由 32 位正整數來表示,IP 地址在計算機是以二進制的方式處理的。

而人類爲了方便記憶採用了點分十進制的標記方式,也就是將 32 位 IP 地址以每 8 位爲組,共分爲 4 組,每組以「.」隔開,再將每組轉換成十進制。

IP 分類

互聯網誕生之初,IP 地址顯得很充裕,因而計算機科學家們設計了分類地址

IP 地址分類成了 5 種類型,分別是 A 類、B 類、C 類、D 類、E 類。

上圖中黃色部分爲分類號,用以區分 IP 地址類別。

什麼是 A、B、C 類地址?

其中對於 A、B、C 類主要分爲兩個部分,分別是網絡號和主機號

A、B、C 分類地址最大主機個數是如何計算的呢?

最大主機個數,就是要看主機號的位數,如 C 類地址的主機號佔 8 位,那麼 C 類地址的最大主機個數:

爲何要減 2 呢?

由於在 IP 地址中,有兩個 IP 是特殊的,分別是主機號全爲 1 和 全爲 0 地址。

  • 主機號全爲 1 指定某個網絡下的全部主機,用於廣播
  • 主機號全爲 0 指定某個網絡

所以,在分配過程當中,應該去掉這兩種狀況。

廣播地址用於什麼?

廣播地址用於在同一個鏈路中相互鏈接的主機之間發送數據包

當主機號全爲 1 時,就表示該網絡的廣播地址。例如把 172.20.0.0/16 用二進制表示以下:

10101100.00010100.00000000.00000000

將這個地址的主機部分所有改成 1,則造成廣播地址:

10101100.00010100.11111111.11111111

再將這個地址用十進制表示,則爲 172.20.255.255

廣播地址能夠分爲本地廣播和直接廣播兩種。

  • 在本網絡內廣播的叫作本地廣播。例如網絡地址爲 192.168.0.0/24 的狀況下,廣播地址是 192.168.0.255 。由於這個廣播地址的 IP 包會被路由器屏蔽,因此不會到達 192.168.0.0/24 之外的其餘鏈路上。
  • 在不一樣網絡之間的廣播叫作直接廣播。例如網絡地址爲 192.168.0.0/24 的主機向 192.168.1.255/24 的目標地址發送 IP 包。收到這個包的路由器,將數據轉發給 192.168.1.0/24,從而使得全部 192.168.1.1~192.168.1.254 的主機都能收到這個包(因爲直接廣播有必定的安全問題,多數狀況下會在路由器上設置爲不轉發。) 。

什麼是 D、E 類地址?

而 D 類和 E 類地址是沒有主機號的,因此不可用於主機 IP,D 類常被用於多播,E 類是預留的分類,暫時未使用。

多播地址用於什麼?

因爲廣播沒法穿透路由,若想給其餘網段發送一樣的包,就可使用能夠穿透路由的多播。

多播使用的 D 類地址,其前四位是 1110 就表示是多播地址,而剩下的 28 位是多播的組編號。

從 224.0.0.0 ~ 239.255.255.255 都是多播的可用範圍,其劃分爲如下三類:

  • 224.0.0.0 ~ 224.0.0.255 爲預留的組播地址,只能在局域網中,路由器是不會進行轉發的。
  • 224.0.1.0 ~ 238.255.255.255 爲用戶可用的組播地址,能夠用於 Internet 上。
  • 239.0.0.0 ~ 239.255.255.255 爲本地管理組播地址,可供內部網在內部使用,僅在特定的本地範圍內有效。
IP 分類的優勢

無論是路由器仍是主機解析到一個 IP 地址時候,咱們判斷其 IP 地址的首位是否爲 0,爲 0 則爲 A 類地址,那麼就能很快的找出網絡地址和主機地址。

其他分類判斷方式參考以下圖:

因此,這種分類地址的優勢就是簡單明瞭、選路(基於網絡地址)簡單

IP 分類的缺點

缺點一

同一網絡下沒有地址層次,好比一個公司裏用了 B 類地址,可是可能須要根據生產環境、測試環境、開發環境來劃分地址層次,而這種 IP 分類是沒有地址層次劃分的功能,因此這就缺乏地址的靈活性

缺點二

A、B、C類有個尷尬處境,就是不能很好的與現實網絡匹配

  • C 類地址能包含的最大主機數量實在太少了,只有 254 個,估計一個網吧都不夠用。
  • 而 B 類地址能包含的最大主機數量又太多了,6 萬多臺機器放在一個網絡下面,通常的企業基本達不到這個規模,閒着的地址就是浪費。

IPv6

IPv6 的亮點

IPv6 不只僅只是可分配的地址變多了,它還有很是多的亮點。

  • IPv6 可自動配置,即便沒有 DHCP 服務器也能夠實現自動分配IP地址,真是便捷到即插即用啊。
  • IPv6 包頭包首部長度採用固定的值 40 字節,去掉了包頭校驗和,簡化了首部結構,減輕了路由器負荷,大大提升了傳輸的性能
  • IPv6 有應對僞造 IP 地址的網絡安全功能以及防止線路竊聽的功能,大大提高了安全性
IPv6 地址的標識方法

IPv4 地址長度共 32 位,是以每 8 位做爲一組,並用點分十進制的表示方式。

IPv6 地址長度是 128 位,是以每 16 位做爲一組,每組用冒號 「:」 隔開。

若是出現連續的 0 時還能夠將這些 0 省略,並用兩個冒號 「::」隔開。可是,一個 IP 地址中只容許出現一次兩個連續的冒號。

IPv6 地址的結構

IPv6 相似 IPv4,也是經過 IP 地址的前幾位標識 IP 地址的種類。

IPv6 的地址主要有如下類型地址:

  • 單播地址,用於一對一的通訊
  • 組播地址,用於一對多的通訊
  • 任播地址,用於通訊最近的節點,最近的節點是由路由協議決定
  • 沒有廣播地址

IPv6 單播地址類型

對於一對一通訊的 IPv6 地址,主要劃分了三類單播地址,每類地址的有效範圍都不一樣。

  • 在同一鏈路單播通訊,不通過路由器,可使用鏈路本地單播地址,IPv4 沒有此類型
  • 在內網裏單播通訊,可使用惟一本地地址,至關於 IPv4 的私有 IP
  • 在互聯網通訊,可使用全局單播地址,至關於 IPv4 的公有 IP

IPv4 首部與 IPv6 首部

IPv6 相比 IPv4 的首部改進:

  • 取消了首部校驗和字段。 由於在數據鏈路層和傳輸層都會校驗,所以 IPv6 直接取消了 IP 的校驗。
  • 取消了分片/從新組裝相關字段。 分片與重組是耗時的過程,IPv6 不容許在中間路由器進行分片與重組,這種操做只能在源與目標主機,這將大大提升了路由器轉發的速度。
  • 取消選項字段。 選項字段再也不是標準 IP 首部的一部分了,但它並無消失,而是可能出如今 IPv6 首部中的「下一個首部」指出的位置上。刪除該選項字段使的 IPv6 的首部成爲固定長度的 40 字節。

DNS

DNS 域名解析,DNS 能夠將域名網址自動轉換爲具體的 IP 地址。

域名解析的工做流程

瀏覽器首先看一下本身的緩存裏有沒有,若是沒有就向操做系統的緩存要,尚未就檢查本機域名解析文件 hosts,若是仍是沒有,就會 DNS 服務器進行查詢,查詢的過程以下:

  1. 客戶端首先會發出一個 DNS 請求,問 www.server.com 的 IP 是啥,併發給本地 DNS 服務器(也就是客戶端的 TCP/IP 設置中填寫的 DNS 服務器地址)。
  2. 本地域名服務器收到客戶端的請求後,若是緩存裏的表格能找到 www.server.com,則它直接返回 IP 地址。若是沒有,本地 DNS 會去問它的根域名服務器:「老大, 能告訴我 www.server.com 的 IP 地址嗎?」 根域名服務器是最高層次的,它不直接用於域名解析,但能指明一條道路。
  3. 根 DNS 收到來自本地 DNS 的請求後,發現後置是 .com,說:「www.server.com 這個域名歸 .com 區域管理」,我給你 .com 頂級域名服務器地址給你,你去問問它吧。」
  4. 本地 DNS 收到頂級域名服務器的地址後,發起請求問「老二, 你能告訴我 www.server.com 的 IP 地址嗎?」
  5. 頂級域名服務器說:「我給你負責 www.server.com 區域的權威 DNS 服務器的地址,你去問它應該能問到」。
  6. 本地 DNS 因而轉向問權威 DNS 服務器:「老三,www.server.com對應的IP是啥呀?」 server.com 的權威 DNS 服務器,它是域名解析結果的原出處。爲啥叫權威呢?就是個人域名我作主。
  7. 權威 DNS 服務器查詢後將對應的 IP 地址 X.X.X.X 告訴本地 DNS。
  8. 本地 DNS 再將 IP 地址返回客戶端,客戶端和目標創建鏈接。

ARP

在傳輸一個 IP 數據報的時候,肯定了源 IP 地址和目標 IP 地址後,就會經過主機「路由表」肯定 IP 數據包下一跳。然而,網絡層的下一層是數據鏈路層,因此咱們還要知道「下一跳」的 MAC 地址。

因爲主機的路由表中能夠找到下一跳的 IP 地址,因此能夠經過 ARP 協議,求得下一跳的 MAC 地址。

ARP 如何知道對方 MAC 地址的呢?

  • 主機會經過廣播發送 ARP 請求,這個包中包含了想要知道的 MAC 地址的主機 IP 地址。
  • 當同個鏈路中的全部設備收到 ARP 請求時,會去拆開 ARP 請求包裏的內容,若是 ARP 請求包中的目標 IP 地址與本身的 IP 地址一致,那麼這個設備就將本身的 MAC 地址塞入 ARP 響應包返回給主機。

操做系統一般會把第一次經過 ARP 獲取的 MAC 地址緩存起來,以便下次直接從緩存中找到對應 IP 地址的 MAC 地址。

不過,MAC 地址的緩存是有必定期限的,超過這個期限,緩存的內容將被清除。

參考:

35 張圖解:被問千百遍的 TCP 三次握手和四次揮手面試題

TCP 的那些事兒(上)

[網絡是怎麼鏈接的]

端口掃描原理及實現

跟着動畫來學習TCP三次握手和四次揮手

30張圖解: TCP 重傳、滑動窗口、流量控制、擁塞控制

IP 基礎知識全家桶,45 張圖一套帶走

相關文章
相關標籤/搜索