程序員應該了解的網路協議
Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。
網路棧執行著看似不可能的事情。它能夠在不可靠的網路上可靠地傳輸信息,並且通常沒有可監測到的停斷。網路棧可以平穩地適應網路阻塞,提供數十億的有效節點。它能夠在損壞的網路基礎架構上進行數據包路由,並且能夠按照正確的順序重新組合無序的數據包。網路棧還能滿足嚴格的物理硬體要求,如平衡乙太網電纜兩端的電荷。這一切都很好地運行著,用戶從來沒有聽說過,甚至大多數程序員也不知道它是如何工作的。
網路路由
在過去的模擬電話中,撥打電話意味著從電話到朋友那裡具有真實的電線連接。就好像電線直接從你那裡連接到他們那裡。雖然沒有這樣實際的電線,當然——連接是通過複雜的開關係統進行的——但它與電線相當。
有太多的Internet節點可以這樣工作。但是我們不能從每個機器到要和其對話的其他機器提供直接、不間斷的路徑連接。
相反,數據是桶式的,從一個路由器傳遞到下一個路由器,在鏈路中,每個路由器使數據更靠近目的地。 我的筆記本電腦和谷歌伺服器之間的每個路由器連接到其他多個路由器,這樣維護著一個粗略的路由表,顯示哪些路由器更接近互聯網的哪個部分。當一個數據包到達的目的地是谷歌伺服器時,路由表中的快速查找功能告訴
數據包
走哪個
路由器能夠更加靠近谷歌伺服器。數據包很小,所以鏈路中的每個路由器只會對下一個路由器佔用一小段時間。路由解決的問題可分為兩個子問題。首先,解決:數據的目的地是什麼?這由IP、互聯網協議、IP地址處理。IPv4仍然是IP的最常見版本,僅提供32位地址空間。現在已完全分配,因此將一個節點添加到公共Internet需要重用現有的IP地址。 IPv6允許2128個地址(約1038個),但截至2017年只有約20%被使用。
現在我們有地址,我們需要知道如何通過互聯網將數據包路由到其目的地。路由過程發生得很快,所以沒有時間查詢遠程資料庫的路由信息。例如,Cisco ASR 9922路由器的最大容量為每秒160兆比特。假設完整的1500位元組數據包(12,000位),也即是在一個19英寸機架中每秒13333333333個數據包!
為了快速路由,路由器維護路由表,指示各種IP地址組的路徑。當新數據包到達時,路由器在表中查找它,告訴它哪個對等體最接近目的地。它將數據包發送給該對等體並移動到下一個。BGP的工作是在不同路由器之間傳遞此路由表信息,確保是最新的路由表。
不幸的是,IP和BGP一起並不能搭建有效的網路,因為它們沒有辦法可靠地傳輸數據。如果路由器過載並丟包,我們需要一種方法來檢測丟失並請求重傳。
分組交換
如果互聯網通過路由器將網路中彼此之間的數據相互連接起來,那麼當數據量大時會發生什麼? 如果我們請求88.5 MB的JavaScript Birth & Death視頻,應該怎麼辦?
我們可以嘗試設計一個網路,其中88.5 MB文檔從Web伺服器發送到第一個路由器,然後到第二個,以此類推。不幸的是,該網路在互聯網規模上甚至內部網路規模都無法工作。
首先,計算機是具有有限存儲量的有限個機器。如果一個給定的路由器只有88.4 MB的緩衝區可用,它根本不能存儲88.5 MB的視頻文件。因
此數據將被丟棄,更糟糕的是,這沒有任何跡象。如果路由器十分繁忙而丟失數據,那麼無法花時間告訴我丟棄
的數據。
二是電腦不可靠
。有時,路由節點失敗。 有時,船舶的錨點會意外損壞關乎大量互聯網數據的水下光纖電纜。
由於這些原因,我們不會通過互聯網發送88.5 MB的郵件。相反,我們將它們分解成數據包,通常在每個1,400位元組左右。我們的視頻文件將分為63,214個單獨的數據包進行傳輸。
無序數據包
通過數
據包捕獲工具Wireshark測量JavaScript的Birth & Death實際傳輸情況,我收到的總共接收到61,807個數據包,每個是1,432個位元組。將這兩個相乘,我們得到88.5兆位元組數據,這是視頻的大小。(這不包括各種協議增加的開銷,如果包括,我們會看到稍高一些的數據大小)
傳輸是通過HTTP完成的,一種通過TCP分層的協議,即傳輸控制協議。它只需要14秒,因此數據包的平均速率約為每秒4,400,或每包約250微秒。在14秒鐘內,我的機器收到全部的61,807個數據包,可能是無序的,當它們進來時將它們重新組裝成完整的文件。
使用最簡單的機制重新組裝TCP包,可以想像:一個計數器。每個數據包發送時都會分配一個序列號。在接收端,按照序列號對數據包進行排序。一旦這一切都順利,沒有差別,我們知道文件是完整的。(實際的TCP序列號往往不是整數,每次只增加1,但這個細節在這裡並不重要。)
我們怎麼知道文件何時完成?TCP並不能回答,因為這是更高級協議的工作。例如,HTTP會有包含一個「Content-Length」頭,指定響應長度(以位元組為單位)。客戶端讀取Content-Length,然後繼續讀取TCP數據包,將其組裝成原始順序,直到它具有Content-Length指定的長度。這就是HTTP頭(和大多數其他協議頭)先於響應有效負載到來的一個原因:否則,我們不知道有效負載的大小。
當我們在這裡說「客戶」時,我們實際上在談論整個電腦接收端。TCP重組發生在內核內部,因此Web瀏覽器和curl和wget等應用程序不必手動重新組合TCP數據包。但內核不處理HTTP,因此應用程序必須了解Content-Length頭文件,並知道要讀取的位元組數。
使用序列號和分組重新排序,即使數據包無序到達,我們也可以發送大的位元組序列。但是如果一個數據包在傳輸中丟失了,會在HTTP響應中留下一個空白嗎?
傳輸窗口和慢啟動
我們一邊
開著Wireshark,一邊下載「Death and Death of JavaScript」。通過瀏覽捕獲的結果,我們看到源源不斷的數據包被接收到。
例如,序列號為563,321的數據包到達。像所有TCP數據包一樣,它有一個「下一個序列號」,它是用於下一個數據包的數字。該數據包的「下一個序列號」為564,753。事實上,下一個數據包的序列號也的確為564,753,所以一切都很好。當連接達到一定速度後,每秒鐘會發生數千次傳輸。
有時候,我的電腦會向伺服器發送一條消息,例如「我已收到所有數據包,包括數據包564,753」。這是一個ACK,用於確認:我的電腦確認收到伺服器的數據包。在一個新的連接上,Linux內核每十個數據包發送一個ACK。這由TCP_INIT_CWND常量控制,我們可以在Linux內核的源代碼中定義它們。(TCP_INIT_CWND中的CWND表示擁塞窗口:一次允許的數據量)如果網路擁塞(超載),窗口大小將減少,傳輸數據包變慢)。
十個數據包大約是14 KB,所以我們一次限制在14 KB數據。這是TCP慢啟動的一部分:連接從小擁塞窗口開始。如果沒有數據包丟失,接收機將不斷增加擁塞窗口,允許一次傳輸更多的數據包。
有時,數據包將發生丟失,所以接收窗口會縮小,傳輸速度變慢。通過自動調整擁塞窗口以及一些其他參數,發送方和接收方保持數據移動速度在網路允許的情況下儘可能快,但不會超出限制。
這種情況發生在連接的兩端:每一方都會確認對方的消息,並且每一方都保留自己的擁塞窗口。非對稱窗口允許協議充分利用具有不對稱上行和下行帶寬的網路連接,比如大多數住宅和移動互聯網連接。
可靠傳輸
電
腦不可靠:由計算機組成的網路是不可靠的。在像網際網路這樣的大型網路中,故障是運行的正常部分,必須適應。在分組網路中,這意味著重傳:如果客戶端接收到數據包1和3,但沒有接收到2,則需要請求伺服器重新發送丟失的數據包。
當每秒接收數千個數據包時,如我們的88.5 MB視頻下載,錯誤幾乎是必然的。為了證明,讓我們回到我的Wireshark捕獲的下載。對於數千個數據包,一切正常。每個數據包指定一個「下一個序列號」,後面是另一個包含該數字的數據包。
突然間出了問題。第6,269個數據包具有7,208,745的「下一個序列號」,但該數據包一直沒有到來。相反,序列號為7,211,609的數據包到達。這是一個亂序的數據包:意味著缺少一些東西。
我們不清楚這裡出了什麼問題,也許互聯網上的一個中間路由器過載,也許我的本地路由器超載,也許有人使用微波爐,引入電磁干擾並減慢了我的無線連接。在任何情況下,數據包丟失,唯一的指示是接收到意外的數據包。
TCP沒有特殊的「我丟了一個包!」信息。相反,ACK被巧妙地用於指示丟失。當出現亂序的數據包時,接收機會re-ACK最後一個
好的數據包
——即正確
順序的
最後一個數據包
。實際上,接收機正在說「我收到了我正在確認的數據包5,之後也收到了一些信息,但是我知道它不是數據包6,因為它與數據包5中的下一個序列號不匹配。如果兩個數據包在傳輸過程中被交換了順序,則會導致單個額外的ACK,並且在收到無序數據包後,所有內容都將繼續正常傳輸。但是如果數據包真的丟失,意外的數據包將繼續到達,並且接收器將繼續發送最後一個好數據包的ACK副本。這可能導致數百個重複的ACK。
當發送方在一行中看到三個重複的ACK時,它假定下一個數據包丟失並重發。這被稱為TCP快速重傳,因為它比以前基於超時的方法更快。有趣的是,協議本身沒有任何明確的方式說「請立即重發!」相反,協議產生的多個ACK自然用作了觸發器。(一個有趣的思考實驗:如果一些重複的ACK丟失,從未到達發送者,會發生什麼?)
即使網路正常工作,重傳也是很常見的。在我們的88.5 MB視頻下載中,我看到了這一點:
由於持續成功的傳輸,擁塞窗口迅速增加到大約一兆位元組;
1)有幾千個包順序出現,一切正常;
2)一個數據包發生故障;
3)數據繼續以兆位元組/秒計算,但數據包仍然丟失;
4)我的機器發送最後一個已知好數據包的數十個重複的ACK,但是內核還存儲待處理的無序數據包以供稍後重新組裝;
5)伺服器接收重複的ACK並重新發送丟失的數據包;
6)我的客戶端對以前丟失的數據包和隨後由於無序傳輸已經收到的數據包進行ACK。這是通過簡單地確認最近的數據包來完成的,這個數據包也隱含地對所有較早的數據進行ACK。
7)傳輸繼續,但是由於丟失的數據包,擁塞窗口縮小。
這個是正常的,這是發生在我所完成的完整下載的每個捕獲中。儘管我們認為日常生活中網路是不可靠的,甚至TCP在正常條件下也會偶爾失敗,但是TCP仍然是工作得很出色的。
物理網路
所有這些網
絡數據必須通過物理介質(如銅纜,光纖和無線電)傳輸。在物理層協議中,乙太網是最為人所知的。在互聯網的早期,它的受歡迎程度使我們設計其他協議以適應其限制。
首先,讓我們認識物理細節。乙太網與RJ45連接器關係最為緊密,RJ45連接器看起來像更大的八針版本的老式四針手機插孔。它還與cat5(或cat5e或cat6或cat7)電纜相關聯,其中包含絞合成四對的八根匯流排。這裡還存在其他媒體,如我們最有可能在家中遇到的:八根電線纏繞在與8針插孔相連的護套中。
乙太網是物理層協議:它描述了這些位如何變成電纜中的電信號。它也是一個鏈路層協議:它描述了一個節點與另一個節點的直接連接。但是,它純粹是點對點的,並且與數據在網路上的路由無關。在這一層沒有TCP概念中的鏈接概念,也沒有IP地址意義上的可重新分配地址的概念。
作為協議,乙太網有兩個主要的工作。首先,每個設備需要注意它連接的設備,需要協商一些連接速度等參數。
第二,一旦鏈路建立,乙太網需要攜帶數據。像較高級協議如TCP和IP一樣,乙太網數據被分解成數據包。數據包的核心是一個幀,它有一個1500位元組的有效載荷,再加上另外22個位元組的頭信息,如源和目標MAC地址,有效載荷長度和校驗和。
經常處理地址長度與校驗和
的程序員對這些領域是很熟悉的,我們可以想像為什麼它們是必要的。然後將幀包含在另一層的header中以形成完整數據包,但這些header很奇怪。他們開始與模擬電氣系統的基礎現實相反,所以他們看起來似乎不會被放在軟體協議中。完整的乙太網數據包包含:
1)前導碼是56位(7位元組)的交替1和0。設備使用它來同步他們的時鐘,就像當人們計數「1-2-3-GO!」時一樣。計算機無法計數超過1,所以他們通過說「101010101010101010101010101010101010101010101010101010」進行同步。
2)8位(1位元組)起始幀分隔符,為171(10101011為二進位),這標誌著前導碼的結束。請注意,再次重複「10」,直到「11」為止。
3)如上所述,幀本身包含源和目的地址,有效載荷等。
4)96位(12位元組)的間隔間隙,其中line處於空閑狀態。大概這是讓設備休息,因為它們很累。
把這一切放在一起:我們想要的是傳輸我們的1500位元組的數據。我們添加22個位元組來創建一個幀,它指示源,目標,大小和校驗和。我們再添加20個位元組的額外數據來滿足硬體需求,創建完整的乙太網數據包。
你可能認為這就是堆棧的底層。並不是這樣,反而因為模擬世界變得越來越複雜,堆棧底層也變得越來越複雜。
當網路遇見現實世界
數
字系統不是真實存在的;所有一切都是模擬的。
假設我們有一個5伏CMOS系統。(CMOS是一種數字系統,如果你不熟悉也不用擔心)這意味著一個完全開啟的信號將是5伏,一個完全關閉的信號將是0。但沒有什麼是永遠完全關閉或完全關閉的,因為物理世界不允許。實際上,我們的5伏CMOS系統將會考慮將高於1.67伏的任何值記為1,而低於1.67的任何值都將為0。(1.67是5的1/3,我們不用擔心為什麼閾值是1/3,如果你想深入研究,
維基百科
有一篇相關文章!而且,乙太網不是CMOS甚至不與CMOS相關,CMOS和其1/3的值僅用於簡單的說明。)我們的乙太網數據包必須通過物理線路,這意味著更改電線上的電壓。乙太網是一個5伏系統,所以我們期望乙太網協議中的每1位為5伏,每個0位為0伏。但是有兩個竅門:首先,電壓範圍是-2.5 V到+2.5 V。其次,更奇妙的是,每組8位在到達電線之前就會擴展成10位。
有256個可能的8位值和1024個可能的10位值,因此可以將其映射到表中。每個8位位元組可以映射到四個不同的10位模式中的任一個,每個模式將被轉回到接收端的同一個8位位元組。例如,10位值00.0000.0000可能映射到8位值0000.0000。但也可能10位的值10.1010.1010也映射到0000.0000。當乙太網設備看到00.0000.0000或10.1010.1010時,它們將被理解為位元組0(二進位0000.0000)。(警告:現在有一些電子相關的術語。)
這是為了提供特別的模擬電路需求:平衡設備中的電壓。假設這個8位到10位編碼不存在,我們發送一些恰好全是1的數據。乙太網的電壓範圍為-2.5至+2.5伏,因此我們將乙太網電纜的電壓保持在+2.5 V,不斷從另一側拉電子。
為什麼我們關心一方比另一方更多的電子?因為模擬世界是混亂的,會造成各種不良影響。要採取一個:它可以對低通濾波器中使用的電容器進行充電,從而在信號電平本身產生偏移,最終導致位錯誤。這些錯誤將需要時間來積累,但是我們不希望我們的網路設備在兩年的正常運行時間之後突然破壞數據,就因為我們碰巧發送了1比0多的二進位數。(電子相關術語在這裡結束。)
通過使用8b / 10b編碼,乙太網可以平衡通過線路發送的0和1數量,即使我們發送大多數為1或大多數為0的數據。硬體跟蹤0到1的比例,將溢出的8位位元組映射到10位表的不同選項,以實現電氣平衡。(較新的乙太網標準,如10 GB乙太網,使用不同的和更複雜的編碼系統。)
我們就討論到這裡,因為我們已經超出了可以被視為編程的範圍,但是還有更多的協議來適應物理層。在許多情況下,硬體問題的解決方案在於軟體本身,如在用於校正DC偏移的8b / 10b編碼的情況下。這對程序員來說可能有點令人不安:我們喜歡假裝我們的軟體生活在一個完美的柏拉圖式世界,沒有粗俗的物理缺陷。實際上,一切都是模擬的,並且限制著每個人的工作和軟體的複雜性。
互聯網路棧
互
聯網協議最好被理解為層堆棧。乙太網提供物理數據傳輸和兩個點對點設備之間的鏈接。IP提供了定址層,允許路由器和大型網路存在,但它是無連接的。分組數據被發射到乙太網中,但沒有指示是否到達的功能。TCP通過使用序列號,通過確認和重傳來添加一層可靠的傳輸。
最後,像HTTP這樣的應用層協議在TCP之上。在這個層面上,我們能夠定址,並且似乎實現了可靠傳輸和持續連接。IP和TCP將應用程序開發人員從不斷重新實現數據包重傳和定址等工作中解放出來。
這些層的獨立性很重要。例如,當我的88.5 MB視頻傳輸丟包時,互聯網的骨幹路由器不知道,只有我的機器和網路伺服器知道。來自我計算機的數十個重複ACK都被完全路由在丟失原始數據包的相同路由基礎設施上。負責丟棄丟包路由器的也可能是幾毫秒後更換的路由器。這是理解互聯網的重要一點:路由基礎架構並不了解TCP,它只有路線。(雖然有一些例外,但通常是這樣的。)
協議棧的層獨立運行,但它們不是獨立設計的。更高級別的協議往往建立在較低級別的協議上:HTTP建立在基於IP的TCP上,IP建立在乙太網上。較低層次的設計決策往往影響更高層次的決策,甚至影響數十年後的決策。
乙太網是傳統的,涉及到物理層,所以它需要設置基本參數。乙太網有效載荷最多為1500位元組。
IP包需要配置在乙太網幀中。IP具有20位元組的最小報頭,因此IP數據包的最大有效負載為1,500 - 20 = 1,480位元組。
同樣,TCP分組需要適應IP分組。TCP也具有20位元組的最小報頭,最大TCP有效負載為1,480 - 20 = 1,460位元組。實際上,其他header和協議可以進一步減少。1,400是一個保守的TCP有效載荷大小。
1,400位元組的限制影響現代協議的設計。例如,HTTP請求通常很小,如果我們將它們分配於一個數據包而不是兩個數據包,那麼我們可以減少丟失部分請求的可能性,同時可以減少TCP重傳的可能性。為了從HTTP請求中擠出位元組,HTTP / 2指定了頭文件的壓縮,這通常很小。沒有來自TCP,IP和乙太網的上下文,這似乎很愚蠢:為什麼添加到協議的header
壓縮
來只保存幾個位元組?因為按照HTTP / 2規範在第2節的介紹,壓縮允許「許多請求被壓縮成一個數據包」。HTTP / 2的header壓縮是為了滿足來自IP約束的TCP限制,這些限制來自於20世紀70年代開發的乙太網中的約束,1980年開始實施,並於1983年進行了標準化。
最後一個問題:為什麼乙太網有效載荷大小設置為1500位元組?沒有深刻的理由,這只是一個很好的折衷點。每幀需要42位元組的非有效載荷數據。如果有效載荷最大值只有100位元組,那麼只有70%(100/142)的時間用於發送有效載荷。有效載荷為1500位元組意味著大約有97%(1500/1542)的時間用於發送有效載荷,這是一個很好的效率水平。將數據包大小提高較多的話,則設備需要較大的緩衝區,我們還是很難獲得另外高1%或2%的效率。簡而言之:HTTP / 2
的header壓縮
是由於20世紀70年代後期網路設備的RAM限制而導致的。英文原文:https://www.destroyallsoftware.com/compendium/network-protocols/97d3ba4c24d21147
譯者:精武英雄


※AlphaGo的下一步行動
※翻譯社有4個任務待認領
※月考結果公布
※對一致哈希環的簡單解釋
※Bash / WSL和Windows控制台的新功能(Windows 10 Creators Update)
TAG:Python程序員 |
※兩會代表委員再議「打假」:互聯網平台應該承擔什麼責任?
※安卓手機到底應不應該採用劉海設計?網友的回答亮了
※關於少兒編程家長應該了解……
※《復仇者聯盟3》上映前你應該了解的信息匯總
※我們應該與世界和解
※程序員春節期間應該做的幾件事
※關於網聯,這些你都應該知道
※快播掌門人王欣歸來,網友評論:欠王總的會員,應該還了!
※練瑜伽的注意事項 初學者應該了解
※編程初學者應該看什麼樣的書?
※親歷博世一年一度的產業鏈盛會,除了成立全新智能網聯事業部,還應該了解哪些信息?
※網友大呼「我應該沒瞎」!韓網友發 Twice 成員「素顏」機場照引熱議
※這應該是《蘭亭序》最好的筆法講解教程了
※分析師認為:蘋果應該面向用戶推出蘋果會員服務
※鹿晗正式回應演唱會二巡問題,期望與街舞選手合作「應該會」答案引人期待
※安安:制定合理方案幫助會員達成訓練目標,這才是教練應該做的!
※中國飛行員應該配什麼槍?
※網路遊戲究竟應該靠什麼贏得未來
※作為新學員,你應該知道……
※嚴重便秘應該怎麼緩解?