目錄
1. WebRTC 概述
2. WebRTC 揭秘
2.1 Network Address Translation: NAT
2.2 Session Traversal Utilities for NAT:STUN
2.3 Traversal Using Relays around NAT: TURN
2.4 Interactive Connectivity Establishment: ICE
2.5 Session Description Protocol: SDP
2.6 信令交換:Signaling
工作流程總結(jié)
3. Demo
4. WebRTC的優(yōu)缺點(diǎn)
1. 優(yōu)點(diǎn)
2. 缺點(diǎn)
5. 擴(kuò)展內(nèi)容
5.1 Media API
5.2 onIceCandidate 和 addIceCandidate
5.3 自定義 TURN 和 STUN 服務(wù)器
5.4 公共 STUN 服務(wù)器
WebRTC (Web Real-Time Communication)是一個(gè)免費(fèi)、開(kāi)源的項(xiàng)目,通過(guò)簡(jiǎn)單的應(yīng)用程序編程接口(API)為 Web 瀏覽器和移動(dòng)應(yīng)用程序提供實(shí)時(shí)通信(RTC)。這也表明了 WebRTC 設(shè)計(jì)的目標(biāo)就是“設(shè)計(jì)一種通過(guò)盡量短的、延遲盡量低的路徑進(jìn)行 P2P 通信的協(xié)議,提供一種簡(jiǎn)單的、能讓所有人使用的 API”。一旦你把它放入瀏覽器,它就是標(biāo)準(zhǔn);一旦它成為了標(biāo)準(zhǔn),開(kāi)發(fā)時(shí)會(huì)遇到的“摩擦”就會(huì)消失。
我追蹤 WebRTC 這項(xiàng)技術(shù)大概已經(jīng)兩年了,聽(tīng)眾們?yōu)槲姨峁┝舜罅績(jī)?yōu)質(zhì)的資源,也提出了很多優(yōu)秀的問(wèn)題。應(yīng)大家的呼聲,我做出了這期視頻,為大家提供一個(gè) WebRTC 的基本教程。我將按以下順序進(jìn)行講解:
WebRTC 概述
WebRTC 揭秘:NAT、STUN、TURN、ICE、SDP、信令
Demo
WebRTC的優(yōu)缺點(diǎn)
擴(kuò)展內(nèi)容
1. WebRTC 概述
首先想到的問(wèn)題是我們?yōu)楹我?WebRTC?
建立它的理由是人們需要用一種標(biāo)準(zhǔn)的、低延遲的方式來(lái)傳遞媒體數(shù)據(jù)(視頻&音頻)。所謂“標(biāo)準(zhǔn)的”意味著我們需要簡(jiǎn)單易使用的 API;而所謂“低延遲的”意味著需要一種合適的協(xié)議,UDP 顯然是一個(gè)好的選擇,因?yàn)?UDP 沒(méi)有過(guò)多的應(yīng)答過(guò)程(Acknowledgment)。但我們需要的協(xié)議要比 UDP 更好,要能支持 P2P 的通信。因?yàn)橐坏┮蕾?lài)服務(wù)器來(lái)傳遞內(nèi)容就會(huì)因?yàn)榉聪虼砘蛘叽┩敢腩~外的延遲,用戶(hù)需要進(jìn)行終止、觀(guān)察、處理、轉(zhuǎn)化流等操作,這些都會(huì)造成額外消耗。對(duì)于視頻傳輸、特別是直播、會(huì)話(huà)等場(chǎng)景,用戶(hù)希望內(nèi)容到達(dá)得越快越好,所以 P2P 是最快的路徑。
此外,WebRTC 也旨在實(shí)現(xiàn)瀏覽器之間豐富的溝通。瀏覽器已經(jīng)發(fā)展了很長(zhǎng)時(shí)間,它“擁有”大量的優(yōu)質(zhì)視頻,它可以訪(fǎng)問(wèn)攝像頭和麥克風(fēng),這些特性都值得被開(kāi)發(fā)利用。用戶(hù)不需要寫(xiě)自己的應(yīng)用,而是基于 WebRTC 的標(biāo)準(zhǔn) API 便可以輕松使用。不僅是瀏覽器,在移動(dòng)設(shè)備和 IoT 設(shè)備通信時(shí)也同樣。
那么在 WebRTC 中究竟發(fā)生了哪些事呢?
舉個(gè)例子,A 想要與 B 進(jìn)行通信,但 A 與 B 之間“互不相識(shí)”。所以 A 首先需要找到所有 Public(不是 B)能連接到它的途徑,檢查 A 是否有一個(gè)公共 IP 能被 Public 識(shí)別或使用,如果沒(méi)有檢查 A 的路由器是否允許公開(kāi)端口轉(zhuǎn)發(fā)規(guī)則、是否在路由上有公共代表等等。B 也做了以上同樣的事。
此外,A 和 B 還會(huì)收集自身所支持的加密方式、安全參數(shù)、視頻編解碼器等等大量的信息,注意這些信息還沒(méi)有被送到對(duì)端,在這個(gè)階段只是廣泛地收集。所有這些信息,構(gòu)成了“SDP”。
接下來(lái),A 和 B 會(huì)通過(guò)其他方式(可以是 WhatsApp、QR、Tweet、WebSockets、HTTP Fetch…)發(fā)出會(huì)話(huà)信息,這種方式具體是什么 WebRTC 并不關(guān)心,只要能從 A 到 B(B 到 A)就可以。
這種工作方式表面上是有些“愚蠢的”,部分人可能會(huì)認(rèn)為“既然我已經(jīng)有了 A 和 B 之間通信的線(xiàn)路,那還要 WebRTC 做什么呢?”但認(rèn)真思考一下就可以發(fā)現(xiàn),WebRTC 只要首次通信雙方交換了 SDP,后面就會(huì)實(shí)現(xiàn)真正的 P2P 通信,不再需要 WhatsApp、QR 等等中間途徑,不會(huì)有比這更快的通信路徑。因此最終 A 通過(guò)最優(yōu)路徑連接到了 B,這就是 WebRTC 的工作流程。
更詳細(xì)的闡釋這個(gè)例子如下:
如上圖所示,假設(shè) A 找到了 A1、A2、A3 三種方式可以訪(fǎng)問(wèn)它,同時(shí)還找到了安全參數(shù)、媒體選項(xiàng)等信息。同時(shí),B 也做了一樣的工作。接下來(lái),他們通過(guò)一些方式(例如 WhatsApp)交換了以上信息。然后 A 找到 B2 是可用的最佳路徑,而 B 也發(fā)現(xiàn) A1 是可用的最佳路徑,那么二者將通過(guò)這條路徑直接連接彼此。本質(zhì)上 WebRTC 就是這樣工作的。
2. WebRTC 揭秘
接下來(lái)我們對(duì) WebRTC 進(jìn)行深入理解,對(duì)細(xì)節(jié)內(nèi)容進(jìn)行講述。首先了解 NAT 的細(xì)節(jié),學(xué)習(xí) WebRTC 是如何進(jìn)行正確的網(wǎng)絡(luò)地址轉(zhuǎn)換;其次了解為什么我們需要 STUN 和 TURN;此外還會(huì)介紹 ICE、SDP 以及信令交換的相關(guān)內(nèi)容。
2.1 Network Address Translation: NAT
如果你有一個(gè)公開(kāi)的 Public IP 地址,連接過(guò)程將不會(huì)有什么問(wèn)題。因?yàn)槟銜?huì)像 Web 服務(wù)器一樣一直監(jiān)聽(tīng)端口,把端口和 IP 都提供給對(duì)方后,你和它就可以直接進(jìn)行連接了。但在大多數(shù)情況下,用戶(hù)都是隱藏在公共網(wǎng)絡(luò)之后的,無(wú)法直接連接。如下圖所示的示例中,路由器有一個(gè) Public IP 5.5.5.5,也有一個(gè) Private IP 10.0.0.1(也被稱(chēng)為 gateway),你的機(jī)器只有一個(gè) Private IP 10.0.0.2,但你想要訪(fǎng)問(wèn) IP 為 4.4.4.4:80 的機(jī)器,要如何實(shí)現(xiàn)呢?
首先你的機(jī)器會(huì)構(gòu)建一個(gè)數(shù)據(jù)包,聲明想向 4.4.4.4:80 發(fā)出 GET 請(qǐng)求,10.0.0.2 是源 IP 地址。接下來(lái),你的機(jī)器會(huì)通過(guò)子網(wǎng)掩碼判斷是否可以直接與 4.4.4.4:80 進(jìn)行連接,運(yùn)算結(jié)果會(huì)顯示 4.4.4.4:80 并不在你所在的子網(wǎng)中,因此無(wú)法直接進(jìn)行通信。所以下一步就需要將請(qǐng)求發(fā)送給路由器,借助 gateway 進(jìn)行通信。路由器會(huì)替換源 IP 地址和端口為 Public IP 和一個(gè)隨機(jī)端口,但在此之前會(huì)創(chuàng)建 NAT 表,來(lái)記錄三者之間的對(duì)應(yīng)關(guān)系。這樣對(duì)端就能收到你的GET請(qǐng)求,并進(jìn)行后續(xù)處理了。
在這之后,服務(wù)器 4.4.4.4:80 將向你的機(jī)器發(fā)送回復(fù),工作原理和上述相同,根據(jù) NAT 表查詢(xún)對(duì)應(yīng)地址完成通信。
NAT 的轉(zhuǎn)換方式主要有以下幾種,在默認(rèn)情況下,WebRTC 可以支持前三種 NAT 方式,對(duì)最后一種并不友好。實(shí)際上 90% 以上的通信就是通過(guò)前三種方式完成的,最后一種作者個(gè)人認(rèn)為沒(méi)有使用的價(jià)值。
一對(duì)一 NAT(完全圓錐型 NAT):One to One NAT(Full-cone NAT)路由器上要發(fā)送到外部 IP:port 的數(shù)據(jù)包總是可以映射到內(nèi)部 IP:port ,無(wú)一例外。舉例說(shuō)明,所有發(fā)送到 5.5.5.5:3333 的數(shù)據(jù)包總是會(huì)被自動(dòng)轉(zhuǎn)發(fā)到 10.0.0.2:8992,無(wú)論這個(gè)包是來(lái)自 4.4.4.4:80 或者其他任何地址。
IP 受限型 NAT:Address restricted NAT出于安全考慮,部分路由器會(huì)地址限制,考慮之前是否與該地址進(jìn)行過(guò)通信。即路由器上要發(fā)送到外部 IP:port 的數(shù)據(jù)包可以映射到內(nèi)部 IP:port,前提是數(shù)據(jù)包的源地址與 NAT 表相符,無(wú)所謂端口是什么。舉例說(shuō)明,發(fā)送到 5.5.5.5:3333 的數(shù)據(jù)包中,只有源 IP 是 4.4.4.4 或其他表中有過(guò)記錄的 IP 才會(huì)被自動(dòng)轉(zhuǎn)發(fā)到 10.0.0.2:8992,即使這個(gè) IP 之前并不是和 3333 端口進(jìn)行的通信。
端口受限型 NAT:Port restricted NAT與前者相比,增加了端口限制,即路由器上要發(fā)送到外部 IP:port 的數(shù)據(jù)包可以映射到內(nèi)部 IP:port,前提是數(shù)據(jù)包的源 IP 和 Port 都要與 NAT 表相符。舉例說(shuō)明,發(fā)送到 5.5.5.5:3333 的數(shù)據(jù)包中,只有來(lái)自 4.4.4.4:80 或其他表中有過(guò)記錄的 IP:Port 才會(huì)被自動(dòng)轉(zhuǎn)發(fā)到 10.0.0.2:8992,即使這個(gè) IP:Port 之前并不是和 3333 端口進(jìn)行的通信。
對(duì)稱(chēng) NAT:Symmetric NAT該方式是限制最多的一種,即必須匹配完整的 IP:port,區(qū)別在于發(fā)送到 5.5.5.5:3333 的數(shù)據(jù)包中,只有來(lái)自 4.4.4.4:80 的才會(huì)被自動(dòng)轉(zhuǎn)發(fā)到 10.0.0.2:8992,其他的包均無(wú)法通過(guò)。這種方式無(wú)法在 WebRTC 中使用,因?yàn)?WebRTC 需要 STUN 服務(wù)器。一旦 STUN 服務(wù)器建立了一個(gè) Public 代表,Symmetric NAT 要求只能與一個(gè)特定的對(duì)端通信,這種限制不適合 WebRTC。
2.2 Session Traversal Utilities for NAT:STUN
STUN 是可以賦予一個(gè)應(yīng)用程序所需要的 Public IP 和 Port,適用于 Full-cone、Address restricted 和 Port restricted NAT,無(wú)法用于 Symmetric NAT。STUN 服務(wù)器通常在 3478 端口上運(yùn)行,TLS 端口為 5349。STUN 是非常輕量級(jí)的,用戶(hù)可以使用 docker 建立一個(gè) STUN 服務(wù)器。STUN 服務(wù)器的目的就是讓用戶(hù)找到自己的 Public 表示,并通過(guò)這個(gè) Public 表示與其他用戶(hù)進(jìn)行通信。如果我們使用的是像大約 1996 年或 2000 年早期時(shí)那樣的 Public IP 地址,通信也將非常簡(jiǎn)單。但就現(xiàn)在而言,我們必須使用 STUN 服務(wù)器。STUN 服務(wù)器的工作流程如下圖所示:
首先創(chuàng)建一個(gè)數(shù)據(jù)包進(jìn)行 STUN 請(qǐng)求,STUN 服務(wù)器的地址為 9.9.9.9:3478,同樣在路由器創(chuàng)建了 NAT 表并進(jìn)行了地址轉(zhuǎn)換,然后數(shù)據(jù)包被送到了 STUN 服務(wù)器。
服務(wù)器收到請(qǐng)求后,為 10.0.0.2 的機(jī)器構(gòu)建了一個(gè) Public 表示 5.5.5.5:3333,并把這個(gè)信息打包進(jìn)一個(gè)數(shù)據(jù)包進(jìn)行反饋。
上述是一個(gè) STUN 請(qǐng)求的詳細(xì)過(guò)程,以下圖為例 STUN 在整個(gè)通信過(guò)程中進(jìn)行了以下工作:首先給予 10.0.0.2 的機(jī)器一個(gè) Public 表示 5.5.5.5:3333,同時(shí)給予 192.168.1.2 的機(jī)器一個(gè) Public 表示 7.7.7.7:4444。隨后二者都使用獲得的 Public 表示進(jìn)行連接。
值得注意的是,這二者之前并沒(méi)有進(jìn)行過(guò)通信。如果是 Full-cone NAT,那么沒(méi)有問(wèn)題可以連接;如果是 Address restricted NAT,第一個(gè)請(qǐng)求連接的請(qǐng)求將會(huì)失敗。在這種情況下,用戶(hù)需要通過(guò)服務(wù)器建立至少一個(gè)通信請(qǐng)求,先讓兩個(gè)地址都能保存在兩端的路由器中,這樣再次通過(guò) Public 表示進(jìn)行連接請(qǐng)求時(shí)就能找到匹配的地址,繼而可以完成連接。Port restricted NAT 工作原理與之類(lèi)似。
2.3 Traversal Using Relays around NAT: TURN
在應(yīng)用 Symmetric NAT 的情況下,必須使用 TURN。所有的通信內(nèi)容都要經(jīng)過(guò) TURN 服務(wù)器的轉(zhuǎn)發(fā),所以 TURN 服務(wù)器的維護(hù)成本比較高,這也是為什么幾乎沒(méi)有人免費(fèi)提供這種服務(wù)器供用戶(hù)使用。下圖是一個(gè) TURN 服務(wù)器工作流程的示例,二者之間并不是直接的 P2P 通信,所有的信息都經(jīng)過(guò)了 TURN 服務(wù)器進(jìn)行轉(zhuǎn)發(fā)。
2.4 Interactive Connectivity Establishment: ICE
在建立了很多 STUN 和 TURN 服務(wù)器后,從 A 到 B 之間的路徑有了非常多的選擇,為了更好的處理這些路徑,人們提出了 ICE。ICE 會(huì)收集所有可用的通信路徑作為“候選人”(ICE Candidates),有可能是本地 IP 地址、STUN 和 TURN 服務(wù)器提供的地址等等。收集到的所有地址都將放入 SDP 中,再送到對(duì)端,對(duì)端通過(guò)解析 SDP 來(lái)了解我方提供的重要信息。因此,ICE 是 WebRTC 中非常關(guān)鍵的組成部分。
2.5 Session Description Protocol: SDP
SDP 是一種用于表述 ICE Candidates 的格式,它描述了網(wǎng)絡(luò)選項(xiàng)、媒體選項(xiàng)、安全選項(xiàng)和其他很多信息,開(kāi)發(fā)者甚至可以自定義 SDP 內(nèi)容。實(shí)際上 SDP 并不是一種協(xié)議,只是一種數(shù)據(jù)格式,但 SDP 是 WebRTC 中最重要的幾個(gè)概念之一。它的設(shè)計(jì)目的是將用戶(hù)產(chǎn)生的 SDP 送至其他端,送的方式并不關(guān)心。
2.6 信令交換:Signaling
Signaling 過(guò)程是將用戶(hù)產(chǎn)生的 SDP 通過(guò)某種方式傳遞給想要通信的那方,如上所述,以何種方式傳遞并不重要。很多人通過(guò) Websockets 或者 socket io 來(lái)傳遞 SDP 信息,這個(gè)過(guò)程就是 Signal SDP。盡管要找到所有的 ICE candidate 是耗費(fèi)時(shí)間的,但一旦完成了這個(gè)過(guò)程,下一步就是創(chuàng)建一個(gè) SDP,進(jìn)而生成一個(gè) QR code 并把 QR code 公布到 twitter 上,其他人掃描了這個(gè)二維碼就可以獲取相應(yīng)的 SDP。這個(gè)過(guò)程是通過(guò) twitter、QR code、Whatsapp、WebSockets、還是 HTTP 請(qǐng)求都不重要,因?yàn)閷?shí)際上就是將一個(gè)長(zhǎng)字符串傳遞給其他人罷了。簡(jiǎn)而言之,Signaling 就是將 SDP 信息傳遞給另外一方。
工作流程總結(jié)
A 想要和B建立連接;
A 創(chuàng)建了一個(gè) offer,它尋找所有的 ICE candidate、安全選項(xiàng)、音視頻選項(xiàng)等并創(chuàng)建 SDP,簡(jiǎn)單來(lái)說(shuō)這個(gè) offer 就是 SDP;
A 將 SDP 信令傳遞給 B(Signaling);
B 根據(jù) A 的 offer 進(jìn)行設(shè)置,并創(chuàng)建應(yīng)答(answer);
B 將 Answer 信令傳遞給 A(Signaling);
連接建立。
3. Demo
作者詳細(xì)講述了一個(gè) Demo 程序的編寫(xiě),該程序可以:
在兩個(gè)瀏覽器間進(jìn)行通信(瀏覽器 A 和瀏覽器 B);
A 創(chuàng)建一個(gè) offer(SDP),并設(shè)置它為本地描述;
B 接收一個(gè) offer 并設(shè)置它為遠(yuǎn)端描述;
B 創(chuàng)建一個(gè) answer 并設(shè)置它為本地描述,并將其傳遞給 A;
A 接收 answer 并設(shè)置它為遠(yuǎn)端描述;
建立連接、建立數(shù)據(jù)通道、交換數(shù)據(jù)。
源碼:https://github.com/hnasr/javascript_playground/tree/master/webrtc
4. WebRTC的優(yōu)缺點(diǎn)
1. 優(yōu)點(diǎn)
P2P 通信是非常棒的,對(duì)于高帶寬內(nèi)容可以有降低的延遲。P2P 是最快的路徑,不需要經(jīng)過(guò)其他的第三方進(jìn)行通信。即使通互聯(lián)網(wǎng)傳輸要經(jīng)過(guò)大量的路由器,但如果內(nèi)容已經(jīng)被加密了所有的路由器都不會(huì)查看內(nèi)容,它們會(huì)直接傳遞數(shù)據(jù)包,所以 P2P 是非常好的通信方式。對(duì)于高帶寬內(nèi)容,它們通過(guò) UDP 直接被“送入”和“推出”,通過(guò) P2P UDP 傳遞這些內(nèi)容(特別是視頻內(nèi)容),用戶(hù)將收獲最好的性能。
標(biāo)準(zhǔn)可用的 APIWebRTC 有一套非常標(biāo)準(zhǔn)、非常優(yōu)雅的 API,可以直接在瀏覽器中應(yīng)用,不需要安裝其他的包、也不需要用多余的開(kāi)發(fā)工具。
2. 缺點(diǎn)
需要維護(hù) STUN 和 TURN 服務(wù)器在某些情況下 P2P 不能工作,你仍需要一個(gè) TURN 服務(wù)器。但維護(hù) STUN 和 TURN 服務(wù)器需要耗費(fèi)大量的人力物力,特別是 TURN 服務(wù)器。因?yàn)槟闶紫纫ㄥX(qián)維護(hù)一個(gè) Public IP,并且必須維護(hù)這個(gè)服務(wù)器使其可以正常啟動(dòng)和運(yùn)行。作者個(gè)人認(rèn)為與其花費(fèi)這種代價(jià),不如自己建立一個(gè)擁有全部控制權(quán)的服務(wù)器,進(jìn)行反向代理。
在參與者過(guò)多的情況下,P2P 會(huì)崩潰假設(shè)有 100 個(gè)人想要相互交流,你會(huì)創(chuàng)建 P2P 連接嗎?那會(huì)是幾百乘幾百的連接量,因?yàn)槊總€(gè)人都需要連接到其他任何一個(gè)用戶(hù),這將是非常大規(guī)模的。但如果你有一個(gè)集中式服務(wù)器,每個(gè)用戶(hù)只需要和這個(gè)服務(wù)器建立一個(gè)連接,你可以通過(guò)這個(gè)服務(wù)器控制所有的流量,這明顯是一種更好的方式。所以 WebRTC 有時(shí)候無(wú)法用在游戲上,你沒(méi)辦法利用 WebRTC 來(lái)創(chuàng)建一個(gè)多用戶(hù)游戲,當(dāng)然 3 個(gè)用戶(hù)是可以的,但幾百個(gè)用戶(hù)作者認(rèn)為是無(wú)法實(shí)現(xiàn)的。
5. 擴(kuò)展內(nèi)容
5.1 Media API
getUserMedia 函數(shù)可以用于獲取麥克風(fēng)和攝像頭,進(jìn)而獲得一個(gè)流(stream),這個(gè)流的內(nèi)容會(huì)通過(guò)RTCPConnection.addTrack(stream)送入 RTC 連接中。理論上你可以用數(shù)據(jù)通道傳遞任何類(lèi)型的數(shù)據(jù),但如果你想要傳遞媒體信息就要用到 stream,這些數(shù)據(jù)的傳遞將使用不同的協(xié)議。
更多內(nèi)容可參考:https://www.html5rocks.com/en/tutorials/webrtc/basics/
5.2 onIceCandidate 和 addIceCandidate
這兩個(gè)函數(shù)可用于在新的 Candidate 加入或離開(kāi)時(shí)維護(hù)連接。用戶(hù)每次從系統(tǒng)獲取一個(gè) ICE Candidate 時(shí),onIceCandidate 函數(shù)就會(huì)被調(diào)用。onIceCandidate 函數(shù)將告知用戶(hù)“在 SDP 已經(jīng)被創(chuàng)建后,又有了新的 Candidate”。新的 Candidate 將被告知對(duì)端,告知的方式可以是 Signaling,也可以直接通過(guò)同一個(gè) SDP 連接。對(duì)端通過(guò) addIceCandidate 函數(shù)將新的 Candidate 加入 SDP。
5.3 自定義 TURN 和 STUN 服務(wù)器
在創(chuàng)建 RTCP 連接時(shí),可以選擇傳遞配置信息,下圖為一個(gè)配置信息示例?;旧嫌脩?hù)可以自定義 ICE 服務(wù)器,其中有很多可選項(xiàng)。
此外,有一個(gè)開(kāi)源庫(kù)也可以幫助大家創(chuàng)建屬于自己的 TURN 服務(wù)器,地址:https://github.com/coturn/coturn
5.4 公共 STUN 服務(wù)器
作者給出了部分 Google 提供的公共服務(wù)器,可供開(kāi)發(fā)人員參考:
stun1.l.google.com:19302
stun2.l.google.com:19302
stun3.l.google.com:19302
stun4.l.google.com:19302
stun.stunprotocol.org:3478
審核編輯 :李倩
-
服務(wù)器
+關(guān)注
關(guān)注
12文章
9160瀏覽量
85425 -
WebRTC
+關(guān)注
關(guān)注
0文章
57瀏覽量
11250
原文標(biāo)題:WebRTC 速成課程
文章出處:【微信號(hào):livevideostack,微信公眾號(hào):LiveVideoStack】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論