Java面試系列(一)--- TCP協議精準剖析

0.面試高頻題目

  • 順豐面試題:解釋下Tcp的三次握手與四次揮手的過程?
  • 順豐面試題:osi分層,tcp/ip分層,以及各層的做用?
  • 阿里面試題:TCP的鏈接創建和斷開的過程,如何保證TCP發送的信息是正確的,且保證其前後順序不被篡改?
  • 阿里面試題:TCP鏈接中的三次握手和四次揮手,四次揮手的最後一個ack的做用是什麼,爲何要time wait,爲何是2msl?
  • 今日頭條面試題:解釋下TCP擁塞控制和流量控制?

本篇,將完全剖析上述問題,讓面試官對你欲罷不能。nginx

1.TCP/IP

1.1簡介

TCP/IP協議(傳輸控制協議/互聯網協議)不是簡單的一個協議,而是一組特別的協議,包括:TCP,IP,UDP,ARP等,這些被稱爲子協議。git

1.2分層

osi 七層模型github

此圖來自: @知乎 仇諾伊面試

tcp/ip 四層模型算法

網絡接口層編程

負責監視數據在主機和網絡之間的交換;與OSI參考模型中的物理層和數據鏈路層相對應。事實上,TCP/IP自己並未定義該層的協議,而由參與互連的各網絡使用本身的物理層和數據鏈路層協議,而後與TCP/IP的網絡接入層進行鏈接。緩存

網際互連層tomcat

是整個體系結構的關鍵部分,其功能是使主機能夠把分組發往任何網絡,並使分組獨立地傳向目標。這些分組可能經由不一樣的網絡,到達的順序和發送的順序也可能不一樣。高層若是須要順序收發,那麼就必須自行處理對分組的排序。互聯網層使用因特網協議(IP,Internet Protocol)。TCP/IP參考模型的互聯網層和OSI參考模型的網絡層在功能上很是類似。安全

傳輸層服務器

使源端和目的端機器上的對等實體能夠進行會話。在這一層定義了兩個端到端的協議:傳輸控制協議(TCP,Transmission Control Protocol)和用戶數據報協議(UDP,User Datagram Protocol)。TCP 是面向鏈接的協議,它提供可靠的報文傳輸和對上層應用的鏈接服務。爲此,除了基本的數據傳輸外,它還有可靠性保證、流量控制、多路複用、優先權和安全性控制等功能。UDP 是面向無鏈接的不可靠傳輸的協議,主要用於不須要TCP的排序和流量控制等功能的應用程序。

應用層

包含全部的高層協議,包括:虛擬終端協議(TELNET,TELecommunications NETwork)、文件傳輸協議(FTP,File Transfer Protocol)、電子郵件傳輸協議(SMTP,Simple Mail Transfer Protocol)、域名服務(DNS,Domain Name Service)、網上新聞傳輸協議(NNTP,Net News Transfer Protocol)和超文本傳送協議(HTTP,HyperText Transfer Protocol)等。TELNET 容許一臺機器上的用戶登陸到遠程機器上,並進行工做;FTP 提供有效地將文件從一臺機器上移到另外一臺機器上的方法;SMTP 用於電子郵件的收發;DNS 用於把主機名映射到網絡地址;NNTP 用於新聞的發佈、檢索和獲取;HTTP 用於在 WWW 上獲取主頁。

1.3 TCP

1.3.1簡介

TCP(Transmission Control Protocol 傳輸控制協議)是一種面向鏈接的、可靠的、基於字節流的傳輸層通訊協議。

1.3.2 鏈接

面向鏈接意味着兩個使用TCP的應用(一般是一個客戶和一個服務器)在彼此交換數據包以前必須先創建一個TCP鏈接。這一過程與打電話很類似,先撥號振鈴,等待對方摘機說「喂」,而後才說明是誰。

1.3.2.1 三次握手

咱們先看一下tcp的數據報文的結構:

帶陰影的幾個字段須要重點說明一下:

  1. 序號:Seq(Sequence Number)序號佔32位,用來標識從計算機A發送到計算機B的數據包的序號,計算機發送數據時對此進行標記。

  2. 確認號:Ack(Acknowledge Number)確認號佔32位,客戶端和服務器端均可以發送,Ack = Seq + 1。

  3. 標誌位:每一個標誌位佔用1Bit,共有6個,分別爲 URG、ACK、PSH、RST、SYN、FIN,具體含義以下:

  • SYN(synchronous創建聯機)
  • ACK(acknowledgement 確認)
  • PSH(push傳送)
  • FIN(finish結束)
  • RST(reset重置)
  • URG(urgent緊急)

如何創建一個鏈接呢?

TCP的三次握手:「三次握手」的意思是創建TCP鏈接「須要三個步驟才能創建鏈接的機制」。三次握手的目的是鏈接服務器指定端口,創建TCP鏈接,並同步鏈接雙方的序列號和確認號並交換 TCP 窗口大小信息。

在socket編程中,客戶端執行connect()時,將觸發三次握手。

1)主機A發送標誌syn=1,隨機產生 seq=x 的數據包到服務器,主機B由syn=1知道,A要求創建鏈接; 此時狀態A爲SYN_SENT,B爲LISTEN。

2)主機B收到請求後要確認鏈接信息,向A發送ack=(主機A的seq+1),標誌syn=1,ack=1,隨機產生seq=y的包,此時狀態A爲ESTABLISHED,B爲SYN_RCVD。

3)主機A收到後檢查ack 是否正確,即第一次發送的seqnumber+1,以及位碼ack是否爲1,若正確,主機A會再發送ack =(主機B的seq+1),標誌ack=1,主機B收到後確認seq值與ack=1則鏈接創建成功。此時A、B狀態都變爲ESTABLISHED。

TCP爲何不是兩次鏈接?而是三次握手?

答:若是A與B兩個進程通訊,若是僅是兩次鏈接。可能出現的一種狀況就是:A發送完請求報文之後,因爲網絡狀況很差,出現了網絡擁塞,即B延時很長時間後收到報文,即此時A將此報文認定爲失效的報文。B收到報文後,會向A發起鏈接。此時兩次握手完畢。B會認爲已經創建了鏈接能夠通訊,B會一直等到A發送的鏈接請求,而A對失效的報文回覆天然不會處理。所以會陷入B忙等的僵局,造成了死鎖,形成資源的浪費。

1.3.2.2 SYN攻擊

什麼是 SYN 攻擊(SYN Flood)?

在三次握手過程當中,服務器發送 SYN-ACK 以後,收到客戶端的 ACK 以前的 TCP 鏈接稱爲半鏈接(half-open connect)。此時服務器處於 SYN_RCVD 狀態。當收到 ACK 後,服務器才能轉入 ESTABLISHED 狀態.

SYN 攻擊指的是,攻擊客戶端在短期內僞造大量不存在的IP地址,向服務器不斷地發送SYN包,服務器回覆確認包,並等待客戶的確認。因爲源地址是不存在的,服務器須要不斷的重發直至超時,這些僞造的SYN包將長時間佔用未鏈接隊列,正常的SYN請求被丟棄,致使目標系統運行緩慢,嚴重者會引發網絡堵塞甚至系統癱瘓。

SYN 攻擊是一種典型的 DoS/DDoS 攻擊。

如何檢測 SYN 攻擊?

檢測 SYN 攻擊很是的方便,當你在服務器上看到大量的半鏈接狀態時,特別是源IP地址是隨機的,基本上能夠判定這是一次SYN攻擊。在 Linux/Unix 上可使用系統自帶的 netstats 命令來檢測 SYN 攻擊。

如何防護 SYN 攻擊?

SYN攻擊不能徹底被阻止,除非將TCP協議從新設計。咱們所作的是儘量的減輕SYN攻擊的危害,常見的防護 SYN 攻擊的方法有以下幾種:

  • 縮短超時(SYN Timeout)時間
  • 增長最大半鏈接數
  • 過濾網關防禦
  • SYN cookies技術

1.3.3 數據傳輸

創建鏈接後,兩臺主機就能夠相互傳輸數據了。以下圖所示:

上圖給出了主機A分2次(分2個數據包)向主機B傳遞200字節的過程。

  1. 首先,主機A經過1個數據包發送100個字節的數據,數據包的 Seq 號設置爲1200。

  2. 主機B爲了確認這一點,向主機A發送 ACK 包,並將 Ack 號設置爲 1300。

爲了保證數據準確到達,目標機器在收到數據包(包括SYN包、FIN包、普通數據包等)包後必須當即回傳ACK包,這樣發送方纔能確認數據傳輸成功。

此時 Ack 號爲 1300 而不是 1200,緣由在於 Ack 號的增量爲傳輸的數據字節數。

假設每次 Ack 號不加傳輸的字節數,這樣雖然能夠確認數據包的傳輸,但沒法明確100字節所有正確傳遞仍是丟失了一部分,好比只傳遞了80字節。

所以按以下的公式確認 Ack 號: Ack號 = Seq號 + 傳遞的字節數

下面分析傳輸過程當中數據包丟失的狀況,以下圖所示:

上圖表示經過 Seq 1300 數據包向主機B傳遞100字節的數據,但中間發生了錯誤,主機B未收到。通過一段時間後,主機A仍未收到對於 Seq 1300 的ACK確認,所以嘗試重傳數據。

爲了完成數據包的重傳,TCP套接字每次發送數據包時都會啓動定時器,若是在必定時間內沒有收到目標機器傳回的 ACK 包,那麼定時器超時,數據包會重傳。

上圖演示的是數據包丟失的狀況,也會有 ACK 包丟失的狀況,同樣會重傳。

超時時間如何判斷是由具體的算法去確認,重傳次數根據系統設置的不一樣而有所區別,在此很少贅述。

1.3.3.1 可靠傳輸過程

在上述的數據傳輸過程當中,tcp如何確保數據的可靠傳輸呢?

(1)爲了保證數據包的可靠傳遞,發送方必須把已發送的數據包保留在緩衝區;

(2)併爲每一個已發送的數據包啓動一個超時定時器;

(3)如在定時器超時以前收到了對方發來的應答信息,則釋放該數據包占用的緩衝區;

(4)不然,重傳該數據包,直到收到應答或重傳次數超過規定的最大次數爲止。

(5)接收方收到數據包後,先進行CRC校驗(循環冗餘校驗碼,保證數據傳輸的正確性和完整性),若是正確則把數據交給上層協議,而後給發送方發送一個累計應答包,代表該數據已收到,若是接收方正好也有數據要發給發送方,應答包也可方在數據包中捎帶過去。

1.3.3.2 可靠傳輸之超時重傳和快速重傳  

  • 超時重傳:當超時時間到達時,發送方還未收到對端的ACK確認,就重傳該數據包。
  • 快速重傳:當後面的序號先到達,如接收方接收到了一、 三、 4,而2沒有收到,就會當即向發送方重複發送三次ACK=2的確認請求重傳。若是發送方連續收到3個相同序號的ACK,就重傳該數據包。而不用等待超時 。

1.3.3.3 可靠傳輸之流量控制

一條TCP鏈接每一側主機都爲該鏈接設置了接收緩存。當該TCP鏈接收到了正確的、按序的字節後,他就將數據放入接收緩存。相關聯的應用進程會從該緩存中讀取數據。但沒必要是數據一到達就當即讀取。事實上,接收方也許正忙於其餘任務,甚至要過很長時間後纔讀取該數據。若是某個應用進程讀取比較緩慢,可是發送方發送的太多、太快,發送的數據就會很容易地使該鏈接的接收緩存溢出。

TCP爲它的應用程序提供了流量控制服務(flow-control service) 以消除發送方使接收方緩存溢出的可能性。流量控制所以是一個速度匹配服務,即發送方的發送速率與接收方應用程序的讀取速率相匹配。

TCP經過讓發送方維護一個稱爲 接收窗口(receivewindow) 的變量(TCP報文段首部的接收窗口字段)來提供流量控制。通俗的講,接收窗口用於給發送方一個指示--該接收方還有多少可用的緩存空間。由於TCP是全雙工通訊,在鏈接兩端的發送方都各自維護了一個接收窗口。

考慮一種特殊的狀況,就是接收方若沒有緩存足夠使用,就會發送零窗口大小的報文,此時發送放將發送窗口設置爲0,中止發送數據。以後接收方有足夠的緩存,發送了非零窗口大小的報文,可是這個報文在中途丟失的,那麼發送方的發送窗口就一直爲零致使死鎖。 解決這個問題,TCP爲每個鏈接設置一個 持續計時器(persistencetimer)。只要TCP的一方收到對方的零窗口通知,就啓動該計時器,週期性的發送一個零窗口探測報文段。對方就在確認這個報文的時候給出如今的窗口大小。

1.3.3.4 可靠傳輸之擁塞控制

爲何要進行擁塞控制?

要回答這個問題,首先必須知道何時TCP會出現擁塞。TCP做爲一個端到端的傳輸層協議,它並不關心鏈接雙方在物理鏈路上會通過多少路由器交換機以及報文傳輸的路徑和下一條,這是IP層該考慮的事。然而,在現實網絡應用中,TCP鏈接的兩端可能相隔千山萬水,報文也須要由多個路由器交換機進行轉發。交換設備的性能不是無限的!, 當多個入接口的報文都要從相同的出接口轉發時,若是出接口轉發速率達到極限,報文就會開始在交換設備的入接口緩存隊列堆積。但這個隊列長度也是有限的,當隊列塞滿後,後續輸入的報文就只能被丟棄掉了。對於TCP的發送端來講,看到的就是發送超時丟包了。

網絡資源是各個鏈接共享的,爲了你們都能完成數據傳輸。因此,TCP須要當它感知到傳輸發生擁塞時,須要下降本身的發送速率,等待擁塞解除

如何進行擁塞控制?

擁塞窗口 cwnd:首先須要明確的是,TCP是在發送端進行擁塞控制的。TCP爲每條鏈接準備了一個記錄擁塞窗口大小的變量cwnd,它限制了本端TCP能夠發送到網絡中的最大報文數量。顯然,這個值越大,鏈接的吞吐量越高,但也更容易致使網絡擁塞。因此,TCP的擁塞控制本質上就是根據丟包狀況調整cwnd,使得傳輸的吞吐率儘量地大!而不一樣的擁塞控制算法就是調整cwnd的方式不一樣!

rwnd與cwnd區別
rwnd(Receiver Window,接收者窗口)與cwnd(Congestion Window,擁塞窗口)的概念: rwnd:是用於流量控制的窗口大小,即上述流量控制中的AdvertisedWindow,主要取決於接收方的處理速度,由接收方通知發送方被動調整(詳細邏輯見上)。
cwnd:是用於擁塞處理的窗口大小,取決於網絡情況,由發送方探查網絡主動調整。
介紹流量控制時,咱們沒有考慮cwnd,認爲發送方的滑動窗口最大即爲rwnd。實際上,須要同時考慮流量控制與擁塞處理,則發送方窗口的大小不超過min{rwnd, cwnd}。下述4種擁塞控制算法只涉及對cwnd的調整,同介紹流量控制時同樣,暫且不考慮rwnd,假定滑動窗口最大爲cwnd;但讀者應明確rwnd、cwnd與發送方窗口大小的關係。

四種擁塞控制算法

TCP從誕生至今,已經有了多種的擁塞控制算法,直到如今還有新的在被提出!其中TCP Tahoe(1988)和TCP Reno(1990)是最初的兩個算法。雖然看上去年代很就遠了,但 Reno算法直到如今還在普遍地使用。

Tahoe 提出了  1)慢啓動,2)擁塞避免,3)快速重傳
Reno 在Tahoe的基礎上增長了 4)快速恢復
複製代碼

Tahoe算法的基本思想是:
首選設置一個符合情理的初始窗口值。
當沒有出現丟包時,慢慢地增長窗口大小,逐漸逼近吞吐量的上界。
當出現丟包時,快速地減少窗口大小,等待阻塞消除。

擁塞控制算法之慢啓動算法

慢啓動算法(Slow Start)做用在擁塞產生以前:對於剛剛加入網絡的鏈接,要一點一點的提速,不要妄圖一步到位。以下:

鏈接剛建好,初始化cwnd = 1(固然,一般不會初始化爲1,過小),代表能夠傳一個MSS大小的數據。
每收到一個ACK,cwnd++,線性增加。
每通過一個RTT,cwnd = cwnd * 2,指數增加(主要增加來源)。
還有一個ssthresh(slow start threshold),當cwnd >= ssthresh時,就會進入擁塞避免算法(見後)。
複製代碼

所以,若是網速很快的話,Ack返回快,RTT短,那麼,這個慢啓動就一點也不慢。下圖說明了這個過程:

注4RFC 2581 已經容許cwnd的初始值最大爲2, RFC 3390 已經容許cwnd的初始值最大爲4, RFC 6928已經容許cwnd的初始值最大爲10

擁塞控制算法之擁塞避免算法

前面說過,當cwnd >= ssthresh(一般ssthresh = 65535)時,就會進入擁塞避免算法(Congestion Avoidance):緩慢增加,當心翼翼的找到最優值。以下:

每收到一個Ack,cwnd = cwnd + 1/cwnd,顯然,cwnd > 1時無增加。
每通過一個RTT,cwnd++,線性增加(主要增加來源)。
複製代碼

慢啓動算法主要呈指數增加,粗獷型,速度快(「慢」是相對於一步到位而言的);而擁塞避免算法主要呈線性增加,精細型,速度慢,但更容易在不致使擁塞的狀況下,找到網絡環境的cwnd最優值。

擁塞控制算法之快速重傳算法

因爲TCP採用的是累計確認機制,即當接收端收到比指望序號大的報文段時,便會重複發送最近一次確認的報文段的確認信號,咱們稱之爲冗餘ACK(duplicate ACK)。 如圖所示,報文段1成功接收並被確認ACK 2,接收端的期待序號爲2,當報文段2丟失,報文段3失序到來,與接收端的指望不匹配,接收端重複發送冗餘ACK 2。

這樣,若是在超時重傳定時器溢出以前,接收到連續的三個重複冗餘ACK(實際上是收到4個一樣的ACK,第一個是正常的,後三個纔是冗餘的),發送端便知曉哪一個報文段在傳輸過程當中丟失了,因而重發該報文段,不須要等待超時重傳定時器溢出,大大提升了效率。這即是快速重傳機制。

擁塞控制算法之快速恢復算法

若是觸發了快速重傳,即發送方收到至少3次相同的Ack,那麼TCP認爲網絡狀況不那麼糟,也就不必提心吊膽的,能夠適當大膽的恢復。爲此設計快速恢復算法(Fast Recovery),下面介紹TCP Reno中的實現。

回顧一下,進入快速恢復以前,cwnd和sshthresh已被更新:

ssthresh = cwnd /2
cwnd = cwnd /2
複製代碼

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

cwnd = ssthresh + 3 * MSS (嘗試一步到位)
重傳重複Ack對應的Seq
若是再收到該重複Ack,則cwnd++,線性增加(緩慢調整)
若是收到了新Ack,則cwnd = ssthresh ,而後就進入了擁塞避免的算法了
複製代碼

1.3.4 斷開

鏈接終止協議(四次握手)

因爲TCP鏈接是全雙工的,所以每一個方向都必須單獨進行關閉。這原則是當一方完成它的數據發送任務後就能發送一個FIN來終止這個方向的鏈接。收到一個 FIN只意味着這一方向上沒有數據流動,一個TCP鏈接在收到一個FIN後仍能發送數據。首先進行關閉的一方將執行主動關閉,而另外一方執行被動關閉。

  • A的應用進程先向其TCP發出鏈接釋放報文段(FIN=1,序號seq=u),並中止再發送數據,主動關閉TCP鏈接,進入FIN-WAIT-1(終止等待1)狀態,等待B的確認。
  • B收到鏈接釋放報文段後即發出確認報文段,(ACK=1,確認號ack=u+1,序號seq=v),B進入CLOSE-WAIT(關閉等待)狀態,此時的TCP處於半關閉狀態,A到B的鏈接釋放。
  • A收到B的確認後,進入FIN-WAIT-2(終止等待2)狀態,等待B發出的鏈接釋放報文段。
  • B沒有要向A發出的數據,B發出鏈接釋放報文段(FIN=1,ACK=1,序號seq=w,確認號ack=u+1),B進入LAST-ACK(最後確認)狀態,等待A的確認。
  • A收到B的鏈接釋放報文段後,對此發出確認報文段(ACK=1,seq=u+1,ack=w+1),A進入TIME-WAIT(時間等待)狀態。此時TCP未釋放掉,須要通過時間等待計時器設置的時間2MSL後,A才進入CLOSED狀態。

爲何創建鏈接協議是三次握手,而關閉鏈接倒是四次握手呢?
這是由於服務端的LISTEN狀態下的SOCKET當收到SYN報文的建連請求後,它能夠把ACK和SYN(ACK起應答做用,而SYN起同步做用)放在一個報文裏來發送。但關閉鏈接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了;但未必你全部的數據都所有發送給對方了,因此你可能未必會立刻會關閉SOCKET,也即你可能還須要發送一些數據給對方以後,再發送FIN報文給對方來表示你贊成如今能夠關閉鏈接了,因此它這裏的ACK報文和FIN報文多數狀況下都是分開發送的。

1.3.4.1 FIN_WAIT_2 狀態

在FIN_ WAIT_2狀態咱們已經發出了FIN,而且另外一端也已對它進行確認。除非咱們在實 行半關閉,不然將等待另外一端的應用層意識到它已收到一個文件結束符說明,並向咱們發一 個FIN來關閉另外一方向的鏈接。只有當另外一端的進程完成這個關閉,咱們這端纔會從 FIN_WAIT_2狀態進入TIME_WAIT狀態。 這意味着咱們這端可能永遠保持這個狀態。另外一端也將處於 CLOSE_WAIT狀態,並一直 保持這個狀態直到應用層決定進行關閉。 許多伯克利實現採用以下方式來防止這種在FIN_WAIT_2狀態的無限等待。若是執 行主動關閉的應用層將進行全關閉,而不是半關閉來講明它還想接收數據,就設置一 個定時器。若是這個鏈接空閒10分鐘75秒,TCP將進入CLOSED狀態。在實現代碼的註釋中確認這個實現代碼違背協議的規範。

1.3.4.2 2MSL

什麼是2MSL?MSL即Maximum Segment Lifetime,也就是報文最大生存時間,引用《TCP/IP詳解》中的話:「它 (MSL)是任何報文段被丟棄前在網絡內的最長時間,那麼,2MSL也就是這個時間的2倍,當TCP鏈接完成四個報文段的交換時,主動關閉的一方將繼續等待必定時間(2-4分鐘),即便兩端的應用程序結束。

爲何TIME_WAIT狀態須要通過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?

第一:保證客戶端發送的最後一個ACK報文可以到達服務器,由於這個ACK報文可能丟失,站在服務器的角度看來,我已經發送了FIN+ACK報文請求斷開了,客戶端尚未給我回應,應該是我發送的請求斷開報文它沒有收到,因而服務器又會從新發送一次,而客戶端就能在這個2MSL時間段內收到這個重傳的報文,接着給出迴應報文,而且會重啓2MSL計時器。
第二:防止相似與「三次握手」中提到了的「已經失效的鏈接請求報文段」出如今本鏈接中,即在關閉一個TCP鏈接後,立刻又從新創建起一個相同的IP地址和端口之間的TCP鏈接,後一個鏈接被稱爲前一個鏈接的化身 (incarnation),那麼有可能出現這種狀況。客戶端發送完最後一個確認報文後,在這個2MSL時間中,就可使本鏈接持續的時間內所產生的全部報文段都從網絡中消失。這樣新的鏈接中不會出現舊鏈接的請求報文。

對服務器的影響?

當某個鏈接的一端處於TIME_WAIT狀態時,該鏈接將不能再被使用。事實上,對於咱們比較有現實意義的是,這個端口將不能再被使用。某個端口處於TIME_WAIT狀態(其實應該是這個鏈接)時,這意味着這個TCP鏈接並無斷開(徹底斷開),那麼,若是你bind這個端口,就會失敗。對於服務器而言,若是服務器忽然crash掉了,那麼它將沒法在2MSL內從新啓動,由於bind會失敗。解決這個問題的一個方法就是設置socket的SO_REUSEADDR選項。這個選項意味着你能夠重用一個地址。

1.3.4.3 案例與解決方案

1.掘金文章 juejin.im/post/5b59e6…

2.我的博客:lanjingling.github.io/2016/02/27/…

1.3.5 tcp狀態裝換圖

2 總結

上述文章,講述了tcp協議從創建到傳輸數據到斷開鏈接的過程。文章是從網上搜羅多篇文章總結出來的結果,本人的理解也有限,若是你們發現有什麼問題請及時指出。

若是有關於tcp的好的面試題請在下方留言給我!!!多謝!!!

相關文章
相關標籤/搜索