前言
這一篇文章主要圍繞了IP協議,ICMP協議和UDP協議展開,希望可以在這里大概做一個總結,將《TCP/IP協議詳解 卷一》書中TCP相關章節前面的內容做一個結束,在下一篇文章專心的去寫TCP相關的內容。
如果不是專業相關鏈路層包括物理相關的內容可以忽略不用關心,唯一需要注意的就是MTU這個概念。所以包括本文在內的三篇文章并沒有就鏈路層詳細展開。
這篇文章開始介紹的是IP協議。它是一個逐跳(Hop-by-hop)協議,提供的服務是在端系統和中間系統之間跨越的,這意味著IP協議所要處理的情況會非常的復雜,作者只是盡力的整理相關的內容,雖然已經非常努力的精煉,但文中所說仍然只占實際內容很少的一部分。
其次介紹的是ICMP協議,作為IP協議的維護管理協議它在非常多的地方為我們提供了服務。實際的內容并不復雜,本文會從兩個經典的程序ping和traceroute為大家做一個介紹。
最后我們簡單談論了UDP協議,并借此討論了廣播和多播。實際UDP本身非常簡單,但是卻是我們要學習TCP一個非常重要的基礎。因為UDP本身非常的簡陋,但是提供可靠性傳輸卻是一件非常復雜的事情。我們必須要了解UDP的不足,然后去思考解決方案,才可以真正理解和記住TCP中多而繁雜的設定。
當今網絡發展的非常迅速,高速而廉價的流量給了我們隨時隨地使用網絡生活娛樂的可能,這常常會讓我們面對很多問題的時候會有理所應當的想法。要正確看待各類協議的設計,代入當時的歷史背景和限制也是非常必要的一件事情。
談一談IP協議
IP協議是TCP/IP協議簇中最為核心的協議,它幾乎壟斷了網絡層。我們熟知的TCP UDP ICMP以及各類上層協議都依賴于IP協議提供的是不可靠且無連接的服務。
IP協議初識
不可靠指的是IP協議只是盡力去傳輸,但不能保證數據報一定能夠到達目的端。這一部分在之前的文章討論過,IP數據報在遇到錯誤的情況下會丟棄該數據報,并以ICMP的方式通知源端。如果你的應用需要可靠性的支持,那么必須在上層協議中實現。(比如TCP)。
而無連接則是指IP協議并不維護任何關于后續數據報的狀態信息。每一個數據報的處理是相互獨立的。
首先,在這里連接的本質并不是說通信的雙方預留了物理信道。物理層是共享的,我們提到過在有連接的TCP通信里,所謂的連接也只是一個虛擬的假設而非我們日常生活里所見的那種實際存在的連接。其次,無連接因為數據報獨立,所以每份數據報都是有邊界的,傳遞的過程我們可以假想成遠遠隔著的兩個人通過空拋傳遞物品。而有連接則維護了整個通訊的狀態,這個通訊是源端和目的端專有的。傳輸的過程我們可以抽象理解成流。什么時候傳輸,傳輸給誰,是否送達以及什么時候結束這些都是明確的。
針對無連接的概念可以舉一個簡單的例子:在socket
編程里,如果你選擇了TCP通信,在調用write
之后數據會從應用進程的緩沖區寫入輸出隊列的緩沖區,發送一份數據報之后會留有一個副本,直到接收到對端的確認才會清除該數據報的緩存。而UDP雖然也有緩沖區的概念,但只是簡單的限制發送數據報的大小,數據報會被直接輸出不做任何操作。
聊聊IP地址
IP地址按照結構可以分為五類
32位的地址通常寫成四個十進制的整數。而區分各類地址最簡單的方式就是看IP地址的第一個十進制整數的大小。
如果熟悉socket
編程的朋友應該記得struct in_addr
是一個表示地址的結構體,其中in_addr_t
是一個無符號長整數。
struct in_addr{
in_addr_t s_addr;
}
實際在早期的版本中struct in_addr
是一個包含多個struct
的union
,這樣的定義方式允許訪問32位IPv4地址中的任意一個或兩個字節(8位或者說其中一個十進制的整數)。在IP地址是A B和C三類地址的時候,非常便于使獲取IP地址的適當字節。然而隨著子網劃分技術的來臨和無類地址編排的出現,各種地址類正在消失,union
也就不再需要。
這里提到的子網劃分是為了解決A類地址和B類地址位主機號分配太多空間的問題,兩類地址可容納的主機數分別是2^24 - 2和2^16 - 2個。現實的情況是在一個網絡中人們不需要這么多的空間,因此我們通過劃分子網來進一步分割。
全為0或1的地址是無效的,因此我們需要去除兩個地址。
為了描述IP地址中子網號和主機號是如何劃分,引入了子網掩碼的概念。子網掩碼的英文單詞是subnet mask,其中mask就是掩碼的意思。這個概念被應用的非常廣泛,比如在iOS開發里,當你設置UIButton的state時,你會發現UIButton.h的頭文件里是這樣定義的
typedef NS_OPTIONS(NSUInteger, UIControlState) {
UIControlStateNormal = 0,
UIControlStateHighlighted = 1 << 0, // used when UIControl isHighlighted is set
UIControlStateDisabled = 1 << 1,
UIControlStateSelected = 1 << 2, // flag usable by app (see below)
UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
UIControlStateApplication = 0x00FF0000, // additional flags available for application use
UIControlStateReserved = 0xFF000000 // flags reserved for internal framework use
};
二進制的0和1可以很簡潔的描述對立的兩種情況。如果需要描述多個結果那么只需要包含相應位數的一串比特流即可,將這串比特流按照某種數據類型翻譯出來,所得即是一個掩碼。
很基礎的一個小知識,但第一次了解時我覺得很驚艷 :)
IP選路
選路是IP最重要的功能之一,基于此數據報能夠被正確的轉發投遞。從概念上來看,IP的選路方式是非常簡單的,特別是對于主機而言。
主機和路由器的區別在于:主機從不把數據報從一個接口轉發到另一個接口,如果一份數據報目的地址不是自己那么就會丟棄它;而路由器則會轉發數據報。對于大部分的主機,我們也可以進行配置讓它變成路由器。
當IP層接收到一個數據報需要發送時,我們用偽碼來說明一下處理的流程。
search 路由表:/* */
if 數據包來自網絡接口:
if 目的IP地址為本機的IP地址之一或IP廣播地址:
送入IP首部指定的協議模塊進行處理
else:
if IP層被設置了路由功能:/* 路由器 */
路由選擇
else:
丟棄數據包 /* 主機 */
else:
這是環回接口
以上可以看出,IP的路由選擇是逐跳進行的。需要注意的是對于中間節點而言,IP層是不知道到達目的端的完整路徑的,它只是提供下一站路由器的IP地址,假定下一站路由器更加接近目的端。
偽碼中出現的路由表是由內核進行維護的,其中提供的信息決定了IP層的決策。輸入netstat -rn
來查看本機的路由表。
表中的每一項都包含了以下信息
- 目的IP地址。它既可以是一個完整的主機地址,也可以是一個網絡地址。具體的類型在標志字段進行了說明:主機地址的主機號非0指示一個特定的主機,網絡地址中主機號為0指定網絡中全部的主機。
用一個網絡地址指定一個路由器而避免為每個主機都指定一個路由器,這是IP路由選擇機制的基本特性。可以極大地縮小路由表的規模
下一跳路由器的IP地址,或是直連的網絡的IP地址。
標志。用以表明目的IP地址的某些特性。
這里簡單介紹幾個常見的標志
U 該路由可以使用。
G 該路由是一個網關(Gateway)。如果沒有這個標志則意味著目的端是直連的。
H 該路由是一個主機。如果缺失則代表這個路由是一個網絡。
D 該路由是由重定向報文創建的
M 該路由已被重定向報文修改
- 為數據報的傳輸指定一個網絡接口。
配合路由表中的信息,IP層能夠有序的進行路由選擇:
搜索路由表,尋找能與目的IP地址完全匹配的表目,即網絡號和主機號同時匹配。如果找到,報文會被發往該表目指示的下一跳路由器或直接相連的網絡接口;如果沒有進行下一步。
搜索路由表,尋找能與目的網絡號相匹配的表目。如果找到,報文會被發往該表目指示的下一跳路由器或直接相連的網絡接口;如果沒有進行下一步。這意味著目的網絡上的所有主機都可以通過這個表目來設置。
搜索路由表,尋找標位
default
的表目。如果找到,報文會被發往該表目指示的下一跳路由器;如果沒有找到,那么該數據報就無法被傳遞,會通過ICMP向源端發送一個主機不可達
或網絡不可達
的錯誤。
這里有兩點需要注意。第一,完整主機地址匹配一定在網絡號匹配之前執行,default
路由只有在前兩步都失敗的情況下才會去執行。第二,我們需要關心default
選項是如何被加入路由表中。
默認路由的設置一般是通過配置文件來設置,在每次重新啟動系統時會主動向路由表中加入該默認選項。除此之外,還可以利用ICMP路由器通告和發現報文來更新自身的路由表。
在考慮這兩種報文的時候,我們必須要以路由器和主機兩個不同的角度去看待。
對于路由器而言,它會定期的廣播(或者多播)通告報文,并且響應其他路由器或主機的路由請求報文。注意查看上圖中通告報文的格式,其中的生存時間默認是30分鐘。但是有一個非常特殊的情況就是,在路由器即將關閉的時候,它會發送一份生存時間為0的通告報文,用以刪除自身負責的路由表目。
對于同時存在多臺路由器的網絡,系統管理員必須為每臺路由器設置優先級,用以給主機指明選擇哪一個IP地址作為默認路由。
對于主機而言,會在系統引導期間每隔 3s 發送一次路由請求報文,在收到一次有效的通告報文以后就會停止繼續發送請求。之后主機會一直監聽其他路由的通告報文,用以更新自己的路由表默認表項。如果在生命周期內沒有接收到默認路由器的通告報文,那么默認路由器就會超時。
對于默認路由器而言,一般會每隔10分鐘發送一份通告報文,而報文的生命周期通常是30分鐘。這也就是說即使錯過1 - 2份通告報文主機路由表中的默認表項也不會超時。
IP的未來
本篇以及之前兩篇在內的文章,談論的大部分有關IP協議的內容,都是基于IPv4之上討論的,在這之間我們也會穿插介紹一些IPv6與之不同的實現方法,用以比較IPv4的不足。確實,IPv6代表著IP協議的未來。
在之前文章的討論中我們也曾經說過,在網絡發展的初期各類協議百花齊放,誰也不會料到IP協議最后會發展起來壟斷了網絡層,誰也沒有想到幾十年后的今天網絡會發展的這么迅速。32位的IP地址幾盡枯竭,雖然NAT技術為IPv4續了一波但仍然無法滿足日益增長的需求。
- 實際IPv4被創造出來的時候誰也不會想到地址會枯竭的情況,所以我們看早期的地址分配實際是非常隨意甚至浪費的。比如環回地址并非只有
127.0.0.1
,整個A類網絡號127都是為環回接口所預留的,但是真正被廣泛使用的只有一個地址。 - 我們介紹了IPv4和IPv6,細心的朋友應該注意到了中間跨越了IPv5。作為因特網流協議,IPv5一直停留在實驗室階段,并未被大范圍商用所以我們并不用去關心。
有關IPv6和IPv4的區別,展開來說是一個非常大的話題,本書不再贅述。好消息是近期看到國內有關IPv6將要大范圍部署的消息,有興趣的朋友可以自行查閱一下。:)
初探ICMP
ICMP(Internet Control Message Protocol)Internet控制報文協議,是IP層一個非常重要的組成部分,用以傳遞差錯報文以及其它需要注意的信息。報文的格式非常簡單。
需要注意的是,在這里提到ICMP協議是作為IP層的重要組成部分,而沒有稱作網絡層。雖然IP協議幾乎壟斷了網絡層,兩者之間幾乎可以畫上等號,但這樣描述是為了兩個方面的原因。
ICMP報文是由IP協議承載的。
ICMP的定位就是IP協議的維護管理協議。
需要解釋的是第二點。首先我們明確ICMP的定位:在之前的討論有提到過IP協議是一個不可靠,無連接的協議。從開始設計的時候IP協議就沒有考慮太多可靠性需求,而是將重心放在了構建一個易于使用而且健壯的無中心網絡這一塊。ICMP在此基礎上為IP協議提供了網絡控制診斷的服務,包括但不限于檢測網絡是否通暢、鏈路有沒有死循環、時間戳、開機廣播等等。其次我們解釋一下ICMP的分層:ICMP和IP協議同屬網絡層確實非常的尷尬,這讓網絡分層的邏輯看起來有一些混亂,似乎ICMP更應該被劃分在傳輸層。實則不然,ICMP服務于IP協議,協議棧的兩端就是IP模塊,報文的終點已經確定,所有的處理都在網絡層完成;而上層協議比如TCP/UDP則是網絡層處理完成之后再進行操作。
對比各類ICMP報文,思考每一類報文提供的是什么服務?
ICMP協議大致可分為兩類:差錯報文和查詢報文。其中8位的類型字段用以描述具體類型的ICMP報文,某些ICMP報文還可以使用8位的代碼字段來進一步細分描述不同的條件。
按照類型字段和代碼字段來區分理由非常的容易理解:因為ICMP需要負責多種的服務,提供的字段可以讓我們明確當前報文服務的內容。而劃分差錯報文和查詢報文原因在于,差錯報文有的時候需要一些特殊的處理,在某些情況下不會產生差錯報文。
響應ICMP差錯報文時永遠不會生成另一份ICMP差錯報文
常見的差錯報文有目的不可達,端口不可達,超時等等。我們需要明確在收到ICMP差錯報文的時候,源端和目的端之間的通訊是存在問題的。如果移除了這一條限制,很可能會因為響應差錯報文而生成另一個差錯,差錯再產生差錯,無休止的循環下去。這不是我們所期待的結果。目的地址是廣播地址或多播地址
在《TCP/IP協議詳解 卷一》的第十二章為我們介紹了廣播和多播,在引言的部分提到了這么一段
通常每個以太網幀僅發往單個目的主機,目的地址指明單個接收接口,因此稱為單播 (unicast)。在這種方式下,任意兩個主機的通信不會干擾網內其他主機(可能引起爭奪共享信道的情況除外)。
然而,有時一個主機要向網上的所有主機發送幀,這就是廣播。
換一句話來解釋就是如果你的廣播不是LAN內所有主機都需要的,那么勢必會給不需要的接收廣播的主機造成干擾。假設允許主機為廣播地址的報文響應ICMP差錯報文,那么即使你期待的主機已經成功接收到你的廣播,其他不相關的主機也會生成大量的ICMP差錯報文。這不僅僅是麻煩,更可能會造成LAN的擁堵。
更何況廣播和多播都是基于UDP,本身就是一個無連接的狀態,只管盡力將數據拋出去。這樣的前提下差錯報文也就沒有多大的意義了。
作為鏈路層廣播的數據報不產生差錯報文
如果數據報被分片,那么只響應分片的第一片
要理解這個限制條件,首先要聲明一點:當發送一份ICMP差錯報文時,報文始終包含產生ICMP差錯的IP報文的 首部 和 數據報的前8個字節。
IP首部包含了源IP地址和目的IP地址等等非常重要的信息,這對于接收主機是非常有用的。數據報的前8個字節聽起來顯得很多余,但如果你熟悉數據報的封裝流程應該可以很快反應過來,IP數據報的前8個字節是傳輸層協議首部的前8個字節!(一般來說傳輸層協議首部的長度都是長于8個字節的)
讓我們回憶一下傳輸層的兩個經典協議TCP/UDP的首部的前8個字節。
ICMP差錯報文提供的這兩項內容,讓接收端可以輕松將差錯報文和某個特定的協議(根據IP首部中協議類型的字段)以及用戶進程(傳輸層協議首部的前8個字節包含用戶進程的端口號)聯系起來。
上面提到的是沒有分片的情況。如果一個IP數據報被分片進行傳輸,那么目的端接收的IP報文數據報的前8個字節就不一定是傳輸層協議首部的前8個字節了。
數據報分片之后,除了第一片的數據報都無法提供完整的信息給接收端。因此ICMP差錯報文只在第一片數據報這里產生
-
源地址不是單個主機的數據報。(零地址,環回地址,廣播或多播地址)
這一條也非常好理解。如果允許響應,源地址就變成了返回的差錯報文的目的地址,是一個反向的廣播了。
Ping程序介紹
ping的名字源于 聲吶,是一個使用非常高頻的指令,用于測試目的主機是否可達。實現的原理非常簡單:源端發送一份ICMP回顯請求給目的主機,等待目的端的回答。關于ping,需要注意以下幾點。
- 大部分情況下通過ping來判斷目的主機是否可達是沒有問題的。但是,如果無法ping通則目的主機不可達,這一句話放在當前來說是錯誤的!因為部分運營商會攔截過濾ICMP報文,ICMP的優先級比較低,經過路由器的時候可能會被丟棄。等等各類原因可能會造成我們ping的失敗,但實際網絡是暢通沒有問題的。
之所以不提高ICMP報文的優先級,有部分的考慮是因為構造它非常的簡單,沒有任何的防護措施。另外,短時間內大量的ICMP報文涌入,對于低端網絡設備而言,CPU的壓力非常的大。
- ping本身是基于ICMP協議實現,也就是說協議棧的兩端只是在網絡層。雖然我們仍然將通信的雙方認為是客戶端-服務器模型,但這個和其他的模型是有很大區別的。因為ping的模型不涉及用戶進程,所有的操作都在底層的協議棧上,所以一般是由內核提供服務。對于用戶而言就無需額外的配置可以直接使用。
要操作IP層,需要使用raw socket。但是一般情況下除了root用戶,OS基本只提供傳輸層也就是TCP/UDP及其上層的服務給用戶調用。
雖然大部分情況下使用ping是為了測試網絡通暢,但ping還為我們提供了記錄路由選項和時間戳選項。受限于IP數據報選項部分的長度限制,這兩部分的功能局限性非常的大。也許在發明ping的時候是夠用的,但現在已經無法滿足我們的需求了。
Traceroute程序介紹
traceroute程序提供了查看IP數據報從一臺主機到另一臺主機所經過的路由的服務。這個和ping程序的記錄路由選項提供的服務非常相似,但本質還是有一些不同。要理解traceroute存在的意義,我們必須明確ping程序記錄路由選項有哪些不足。
- ping程序的記錄路由選項需要路由器的支持。這需要額外的配置,并且我們無法保證途徑的每個路由器都可以提供支持。
- 雖然IP數據報往返的路徑可能會發生變化,但大部分的情況是相同的。這意味著往返的路由記錄有一半是重復的。
- 受限于IP首部選項部分的長度限制,最多只能夠存放9個IP地址。這是最為致命的地方,當前的環境下這是絕不夠用的。
正是因為這些條件的限制,traceroute應運而生。它的機制非常的簡單,通過設置TTL來探測路徑。發送一個UDP數據報,將目的端口的數值設置為它的進程ID和32768的邏輯或,依次設置TTL從1到255。如果數據報沒有到達目的端,那么返回的差錯報文是目的不可達;如果數據報到達目的端,那么返回的差錯報文則是端口不可達。
盡管我們無法保證每次發出的TTL不同的探測幀走的是相同路徑,也無法保證數據報的返回路徑和我們探測的發送路徑相同,但大部分情況下我們認為探測幀選擇的路徑是相同的。traceroute提供的是一個有一定誤差但足夠便捷的探測服務,這是可以接受的。
ping程序和traceroute程序都會計算RTT,但兩者的方法不同。ping程序每隔1s發送一次,發送和響應的ICMP報文選項中都可以設置時間戳,ping程序可以依此計算出精確的RTT;traceroute每發送一次,會等待響應或超時,因為不提供時間戳的服務,只能夠依據接收或超時的時間進行計算。
UDP的簡單思考
UDP是一個非常簡單的面向數據報的運輸層協議:運行UDP的進程的每個輸出操作都正好產生一個UDP數據報,并組裝成一份待發送的IP數據報。
首部的選項參數相對同在運輸層的TCP而言非常的少。源端口和目的端口用以指明兩端主機上操作的進程,校驗和選項甚至都可以選擇關閉。我們可以簡單的認為UDP只是將收到的IP數據報做了一個簡單的校驗,然后分發遞送給對應的進程。所以UDP提供的服務和IP協議非常類似,是不可靠并且無連接的。
在和TCP的比較當中,經常會強調的是UDP的傳輸比TCP高效。因為UDP只需要簡單的收發不提供其他額外的服務以供選擇。我覺得令人忽略的一點在于除去高效,UDP的存在還包括給予了應用層自定義傳輸邏輯的能力。
我們在談到TCP的時候,應該知道它的目的是盡可能快的將數據傳遞到彼端。除卻常常被提到的可靠性以外,被遺忘的很重要的一點是盡可能快也就是盡可能充分的利用當前可利用的網絡資源。因此TCP的實現中包含復雜的邏輯,用以應對各類未知的網絡狀況。
但現實情況是部分時候我們不需要那么復雜的邏輯,或者說在網絡環境惡劣的情況下TCP的一些邏輯甚至成為負擔,UDP為此提供了解決的可能。傳輸層只簡單分發接收的數據報,應用需要的服務由自己實現。
比如TFTP協議就是依賴于UDP實現的一種停止等待的文件傳輸協議,TFTP在應用層實現了類似TCP中Negal算法的服務。雖然相對于FTP而言TFTP局限很大,系統的吞吐量也比較低,但在部分情況下TFTP仍然以自身簡單短小更顯優勢。
我們日常強調TCP可靠但消耗更多資源,UDP是不可靠。但得益于現今網絡的快速發展,很多時候TCP額外的開銷并不如我們想的那么不可承受,UDP在某些特定環境下比如LAN內傳輸也是非常可靠的。
我喜歡將UDP類比成一把精雕的小刀,使用它可以允許你在邏輯里仔細雕琢每一個部分,但是面對大的工程往往顯的力不從心。為了解決各種問題而不斷新增代碼,使用UDP很可能最后只是在模擬一個蹩腳的TCP協議。所以在選擇使用TCP還是UDP的時候,遵循一句話:
When in doubt, use TCP
廣播和多播
廣播和多播都是基于UDP實現的,不同于TCP端對端的通訊,廣播和多播提供了一對多的數據報的服務。在討論這兩者之前,我們還是要把書中的一句話拿出來強調一遍。
使用廣播的問題在于它增加了對廣播數據不感興趣主機的處理負荷。
參考協議棧各層對收到的幀過濾過程
假設主機對廣播數據不感興趣,一份廣播的數據報也會一直上傳到傳輸層,在找不到合適的端口分發之后數據報才會被丟棄,這需要主機承擔額外的處理負荷。還有很重要的一點在于,我們一直強調的TCP是一個有連接的通訊,它維護了的會話的過程,兩端之間唯一的連接靠四元組(源端IP地址,源端端口,目的端IP地址,目的端端口)來確定;但UDP是無連接的,它只是一個傳輸的過程,假設廣播的目的端口在這臺主機上恰好有進程在運行,那么這份數據報也會被分發給這個進程,盡管這并不是期望的目的進程!
某些情況下鏈路層也是可以接收到其他主機的數據報,只不過在以太網內依賴于Mac地址可以早早的過濾掉這些數據報。而廣播的問題在于必須到傳輸層甚至更上層才能發現濾除這些不關心的數據!
廣播總共有四類的IP廣播地址
類型 | 地址 |
---|---|
受限的廣播地址 | 255.255.255.255 |
指向網絡的廣播 | 主機號全部為1 |
指向子網的廣播 | 主機號全部為1且有指定的子網號 |
指向所有子網的廣播 | 子網號和主機號全部為1 |
需要注意的是,同一個IP地址在不同的情況下(比如子網號不同)代表的可能是不同的廣播類型。
日常使用最多的應該是指向子網的廣播,但實際應用的過程中也需要考慮廣播給網絡帶來的壓力。與之類似的還有255.255.255.255
這樣一個受限的廣播地址,它更多是應用在系統引導期間,主機尚未知道自己的IP地址或掩碼。
一個簡單的小例子
之前寫過一篇文章CocoaAsyncSocket UDP收發數據包大小限制里面討論了因為iPhone對于默認緩沖區的限制為9216,UDP數據報的收發因此受到影響。前段時間有一個朋友在私信問我,他同樣使用了CocoaAsyncSocket,但是發出的UDP數據報最大長度只能是1472。
要解決這個問題之前我們必須要明白,限制UDP數據報長度的主要因素有哪些:
- UDP首部的標示長度的字段。考慮到它能表達的最大數值,UDP包最長允許65535。
- 緩沖區的大小。和TCP不同的在于,UDP的緩沖區實際是一個虛構的存在,這里只是一個指代允許發送的數據報的大小上限。如果超過這個范圍那么系統會返回一個
EMSGSIZE
的錯誤。
遺憾的是1472并不符合上面兩條,但這個數字卻非常特殊!因為1472 + 8 + 20 = 1500是MTU的大小。我開始的猜想是DF置1拒絕分片,但實際拿到的Demo里并沒有對socket進行設置,CocoaAsyncSocket的源碼也是默認關閉DF的。當我再次查看源碼的時候發現目的端地址寫的是255.255.255.255
。
這就是問題所在了:iPhone拒絕為一個目的端為廣播地址的一個數據報分片。我們必須要考慮到分片帶來的是廣播包數量的翻倍,短時間內一并發出很容易造成擁塞。其次,即使分片允許操作,廣播出去以后對于目的端主機而言,很可能出現各類不可預知的情況(比如重復收到同一個數據報),如何處理重組分片的邏輯?
問題的根源在于,廣播并不是通訊的一種合理方式,它設計的初衷應該是為了簡單消息的通報和發現通信對象。在網絡中避免廣播的使用是一種共識,實際在IPv6中也確實取消了廣播這一特性。
小結多播
多播是介于廣播和單播之間的一種服務。主機可以自由選擇加入一個或多個多播組,網卡將據此決定是否接受多播幀。如我們之前所介紹,D類IP地址被用于多播。
多播的優點在于可以自主選擇,并且不相關的報文可以在底層就被過濾不會增加主機過多的負荷。但是這里我們必須提一個問題。
多播的源端是無法確定接收端的數量,可以沒有,也可以是一個或者多個。這種情況下我們如何設置以太網幀首部的MAC地址?
首先可以確定使用ff:ff:ff:ff:ff:ff
這個廣播地址是不可行的,這樣多播和廣播就毫無區別了。多播給出的解決方案是提供一種簡單的映射規則將IP地址和某一段MAC地址一一對應,網卡和IP層依次過濾數據報。
上圖為我們解釋了映射的規則。在解釋這個規則之前先為大家講一段為何選擇23位的歷史成因。IP地址一共32位,多播使用的是D類廣播地址,也就是說開始的4位是固定的,我們如果希望MAC地址可以和D類IP地址一一對應,那么MAC地址中最少應當提供28位以供選擇。那么MAC地址又是如何分配呢?在這里必須要簡單介紹一下OUI(Organizationally unique identifier)組織唯一標識符,48位的MAC地址被分為兩部分:24位組織唯一標志符(OUI),剩下24位供使用者自行決定。這也就是說28位必須要16個OUI才能夠滿足。但是當時做多播的人,ta的老板覺得這樣成本太高,畢竟每個OUI都是要真金實銀去購買的!老板只批準了1個OUI,并且只把其中的一半給了多播使用,這也就是為什么多播只能映射23位到MAC地址的原因。
需要16個OUI的原因在于16 = 2 ^ 4也就是4個位的長度。
24位長度也就是2 ^ 14個組合,一半也就是2 ^ 13 = 23位。
我比較笨第一次看還理解了半天,哈哈 :)
現在我們再來看看規則: 24位的OUI是00:00:5e,剩下的24位的所有組合只有一半供多播使用,24位的最高位置0的MAC地址是屬于多播的。
這樣留下了一個隱患那就是多個多播地址可能會共享一個MAC地址。網卡無法準確的過濾掉不關心的數據幀。這就是為什么前文我們說 網卡和IP層依次過濾數據報
的原因,上層必須提供邏輯再次檢查數據報是否是我們所期待的。
多播數據報的轉發
單個物理網絡的多播是簡單的。如果多播擴展到多個網絡需要路由轉發,復雜性會增加很多。難點就在于路由如何確定網絡中哪些主機是屬于多播組內的。IGMP(Internet Group Management Protocol)組管理協議正是為解決這個問題應運而生的。但本文不再贅述,有興趣的朋友可以參照《TCP/IP協議 卷一》的第13章。