<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 第 3 章 ### UDP的構成 1980 年 8 月,緊隨 TCP/IP 之后, UDP( User Datagram Protocol,用戶數據報協議)被 John Postel 加入了核心網絡協議套件 。當時,正值 TCP 和 IP 規范分立為兩個單獨的 RFC。這個時間點非常重要,稍后我們會看到, UDP 的主要功能和亮點并不在于它引入了什么特性,而在于它忽略的那些特性。 UDP 經常被稱為無( Null)協議, RFC 768 描述了其運作機制,全文完全可以寫在一張餐巾紙上。 * 數據報 一個完整、獨立的數據實體,攜帶著從源節點到目的地節點的足夠信息,對這些節點間之前的數據交換和傳輸網絡沒有任何依賴。 數據報( datagram)和分組( packet)是兩個經常被人混用的詞,實際上它們還是有區別的。分組可以用來指代任何格式化的數據塊,而數據報則通常只用來描述那些通過不可靠的服務傳輸的分組,既不保證送達,也不發送失敗通知。正因為如此,很多場合下人們都把 UDP 中 User(用戶)的 U,改成 Unreliable(不可靠)的 U,于是 UDP 就成了“不可靠數據報協議”( Unreliable Datagram Protocol)。這也是為什么把UDP 分組稱為數據報更為恰當的原因。 關于 UDP 的應用,最廣為人知同時也是所有瀏覽器和因特網應用都賴以運作的,就是 DNS( Domain Name System,域名系統)。 DNS 負責把對人類友好的主機名轉換成 IP 地址。可是,盡管瀏覽器有賴于 UDP,但這個協議以前從未被看成網頁和應用的關鍵傳輸機制。 HTTP 并未規定要使用 TCP,但現實中所有 HTTP 實現(以及構建于其上的所有服務)都使用 TCP。 不 過, 這 都 是 過 去 的 事 了。 IETF 和 W3C 工 作 組 共 同 制 定 了 一 套 新 API——WebRTC( Web Real-Time Communication, Web 實時通信)。 WebRTC 著眼于在瀏覽器中通過 UDP 實現原生的語音和視頻實時通信,以及其他形式的 P2P( Peer-to-Peer,端到端)通信。正是因為 WebRTC 的出現, UDP 作為瀏覽器中重要傳輸機制的地位才得以突顯,而且還有了瀏覽器 API !本書將在第 18 章再探討 WebRTC,本章我們先來介紹一下 UDP 協議的工作原理,搞清楚為什么以及什么時候會用到它。 ### 3.1 無協議服務 要理解為什么 UDP 被人稱作“無協議”,必須從作為 TCP 和 UDP 下一層的 IP 協議說起。 IP 層的主要任務就是按照地址從源主機向目標主機發送數據報。為此,消息會被封裝在一個 IP 分組內(圖 3-1),其中載明了源地址和目標地址,以及其他一些路由參數。注意,數據報這個詞暗示了一個重要的信息: IP 層不保證消息可靠的交付,也不發送失敗通知,實際上是把底層網絡的不可靠性直接暴露給了上一層。如果某個路由節點因為網絡擁塞、負載過高或其他原因而刪除了 IP 分組,那么在必要的情況下, IP 的上一層協議要負責檢測、恢復和重發數據。 ![圖 3-1: IPv4 首部( 20 字節)](https://box.kancloud.cn/c686dced25a63b98ee5f0f5cf533c70a_697x267.png) 圖 3-1: IPv4 首部( 20 字節) UDP 協議會用自己的分組結構(圖 3-2)封裝用戶消息,它只增加了 4 個字段:源端口、目標端口、分組長度和校驗和。這樣,當 IP 把分組送達目標主機時,該主機能夠拆開 UDP 分組,根據目標端口找到目標應用程序,然后再把消息發送過去。僅此而已。 ![圖 3-2: UDP 首部( 8 字節)](https://box.kancloud.cn/786b9789a1f516f34a5bf745b839486d_698x136.png) 圖 3-2: UDP 首部( 8 字節) 事實上, UDP 數據報中的源端口和校驗和字段都是可選的。 IP 分組的首部也有校驗和,應用程序可以忽略 UDP 校驗和。也就是說,所有錯誤檢測和錯誤糾正工作都可以委托給上層的應用程序。說到底, UDP 僅僅是在 IP 層之上通過嵌入應用程序的源端口和目標端口,提供了一個“應用程序多路復用”機制。明白了這一點,就可以總結一下 UDP 的無服務是怎么回事了。 * 不保證消息交付 不確認,不重傳,無超時。 * 不保證交付順序 不設置包序號,不重排,不會發生隊首阻塞。 * 不跟蹤連接狀態 不必建立連接或重啟狀態機。 * 不需要擁塞控制 不內置客戶端或網絡反饋機制。 TCP 是一個面向字節流的協議,能夠以多個分組形式發送應用程序消息,且對分組中的消息范圍沒有任何明確限制。因此,連接的兩端存在一個連接狀態,每個分組都有序號,丟失還要重發,并且要按順序交付。相對來說, UDP 數據報有明確的限制:數據報必須封裝在 IP 分組中,應用程序必須讀取完整的消息。換句話說,數據報不能分片。 UDP 是一個簡單、無狀態的協議,適合作為其他上層應用協議的輔助。實際上,這個協議的所有決定都需要由上層的應用程序作出。不過,在急著去實現一個協議來扮演 TCP 的角色之前,你還應該認真想一想這里涉及的復雜細節,比如 UDP 要與很多中間設備打交道( NAT 穿透),再想一想設計網絡協議的那些最佳實踐。如果沒有周密的設計和規劃,一流的構想也可能淪為二流的 TCP 實現。 TCP 中的算法和狀態機已經經過了幾十年的磨合與改進,而且吸收幾十種并不那么容易重新實現的機制。 ### 3.2 UDP與網絡地址轉換器 令人遺憾的是, IPv4 地址只有 32 位長,因而最多只能提供 42.9 億個唯一 IP 地址。1990 年代初,互聯網上的主機數量呈指數級增長,但不可能所有主機都分配一個唯一的 IP 地址。 1994 年,作為解決 IPv4 地址即將耗盡的一個臨時性方案, IP 網絡地址轉換器( NAT, Network Address Translator) 規范出臺了,這就是 RFC 1631。建議的 IP 重用方案就是在網絡邊緣加入 NAT 設備,每個 NAT 設備負責維護一個表,表中包含本地 IP 和端口到全球唯一(外網) IP 和端口的映射(圖 3-3)。這樣,NAT 設備背后的 IP 地址空間就可以在各種不同的網絡中得到重用,從而解決地址耗盡問題。 ![圖 3-3: IP 網絡地址轉換器](https://box.kancloud.cn/9a7fed5b25ca6c4b7bc829095cede99a_694x368.png) 圖 3-3: IP 網絡地址轉換器 然而,這個臨時性的方案居然就那么一直沿用了下來(這種現象倒也不鮮見)。新增的 NAT 設備不僅立桿見影地解決了地址耗盡問題,而且迅速成為很多公司及家庭代理和路由器、安全裝置、防火墻,以及其他許多硬件和軟件設備中的內置組件。 NAT 不再是個臨時性方案,它已經成了因特網基礎設施的一個組成部分。 >保留的私有網絡地址范圍 作為監管全球 IP 地址分配的機構, IANA( Internet Assigned Numbers Authority,因特網號碼分配機構)為私有網絡保留了三段 IP 地址,這些 IP 地址經常可以在NAT 設備后面的內網中看到。 表3-1:保留的IP地址范圍 |IP地址范圍 |地址數量| |--|--| |10.0.0.0~10.255.255.255 |16777216| |172.16.0.0~172.31.255.255 |1048576| |192.168.0.0~192.168.255.255| 65536| 其中一段(或所有三段) IP 地址是不是很眼熟?在你的局域網中,路由器給你的計算機分配的 IP 地址很可能位于其中一段。這個地址就是你在內網中的私有地址。在需要與外網通信時, NAT 設備會將它們轉換成外網地址。 為防止路由錯誤和引起不必要的麻煩,不允許給外網計算機分配這些保留的私有IP 地址。 #### 3.2.1 連接狀態超時 NAT 轉換的問題(至少對于 UDP 而言)在于必須維護一份精確的路由表才能保證數據轉發。 NAT 設備依賴連接狀態,而 UDP 沒有狀態。這種根本上的錯配是很多UDP 數據報傳輸問題的總根源。況且,客戶端前面有很多個 NAT 設備的情況也不鮮見,問題由此進一步惡化了。 每個 TCP 連接都有一個設計周密的協議狀態機,從握手開始,然后傳輸應用數據,最后通過明確的信號確認關閉連接。在這種設計下,路由設備可以監控連接狀態,根據情況創建或刪除路由表中的條目。而 UDP 呢,沒有握手,沒有連接終止,實際根本沒有可監控的連接狀態機。 發送出站 UDP 不費事,但路由響應卻需要轉換表中有一個條目能告訴我們本地目標主機的 IP 和端口。因此,轉換器必須保存每個 UDP 流的狀態,而 UDP 自身卻沒有狀態。 更糟糕的是, NAT 設備還被賦予了刪除轉換記錄的責任,但由于 UDP 沒有連接終止確認環節,任何一端隨時都可以停止傳輸數據報,而不必發送通告。為解決這個問題, UDP 路由記錄會定時過期。定時多長?沒有規定,完全取決于轉換器的制造商、型號、版本和配置。因此,對于較長時間的 UDP 通信,有一個事實上的最佳做法,即引入一個雙向 keep-alive 分組,周期性地重置傳輸路徑上所有 NAT 設備中轉換記錄的計時器。 ##### TCP 超時和 NAT >從技術角度講, NAT 設備不需要額外的 TCP 超時機制。 TCP 協議就遵循一個設計嚴密的握手與終止過程,通過這個過程就可以確定何時需要添加或刪除轉換記錄。遺憾的是,實際應用中的 NAT 設備給 TCP 和 UDP 會話應用了類似的超時邏輯。 這樣就導致 TCP 連接有時候也需要雙向 keep-alive 分組。如果你的 TCP 連接突然斷開,那很有可能就是中間 NAT 超時造成的。 #### 3.2.2 NAT穿透 不可預測的連接狀態處理是 NAT 設備帶來的一個嚴重問題,但更為嚴重的則是很多應用程序根本就不能建立 UDP 連接。尤其是 P2P 應用程序,涉及 VoIP、游戲和文件共享等,它們客戶端與服務器經常需要角色互換,以實現端到端的雙向通信。 NAT 帶來的第一個問題,就是內部客戶端不知道外網 IP 地址,只知道內網 IP 地址。 NAT 負責重寫每個 UDP 分組中的源端口、地址,以及 IP 分組中的源 IP 地址。 如果客戶端在應用數據中以其內網 IP 地址與外網主機通信,連接必然失敗。所謂的“透明”轉換因此也就成了一句空話,如果應用程序想與私有網絡外部的主機通信,那么它首先 必須知道自己的外網 IP 地址。 然而,知道外網 IP 地址還不是實現 UDP 傳輸的充分條件。任何到達 NAT 設備外網 IP的分組還必須有一個目標端口,而且 NAT 轉換表中也要有一個條目可以將其轉換為內部主機的 IP 地址和端口號。如果沒有這個條目(通常是從外網傳數據進來),那到達的分組就會被刪除(圖 3-4)。此時的 NAT 設備就像一個分組過濾器,除非用戶通過端口轉發(映射)或類似機制配置過,否則它無法確定將 . 分組發送給哪臺內部主機。 ![圖 3-4:由于沒有映射規則,入站分組直接被刪除](https://box.kancloud.cn/d8846ee4a55957a1f24a50bdcdf9ddbf_695x316.png) 圖 3-4:由于沒有映射規則,入站分組直接被刪除 需要注意的是,上述行為對客戶端應用程序不是問題。客戶端應用程序基于內部網絡實現交互,會在交互期間建立必要的轉換記錄。不過,如果隔著 NAT 設備,那客戶端(作為服務器)處理來自 P2P 應用程序( VoIP、游戲、文件共享)的入站連接時,就必須面對 NAT 穿透問題。 為解決 UDP 與 NAT 的這種不搭配,人們發明了很多穿透技術( TURN、 STUN、ICE),用于在 UDP 主機之間建立端到端的連接。 #### 3.2.3 STUN、 TURN與ICE STUN( Session Traversal Utilities for NAT)是一個協議(RFC 5389),可以讓應用程序發現網絡中的地址轉換器,發現之后進一步取得為當前連接分配的外網 IP 地址和端口(圖 3-5)。為此,這個協議需要一個已知的第三方 STUN 服務器支持,該服務器必須架設在公網上。 ![圖 3-5: STUN 查詢外網 IP 地址和端口](https://box.kancloud.cn/28ead777461c641e15716d1f76daa093_693x204.png) 圖 3-5: STUN 查詢外網 IP 地址和端口 假設 STUN 服務器的 IP 地址已知(通過 DNS 查找或手工指定),應用程序首先向STUN 服務器發送一個綁定請求。然后, STUN 服務器返回一個響應,其中包含在外網中代表客戶端的 IP 地址和端口號。這種簡單的方式解決了前面討論的一些問題: - 應用程序可以獲得外網 IP 和端口,并利用這些信息與對端通信; - 發送到 STUN 服務器的出站綁定請求將在通信要經過的 NAT 中建立路由條目,使得到達該 IP 和端口的入站分組可以找到內網中的應用程序; - STUN 協議定義了一個簡單 keep-alive 探測機制,可以保證 NAT 路由條目不超時。 有了這個機制,兩臺主機端需要通過 UDP 通信時,它們首先都會向各自的 STUN服務器發送綁定請求,然后分別使用響應中的外網 IP 地址和端口號交換數據。 但在實際應用中, STUN 并不能適應所有類型的 NAT 和網絡配置。不僅如此,某些情況下 UDP 還會被防火墻或其他網絡設備完全屏蔽。這種情況在很多企業網很常見。為解決這個問題,在 STUN 失敗的情況下,我們還可以使用 TURN( Traversal Using Relays around NAT)協議( RFC 5766)作為后備。 TURN 可以在最壞的情況下跳過 UDP 而切換到 TCP。 TURN 中的關鍵詞當然是中繼( relay)。這個協議依賴于外網中繼設備(圖 3-6)在兩端間傳遞數據。 ![圖 3-6: TURN 中繼服務器](https://box.kancloud.cn/ebcb1ee4b85b68a649c4620c007bd5ba_694x196.png) 圖 3-6: TURN 中繼服務器 - 兩端都要向同一臺 TURN 服務器發送分配請求來建立連接,然后再進行權限協商。 - 協商完畢,兩端都把數據發送到 TURN 服務器,再由 TURN 服務器轉發,從而實現通信。 很明顯,這就不再是端對端的數據交換了! TURN 是在任何網絡中為兩端提供連接的最可靠方式,但運維 TURN 服務器的投入也很大。至少,為滿足傳輸數據的需要,中繼設備的容量必須足夠大。因此,最好在其他直連手段都失敗的情況下,再使用 TURN。 ##### 現實中的 STUN 和 TURN >谷歌的 libjingle 是一個用 C++ 開發的用于構建端到端應用程序的開源庫,負責在后臺實現 STUN、 TURN 和 ICE 協商。谷歌聊天軟件 Google Talk 使用的就是這個庫,其文檔也為我們考量現實中的 STUN 與 TURN 性能提供了有價值的參考: >- 92% 的時間可以直接連接( STUN); >- 8% 的時間要使用中繼器( TURN)。 >由于 NAT 設備遍地都是,相當一部分用戶不能通過 STUN 直接建立 P2P 連接。若要提供可靠的 UDP 服務,現實中必須同時支持 STUN 和 TURN。 構建高效的 NAT 穿透方案可不容易。好在,我們還有 ICE( Interactive Connectivity Establishment)協議( RFC 5245)。 ICE 規定了一套方法,致力于在通信各端之間建立一條最有效的通道(圖 3-7):能直連就直連,必要時 STUN 協商,再不行使用 TURN。 ![圖 3-7: ICE 先后嘗試直連、 STUN 和 TURN](https://box.kancloud.cn/a0445d7631a32d3ea9770c20d347479b_694x282.png) 圖 3-7: ICE 先后嘗試直連、 STUN 和 TURN 實際開發中,如果你想構建基于 UDP 的 P2P 應用程序,絕對應該選擇現有的平臺API,或者實現了 ICE、 STUN 和 TURN 的第三方庫。好了,了解了這些協議的用途之后,接下來自然就要考慮安裝和配置了! ### 3.3 針對UDP的優化建議 UDP 是一個簡單常用的協議,經常用于引導其他傳輸協議。事實上,UDP 的特色在于它所省略的那些功能:連接狀態、握手、重發、重組、重排、擁塞控制、擁塞預防、流量控制,甚至可選的錯誤檢測,統統沒有。這個面向消息的最簡單的傳輸層在提供靈活性的同時,也給實現者帶來了麻煩。你的應用程序很可能需要從頭實現上述幾個或者大部分功能,而且每項功能都必須保證與網絡中的其他主機和協議和諧共存。 與內置流量和擁塞控制以及擁塞預防的 TCP 不同, UDP 應用程序必須自己實現這些機制。擁塞處理做得不到位的 UDP 應用程序很容易堵塞網絡,造成網絡性能下降,嚴重時還會導致網絡擁塞崩潰。如果你想在自己的應用程序中使用 UDP,務必要認真研究和學習當下的最佳實踐和建議。 RFC 5405 就是這么一份文檔,它對設計單播 UDP 應用程序給出了很多設計建議,簡述如下: - 應用程序必須容忍各種因特網路徑條件; - 應用程序應該控制傳輸速度; - 應用程序應該對所有流量進行擁塞控制; - 應用程序應該使用與 TCP 相近的帶寬; - 應用程序應該準備基于丟包的重發計數器; - 應用程序應該不發送大于路徑 MTU 的數據報; - 應用程序應該處理數據報丟失、重復和重排; - 應用程序應該足夠穩定以支持 2 分鐘以上的交付延遲; - 應用程序應該支持 IPv4 UDP 校驗和,必須支持 IPv6 校驗和; - 應用程序可以在需要時使用 keep-alive(最小間隔 15 秒)。 設計新傳輸協議必須經過周密的考慮、規劃和研究,否則就是不負責任。要盡可能利用已有的庫或框架,這個庫或框架應該考慮了 NAT 穿透,而且能夠與其他并發的網絡流量和諧共存。 我很高興告訴大家: WebRTC 就是符合這些要求的框架!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看