《TCP/IP協(xié)議 詳解》思考總結 · 一

前言

從去年年底開始大約花了半年時間去啃《TCP/IP協(xié)議 詳解》這本書瞧剖。雖然整體過了一遍拭嫁,也給了我一些基礎能夠參與到網絡相關話題下的討論,但對于這樣一本全面詳細的指導書抓于,我并沒有掌握的很好做粤。拋開沒有記住的內容,很多問題在第一次閱讀的時候會以理所當然的態(tài)度給跳過捉撮。

最近重新拿起這本書怕品,審視一些第一遍沒有考慮到的問題。我選擇將一些思考的問題記錄下來巾遭,分節(jié)整理肉康。當然,要捋完整本書需要一些時間灼舍,一篇文章的篇幅也有限吼和,這篇文章主要講述了五個問題字數(shù)已經過萬,所以后續(xù)我會分篇一一來總結骑素。更新的內容目錄會添加到《TCP/IP協(xié)議 詳解》思考總結目錄里纹安,有興趣的朋友可以在這里搜索找到對應的文章。

因為本人水平有限砂豌,只是一名普通的軟件開發(fā)而非專業(yè)的網工人員,文章難免有所缺漏光督。如果讀者有任何疑問阳距,望不吝賜教。

有任何問題歡迎評論留言结借,我會盡力給予回答筐摘。


網絡分層

計算機網絡采用了分層結構,但是不同地方關于劃分的標準并不是完全一致船老。主流的模型分別是TCP/IP五層模型(在TCP/IP協(xié)議詳解中分為四層咖熟,硬件相關的數(shù)據(jù)鏈路層和物理層被合并在一起)和OSI的七層模型。兩者的差異就在于會話層和表示層的缺失上柳畔。

會話層的功能是會話控制和同步馍管,表示層是解決兩個系統(tǒng)間交換信息的語法和語義的問題,以及數(shù)據(jù)表示轉化薪韩,加解密和壓縮解壓縮的功能确沸。在OSI七層模型中將這兩層從廣泛意義上的應用層里獨立出來捌锭,主要的目的還是讓這兩層的邏輯代碼可以為所有的應用程序共享,同時瘦身應用程序罗捎。

但是現(xiàn)實的情況是這兩層在各種應用程序當中很難設定一個統(tǒng)一的標準观谦,每一個不同的程序關于表示和會話的需求各不相同。這意味著相關的邏輯和代碼是無法復用的桨菜,那么獨立出來也就無從談起了豁状。這部分的邏輯最終都交給應用開發(fā)者在應用層決策實現(xiàn)了。

關于網絡分層的原因倒得,實際大部分軟件系統(tǒng)都是分層架構泻红,這一切都是為了工程上實現(xiàn)/調試/維護的方便。之前在一個關于為什么Linux為什么還要堅持使用宏內核的問題下我看到一段話非常的有意思屎暇。

因為Linus可以把這些亂七八糟的東西全都一個人寫了承桥,一遍寫對了,還能穩(wěn)定跑起來無bug根悼,而我們這些渣渣做不到凶异,只能依靠保護模式來防止幾百個工程師寫出來的那一坨垃圾動不動藍屏,自己弱卻去質疑天才的做法挤巡,和明知自己弱還要模仿天才的做法剩彬,都是認不清現(xiàn)實的表現(xiàn)。
==============================================================================
工程這個東西是很有意思的矿卑,我們說科學是掌握規(guī)律喉恋,技術是利用規(guī)律克服大自然的限制,而工程母廷,卻是利用技術來克服人自身的限制轻黑。技術會告訴你,造個金字塔琴昆,把石頭壘成四棱錐就行了氓鄙,如果你是個力大無窮的巨人,或者是個能意念移物的魔法師业舍,你就啪啪啪把石頭搬過來堆起來就完事了抖拦。但我們是凡人,我們力量很小舷暮,我們很弱态罪,所以我們需要滾木,需要滑輪下面,需要繩索來幫忙复颈,做了許多額外的麻煩事情,只為了克服我們肉體的自身限制诸狭。體力上有限制券膀,智力上同樣有限制君纫。軟件工程很大程度上就是解決我們人類智力上限制的問題,軟件工程師在面對不知所謂的kernel dump的時候會無助芹彬,會哭泣蓄髓,在面對無休止的接口變動的時候會歇斯底里,面對改一行代碼系統(tǒng)就全掛的窘境束手無策舒帮,所以我們需要微內核会喝、微服務這樣的框架來約束系統(tǒng),降低系統(tǒng)的復雜性玩郊,讓我們所有犯的錯誤都能保持在可控的范圍內肢执,讓因為我們的愚笨而寫出的有bug的代碼也能勉勉強強運行起來而不是分分鐘crash,哪怕這些方法額外增加了許多工作量译红、還降低了效率预茄。但是,總是有超人存在的侦厚,我們人要造一個紀念碑耻陕,設計一堆方案刨沦,superman會說诗宣,哈?這個事情,不是只要我去把那個石頭舉起來,然后飛過來咆爽,放在這里不就好了嘛?體力上差距這么大的超人也許不存在徙歼,但智力上差距這么大的超人卻是存在的,所以要記住工程方法只是為了拯救我們這些凡人,對于超人來說,他們是不需要這些的穿稳,他們要做的僅僅是“搬起來,放下去”而已晌坤。
可以多人開發(fā)這件事情在軟件工程上是至關重要的,如果一個工程必須每個程序員都完全理解整個工程的架構才能著手開發(fā)旦袋,那這個工程一定做不成骤菠,不說完全理解一個龐大項目本身不可能這個問題,大家理解也會有偏差疤孕,寫出來代碼合不到一起商乎,這就是為什么需要有嚴格的模塊拆分,而且接口必須控制在解耦的祭阀、可以理解的鹉戚、數(shù)量盡量少的范圍內鲜戒,而且通過框架來避免錯誤相互擴散。

即使是一次看似非常簡單的數(shù)據(jù)傳輸抹凳,背后實際需要做的工作也非常多遏餐。我經常會感嘆網絡的強大,無論需要傳輸?shù)氖且欢挝淖钟祝粡垐D片失都,一段視頻,又或是無論我和通信的對方相距多遠幸冻,數(shù)據(jù)都可以穩(wěn)當?shù)乃瓦_給對方粹庞。如何打包處理大小不同用戶的數(shù)據(jù);如何去和對方建立連接洽损;如何在不可靠的信道上提供可靠的數(shù)據(jù)傳輸庞溜?處理這里面繁雜的邏輯,是一個大問題碑定。前輩們給出的解決辦法是將大問題分割成若干個小問題流码,交由不同的層去解決。每一層相互獨立不傅,互不干擾旅掂,只關心自身的任務;處理結束之后將結果交由下一層繼續(xù)處理访娶。

這樣做的好處非常明顯:
  • 每一層只需要專注自己本身商虐,而不必關心整個網絡的結構,這讓問題簡化了不少崖疤,每一層的自由度也很高秘车;
  • 其他層面的操作對于本層而言可以視作一個黑盒,它隱藏了具體的實現(xiàn)細節(jié)劫哼,只提供了一套可供調用的API叮趴,這樣只要外部接口不變,內部做修改對其他地方毫無影響权烧;
  • 相關邏輯的代碼可以重復被使用眯亦,提高了開發(fā)的效率;
  • 同時也給調試提供了便捷般码,允許逐層排查確認問題妻率。
那么劃分層級的標準又是如何呢?
單個網絡

上圖我們可以看出應用層的程序是用戶的一個進程板祝,而下層則是交由操作系統(tǒng)(內核)處理的宫静,盡管這不是絕對的,但大多數(shù)情況都是這樣的,我們可以簡單的這樣去認為孤里。應用層的程序更多的是關心業(yè)務邏輯的處理伏伯,而不是數(shù)據(jù)在網絡中的傳輸活動;而下層對應用程序毫不知情捌袜,但它們需要處理所有的通信細節(jié)说搅。

鏈路層(或者說數(shù)據(jù)鏈路層和物理層)是和硬件設備以及相關的驅動打交道的這一部分

如果只看這張圖琢蛤,運輸層和網絡層之間的區(qū)別并不是那么的明顯蜓堕。百度百科上給出的答案是:

  • 網絡層:使用權數(shù)據(jù)路由經過大型網絡;
  • 運輸層:提供終端到終端的可靠連接。

這樣的說法是完全錯誤的博其!因為這是基于一個大的前提:網絡層的協(xié)議是IP協(xié)議套才,而運輸層的協(xié)議是TCP協(xié)議。但實際情況是在網絡發(fā)展初期慕淡,各種協(xié)議層出不窮背伴,TCP/IP也只是其中非常普通的一個部分,至少在OSI模型(五層模型)落地的時候誰也不會料到今天TCP/IP會占據(jù)主流地位峰髓。所以這并不是劃分運輸層和網絡層的理由傻寂。

要理解這兩層的含義,我們需要把視野從單個網絡放大到一組網絡

組網絡

上圖中我們可以劃分出一個端系統(tǒng)(兩邊的主機)和中間系統(tǒng)(路由器)携兵。在這張圖中可以明確看出應用層和運輸層的協(xié)議都是端對端協(xié)議疾掰,也就是只有端系統(tǒng)會使用這兩層協(xié)議的內容;而網絡層的協(xié)議是逐跳協(xié)議徐紧,兩個端系統(tǒng)和每一個中間系統(tǒng)都需要使用到它静檬。

不同就在于網絡層服務的對象不僅僅是端系統(tǒng),也包括中間節(jié)點并级。提供服務的目的是將分組盡可能快的從源節(jié)點傳輸?shù)侥康墓?jié)點拂檩,但不為此提供任何可靠性保證。如果我們略去運輸層直接將數(shù)據(jù)交給應用層嘲碧,是否可以稻励?當然可以!這也是為什么我們會覺得運輸層和網絡層區(qū)分不明顯的原因愈涩。但這樣的做法是非常不值得推薦的望抽,因為數(shù)據(jù)經過不可靠信道傳輸之后狀態(tài)是未知的,這一部分的數(shù)據(jù)在交由應用程序之前我們還需要進行處理履婉,包括但不限于數(shù)據(jù)校驗糠聪,安全性檢查,可靠性傳輸服務的提供谐鼎。所以我們要在網絡層和應用層之間建立一個端到端的連接,允許我們在這之間對數(shù)據(jù)能夠有操作的空間和余地。而運輸層就是負責建立狸棍,管理和維護這一部分的連接的身害。

在TCP/IP協(xié)議簇當中,IP協(xié)議負責將分組從源節(jié)點傳輸?shù)侥康墓?jié)點草戈,而TCP在上層提供了可靠性服務塌鸯。但這并不是說可靠性服務一定要由運輸層完成。和TCP對應的UDP是以不可靠傳輸聞名天下唐片。我們可以將UDP看作一個最原始的TCP數(shù)據(jù)包丙猬,在去除了各項feature之后,UDP相比TCP有了輕便费韭,簡潔的優(yōu)勢茧球。這讓它在一些網絡狀況不佳、及時性要求較高的業(yè)務場景中有了發(fā)揮的機會星持,TCP的三次握手以及擁塞控制在這些情況下反而變成了一種負擔抢埋。如果我們需要一些可靠傳輸?shù)男枨蠖植幌胧褂么蠖腡CP,我們可以將這一部分的邏輯移交到應用層來實現(xiàn)督暂。對于應用開發(fā)者而言揪垄,可以更加自由的控制數(shù)據(jù)報傳輸?shù)倪壿嫛?strong>Ethernet Card + Driver + IP + UDP + TFTP就是一個很好的例子。


談一談可靠性是什么

在談及計算機網絡的時候可靠性是一個無法回避的概念逻翁。

我們經常會將運輸層上的TCP和udp協(xié)議進行比較:TCP是一個可靠傳輸?shù)膮f(xié)議饥努,而UDP則是不可靠的。在很長的一段時間里八回,我簡單的把這個可靠性理解為:UDP通信有一定的概率失敗酷愧,TCP通信是肯定會成功的。這是一個錯誤非常明顯的理解辽社,非常容易舉出反例:如果你的電腦是脫機狀態(tài)伟墙,那么無論選擇什么樣的網絡協(xié)議你都無法和外界進行通信。

那么TCP提供的可靠性傳輸究竟是什么滴铅?

第一次意識到我的錯誤戳葵,是在開發(fā)一款藍牙產品的時候。多個藍牙設備自組網絡汉匙,我通過指定Mesh ID(類似IP地址拱烁,用于指明Mesh網絡中具體哪一個設備)發(fā)送消息給直連的藍牙設備,來控制任一一臺Mesh網絡中的藍牙設備噩翠。boss測試程序的時候給我提出了一個問題戏自,為什么我明明點擊了這個按鈕發(fā)送了開的命令,應用內也顯示打開了這個設備伤锚,但實際這個設備并沒有打開擅笔?

問題其實很簡單:這個產品有一個缺陷,在同時發(fā)送多條消息的時候可能會造成信道擁塞,發(fā)送的消息可能會丟失沒有送達猛们。在點擊打開按鈕的同時程序會發(fā)送一條消息念脯,但是因為當時底層可能正在頻繁通訊導致這條消息沒有送達,應用內又將設備狀態(tài)置成打開弯淘,造成了這種錯誤绿店。最后解決的辦法是在底層限制了通訊的速率來避免這種情況的發(fā)生。那么讀者可能會問:為什么你不能讓設備返回你一條打開成功的消息之后再改變設備在應用內的狀態(tài)呢庐橙?這是因為Mesh網絡本身承載能力有限假勿,為此所有的消息是沒有返回的,所以我無法等收到消息然后再在回調里修改設備的狀態(tài)态鳖。這就類似UDP的數(shù)據(jù)報一樣转培,發(fā)出以后,發(fā)送方就不會再去關心郁惜。

這個Bug讓我第一次感受到了TCP的美好:每一條消息發(fā)送成功以后都會返回一條Ack來告知發(fā)送方已送達堡距。我重新定義了一遍可靠性:能夠明確告知發(fā)送方發(fā)送結果

就算沒有發(fā)送成功不會有Ack消息兆蕉,我們也可以設置超時時間來認定發(fā)送失敗

但很顯然這并不全面羽戒,TCP做的遠不止這些

這一次幫助我意識到問題的是一個BugCocoaAsyncSocket UDP收發(fā)數(shù)據(jù)包大小限制,我開始重新審視TCP報文里的參數(shù)虎韵。

TCP/UDP 首部

上圖是TCP和UDP報文的格式易稠。TCP的報文中包含了非常多的選項可以設置,而UDP非常的簡單包蓝,除了目的地址和端口以外驶社,只有Data Len和一個Checksum(如果為了追求極致的速度你甚至可以關閉Checksum這一選項,這在TCP中是不可能的)测萎。之前藍牙項目的消息有兩個特征:精短亡电;每一條消息獨立互不干擾。那么數(shù)據(jù)報非常大(大到一條消息放置不下)的情況要如何處理硅瞧?多個有序數(shù)據(jù)報在網絡中傳遞發(fā)生亂序份乒,丟包該怎么辦?

了解TCP的朋友應該知道TCP提供了擁塞避免腕唧,快速重傳等機制來應對處理未知信道帶來的這些問題或辖。當然,這其中每一部分拿出來都可以說上很多枣接,不在這個問題下反復糾結颂暇。但是我們應該要明確的一點是,在應用開發(fā)者看不見的地方但惶,TCP已經為我們解決了很多不可靠信道帶來的傳輸問題耳鸯。關于可靠性我們應該為它加上一點:能夠自主應對大部分數(shù)據(jù)傳輸過程中出現(xiàn)的問題湿蛔,包括但不限于丟包,亂序等等县爬。

以上我們討論的內容其實都是圍繞消息是否送達煌集,但實際傳輸過程中我們還需要關心送達的內容是否準確

這就是為什么我們需要校驗和的原因

在談論這一部分之前捌省,我們需要認清網絡傳輸?shù)谋举|是什么。無論你需要計算機傳輸?shù)氖鞘裁礃拥馁Y源文件碉钠,最后都會變成一串01000101010101...的比特流纲缓,在互聯(lián)網的血管里流淌『胺希回想一下描述文件大小的單位祝高,就算一個簡單的文件實際也是一串非常長的數(shù)據(jù)。要想保證它在層層傳遞的過程中不出現(xiàn)差錯污筷,這幾乎是不可能實現(xiàn)的工闺。

這也就是為什么多層協(xié)議上都引入了數(shù)據(jù)校驗這一部分。在數(shù)據(jù)鏈路層有FCS瓣蛀;網絡層IP協(xié)議和運輸層的TCP/UDP都提供了Checksum陆蟆。目的就是為了檢測出因為網卡軟硬件Bug、電纜不可靠惋增、信號干擾而造成信號失真造成的數(shù)據(jù)錯誤叠殷。

可是同樣一件事需要幾層同時去做嗎?

當然诈皿!第一個原因在于鏈路層CRC不能完全檢測出錯誤林束;第二,每一層校驗覆蓋的數(shù)據(jù)范圍是不一樣的:IP協(xié)議Checksum只覆蓋IP首部稽亏,而傳輸層只針對自己的數(shù)據(jù)包壶冒。那么為什么設計成一次校驗完成避免多次校驗的浪費?IP協(xié)議Checknum只覆蓋IP首部的原因在于IP首部的信息傳輸過程中會被多次修改的截歉,包括TTL以及某些情況下對源地址的修改胖腾。其次,我們需要理解分而治之是網絡中的一個重要概念怎披。因為你無法保證執(zhí)行校驗的上層一定需要這個校驗的結果胸嘁,即時性和可靠性是無法盡善盡美的,只能取一個平衡凉逛。最好的就是大家各自完成各自的任務性宏,就像一個可以自由組合的積木,還是那句話:分而治之状飞。

有了校驗可以認為數(shù)據(jù)更加安全了嗎

這其實是把安全性和可靠性混淆在了一起毫胜。所有的校驗操作是防君子而防不了小人书斜,它可以檢測硬件故障、軟件bug酵使、信號干擾荐吉、線路差、人為誤操作這些非主觀原因造成的錯誤口渔,但是卻無法防止別人惡意去篡改你的數(shù)據(jù)內容样屠。道理很簡單,篡改了數(shù)據(jù)的同時缺脉,可以將重新校驗的結果覆蓋上去痪欲。可靠性提供的是:避免非主觀因素造成的數(shù)據(jù)錯誤攻礼。

但是如何避免被別人攻擊篡改數(shù)據(jù)內容业踢,這是安全性相關的內容,需要別的機制來提供相關的服務礁扮。以我們熟悉的TCP舉例說明知举,它提供的是一個可靠但不安全的傳輸服務。


談一談分片

鏈路層對于數(shù)據(jù)幀的長度都有一個限制太伊,我們稱之為MTU雇锡。如果需要傳輸?shù)臄?shù)據(jù)報長度大于鏈路層的MTU,那么我們就需要將數(shù)據(jù)報分成若干長度小于MTU的數(shù)據(jù)報倦畅,這個過程叫做分片遮糖。因為數(shù)據(jù)報在發(fā)送的過程中需要經過不同的網絡,鏈路層的MTU不盡相同叠赐,所以分片不僅發(fā)生在源主機端欲账,也可能會發(fā)生在中間路由上。

上述所說的中間路由發(fā)生分片芭概,是IPV4的環(huán)境下赛不;在IPV6中只在源端分片重復,如果數(shù)據(jù)報長度大于中間路由的MTU罢洲,路由會直接返回一條ICMPv6 too big 給源主機端踢故。這一部分我們后面會再說到

我們首先要明確一點為什么要把分片放在IP層

發(fā)展至今,IP協(xié)議幾乎和網絡層劃上了等號惹苗。雖然網絡層仍有其他協(xié)議的存在殿较,但是可以說能走到最終用戶面前的協(xié)議,網絡層都是選擇IP協(xié)議桩蓉。所以分片操作放在IP協(xié)議上淋纲,可以為所有的上層協(xié)議服務,而不必再依次去運輸層的協(xié)議中實現(xiàn)院究,相關的邏輯代碼復用效率很高洽瞬。在這里為什么我們不再像討論Check num那樣去討論分而治之本涕,原因在于分片的根源是MTU的限制,任何一種協(xié)議都無法避免大數(shù)據(jù)報傳輸過程中的分片伙窃。協(xié)議可以決策自己是否實現(xiàn)這部分邏輯菩颖,但必須保證這部分的邏輯一定要實現(xiàn)。而IP協(xié)議就是這一個必須實現(xiàn)的保障为障。

另外晦闰,IP協(xié)議比起上層協(xié)議還有一個很大的優(yōu)勢就在于它更貼近鏈路層,這讓IP協(xié)議可以感知到底層的MTU鳍怨。而上層協(xié)議既不關心鹅髓,也無法直接關心到底層MTU。

注意這里說的是直接關心京景,也就是說上層協(xié)議如果想要關心MTU,可以去設計獲得骗奖,但這個信息并不是必要的确徙。

總結來說,IP層實現(xiàn)分片是一個成本最低的選項执桌。因為物理層去做分片鄙皇,需要和硬件打交道,如果處理的復雜或者不符合大眾標準很可能就是路越走越窄最后把自己走死了仰挣;而上層處理和我之前說的一樣伴逸,運輸層去各自實現(xiàn)成本很高,應用層自己去做對于應用開發(fā)者來說非常痛苦膘壶。

作為一名應用開發(fā)者错蝴,我承認分片是一件簡單但是折磨人的工作。在實現(xiàn)藍牙OTA升級功能的時候颓芭,我需要將升級包.bin文件拆開依次發(fā)送給設備顷锰,中間需要處理分片,對齊亡问,填充等等工作官紫。這是一份不難但是非常考驗耐心的活州藕。這只是簡單的點對點傳輸束世,如果情況復雜,需要處理的工作就會更多床玻。要上層各自去實現(xiàn)分片毁涉,實在不是一個很好的選擇。

分包真的只有IP協(xié)議去實現(xiàn)了嗎

并不盡然笨枯。雖然我們說IP協(xié)議去做分片是一個成本較低的選擇薪丁,但實際還是有其他協(xié)議做了類似的操作遇西。鏈路層中有類似Atm協(xié)議;在TCP協(xié)議中严嗜,MSS(Maximum Segment Size,最大報文長度)實際就是一個分片機制粱檀。

MSS

鏈路層協(xié)議作者并不熟悉,實際也并未接觸漫玄。我們后續(xù)討論以TCP為對象茄蚯。

TCP協(xié)議中的MSS和分片有一些簡單的不同:分片是因為數(shù)據(jù)報長度大于MTU所以被動去分割數(shù)據(jù)報;而MSS是三次握手過程中雙方協(xié)商的結果睦优,提前分割數(shù)據(jù)避免分片發(fā)送渗常。TCP之所以提供這樣一個option來避免IP層分片,相信有部分原因在于IP分片并不如我們所假設的那么完美汗盘。

我們之前提到IP協(xié)議去做分片確實復用率非常高皱碘,成本非常低,但實際情況中這些操作給負責分片和重組的主機和路由的cpu帶來了非常大的壓力隐孽。負責分組的終端的IP層需要去拆分數(shù)據(jù)報癌椿,用ID值相同的IP Fragmented Packet將數(shù)據(jù)發(fā)送出去;而重組終端的IP協(xié)議IP層根據(jù)ID菱阵,MF位踢俄,F(xiàn)ragment Offset信息進行重組,得到完整數(shù)據(jù)提交給上層晴及。需要著重強調的是我們無法保證分片后的數(shù)據(jù)按照順序依次到達目的終端都办,并且IP層沒有類似超時重傳的可靠性支持,其中任一一個分片數(shù)據(jù)丟失都會引起整個數(shù)據(jù)報的丟失B羌凇A斩ぁ!蛛倦!

亂序和丟包的問題確實非常的麻煩槽卫,這里的麻煩更多的在于出現(xiàn)問題需要解決的成本過高。類似UDP只依靠IP協(xié)議分片處理大數(shù)據(jù)報的胰蝠,實際是非常不推薦的歼培。

如果上層提前分割了數(shù)據(jù),IP層只要為每一個數(shù)據(jù)報加上IP頭發(fā)出即可茸塞。每一個數(shù)據(jù)相互獨立躲庄,由上層提供了可靠性支持。相比IP層分片的苦苦掙扎钾虐,這樣做確實簡單了很多噪窘。

但是這并不能完全的避免分片的發(fā)生。如RFC 879所說:TCP provides an option that may be used at the time a connection is established (only) to indicate the maximum size TCP segment that can be accepted on that connection.效扫。這是一個在三次握手過程中協(xié)商的結果倔监,不保證通訊過程中一定不會發(fā)生變化直砂。

那么是否還有其他的方式來避免分片

Path MTU Discovery(傳輸路徑MTU發(fā)現(xiàn))就是為此服務的。

在這一段的開始我們提到分片也可以發(fā)生在中間路由浩习。舉一個簡單的例子:我們從源主機發(fā)生了一個IP包 = 1500静暂,在互聯(lián)網上逐跳傳遞,在中間的某臺路由發(fā)送出去的時候谱秽,因為該接口MTU只有1000 < 1500洽蛀,那么這個包就被分成兩個IP分片發(fā)出去了。但是作為源主機對此毫不知情的疟赊,后續(xù)它仍然會按1500的大小發(fā)送IP包郊供,那么每一個大于1000的IP包都會被分片。

為此我們可以在IP頭中設置DF(Don‘t Fragement)為1來讓中間路由不要分片近哟,如果IP包的大小大于中間路由的mtu驮审,那么直接丟棄并通過ICMP告知源主機。源主機再根據(jù)ICMP中提供的信息修改IP包的大小吉执。

DF=1

如果后續(xù)還碰到更小的MTU怎么辦头岔?旁友,你聽過遞歸不咯鼠证?

在ICMP發(fā)送至源主機的過程中,可能會被攔截或者丟失靠抑,那么這個IP包(包括后續(xù)大小超過中間路由MTU的IP包)就相當于靜悄悄的被丟棄了量九,TCP連接會被中斷。為此要么修改配置允許相關type的ICMP包的通過颂碧,要么關閉PMTUD荠列,畢竟允許分片的話雙方還是可以正常通信的。

IPv6中有關分片和IPv4有什么區(qū)別嗎

IPv6中只會在源端和目的端分片重組载城,拒絕中間節(jié)點來進行分片肌似。如果數(shù)據(jù)報大小小于中間節(jié)點的MTU,那么中間節(jié)點會以ICMPv6 type=2的消息來告訴源端這個情況诉瓦。這個操作看起來就像IPv4中DF=1一樣川队。據(jù)此我們可以認為:避免分片是一種共識。

根據(jù)Wiki - IPv6 packet中有關IPv6的描述睬澡,端節(jié)點負責PMTUD來查詢允許發(fā)送數(shù)據(jù)報的最大長度固额,讓上層協(xié)議來限制Paylod的大小。如果上層協(xié)議不支持煞聪,那么發(fā)送長度不大于1280的數(shù)據(jù)報來避免分片斗躏。

IPv6要求鏈路層的最小MTU是1280


TCP為什么需要三次握手

在思考為什么握手需要三次這個問題之前,我們應該先考慮的是

為什么TCP的建立需要握手昔脯。

TCP是一種可靠傳輸控制協(xié)議啄糙,它的主要任務是在可靠傳輸數(shù)據(jù)的基礎之上笛臣,盡可能的提高傳輸?shù)男?/strong>。但是問題在于傳輸?shù)男诺啦⒉豢煽克肀覀兠鎸Φ氖且粋€未知的網絡環(huán)境沈堡,無法確定信道的可靠性。假如說信道百分百的可靠桑李,那么完全不需要握手的過程踱蛀,我們只需要按照UDP的方式簡單的將消息發(fā)出即可,因為我們知道無論何時何地我們只要發(fā)出消息對方一定能夠收到贵白。但是在廣域網中這種完美的情況幾乎不存在率拒,為了解決在不可靠的信道上完成可靠傳輸這樣一個問題,那么我們必須要在雙方傳輸數(shù)據(jù)之前禁荒,就某些問題達成一致猬膨。回到TCP中來解釋也就是通信雙方約定起始的Seq呛伴。

為什么需要的次數(shù)是三次勃痴,而不是兩次、四次或者更多热康?

首先我們先明確一點沛申,在雙方通信的過程當中,一條消息單方面的確認需要兩次通信姐军,也就是一次單向握手的過程铁材。過程如下

單向握手

消息已收到傳遞到A之后,那么作為A這一方就可以確定消息已經被B接收到了奕锌。但是這個時候作為B著觉,它成功收到了A發(fā)來的消息但是它并不知道消息已送達這條消息是否成功抵達A處。

回到TCP中來看惊暴,建立連接的開始需要握手:我們需要驗證通信雙方之間的信道是否通暢饼丘,雖然中間只有一條信道,但是這條信道是雙向的(非常簡單的例子辽话,一條馬路允許雙向的通行)肄鸽,我們需要同時驗證A->B以及B->A都是通暢的,雙方各有一個Seq需要和對方確認油啤。

那么也就是說兩次握手肯定是沒有辦法實現(xiàn)連接建立前雙方協(xié)商在某些方面達成一致的需求贴捡,因為這只能滿足一方確認消息,另一方無法確認村砂。如果需要雙方都確認某一條消息烂斋,那么必然需要兩次單向握手的過程。從TCP的角度來解釋,發(fā)送方SYN+ACK之后汛骂,響應方要初始化信道必須也要一次SYN+ACK罕模。

那么三次握手是如何來的呢。出于優(yōu)化的目的帘瞭,響應方將對發(fā)送方SYN的ACK和自身的SYN合并成了一條淑掌。所以說三次握手這個說法雖然貼切的描述了握手的過程,但并不準確蝶念。事實其實是雙方各一次的握手抛腕,各一次的確認,只不過其中一次握手和確認合并在一起媒殉。因為這樣一個合并導致雙向握手+雙向確認的過程變成了三次握手担敌。

四次揮手為什么不優(yōu)化成三次揮手

理由也非常的簡單:通信是雙向的廷蓉,如果響應方接受了發(fā)送方的連接請求全封,那么必然也要發(fā)起一個單向握手來確認和發(fā)送方的連接;但是連接的關閉是允許半關閉的桃犬!任何一方都可以拆除自己發(fā)往對方數(shù)據(jù)的那個通道刹悴,而同時還要保證對方發(fā)往自己的數(shù)據(jù)能被正常處理。也就是說拆除階段的第二次單向握手并不一定是要在第一次單線握手之后立馬執(zhí)行的攒暇,那么中間兩次FIN+ACK的合并也就無從談起了土匀。


為什么應用層還需要設計心跳

當TCP連接建立成功以后,它可以長時間處于空閑狀態(tài)沒有任何數(shù)據(jù)的交流形用。這個時間短則幾分鐘就轧,長則幾個小時,幾天尾序。這段時間里只要雙方的主機沒有重啟進程依然存在,無論中間網絡發(fā)生什么樣的情況連接都不會被中斷躯砰。

這樣一個說法似乎很難被理解每币,因為這和我們生活接觸的實際例子不太一樣。比如水管斷開會漏水琢歇,電線斷開會停電兰怠。而TCP連接實質是通信雙方的主機保持的一個狀態(tài),中間的連接是一個虛構的存在李茫,這樣一個鏈接無論是發(fā)生波動抑或某一端異常斷開揭保,通信的雙方是沒有辦法感知的。

上面所說的情況在實際情況中經常會發(fā)生魄宏。我們假設通信的雙方是服務器-客戶端秸侣,提供服務的一端我們認為是服務器,服務器通常會一直保持運行狀態(tài)并同時為多個客戶端服務;發(fā)出請求的一方是客戶端味榛。通常來說客戶端關閉程序或是手動關機的時候系統(tǒng)會為我們主動斷開連接椭坚,發(fā)送一個FIN包給服務器。但是如果客戶端突然崩潰或客戶直接強制關閉了計算機搏色,這個時候系統(tǒng)來不及發(fā)送FIN包善茎,就會留下一個半開放連接在這里。如果服務器再次向這個已經非正常關閉的客戶端發(fā)送消息频轿,那么它會收到一個RST的回復垂涯。但是如果恰巧服務器正處在等待客戶端回應的狀態(tài),那么它會一直等待下去

這樣的情況顯然是不可以接受的航邢。因為服務器打開一個鏈接的同時耕赘,會以一個客戶的身份占用著某一部分的資源,這樣的一個半開放鏈接會導致這一部分的資源永遠得不到釋放。為了應對解決這一個問題拂蝎,TCP設計了比纾活功能,也就是SO_KEEPALIVE選項当娱。

TCP的心跳包是一個有爭議的選項,這只是一個option考榨,而不是一個必須實現(xiàn)的標準跨细。通常情況下我們是在服務器打開這個選項,客戶端被動響應河质,默認間隔時間7200s冀惭。但這不是絕對的,如果有需求客戶端同樣可以打開掀鹅。之所以在服務器設置這個選項是因為它需要長時間保持在工作狀態(tài)散休,并且同時為多個客戶端服務,而客戶端作為個人使用會經常(異常)關閉乐尊。檢測出半開放的連接并刪除它戚丸,釋放資源對于服務器是非常必要的。

NFS雙方都打開了SO_KEEPALIVE扔嵌,而Telnet和Rlogin則只有服務器打開了這個選項限府。

TCP保活機制的缺陷

绷《校活機制是非常有必要的胁勺,但關于是否應該在TCP中提供一直爭論不休。在Host Requirement中提供了3個不使用倍揽酰活定時器的理由

  • 在出現(xiàn)短暫差錯的情況下署穗,可能會使一個運行正常的連接釋放掉寥裂。(比如中間路由崩潰并重新啟動時會發(fā)送一個保活探查蛇捌,會讓TCP誤認客戶端已經崩潰)
  • 耗費不必要的帶寬
  • 在按分組計費的情況下會在互聯(lián)網下花費更多的金錢

拋開歷史的包袱來看抚恒,帶寬和計費相關的問題已經不再需要我們擔心,但TCP的甭绨瑁活機制仍然是一個不被推薦的選項俭驮。

首先我們需要明確的是,TCP的贝好常活只能夠檢測連接是否存活混萝,但是否可用是未知的。做一個簡單的比方萍恕,TCP的币萼郑活就像水道工,它只關心水管是否暢通能否通水允粤,但水廠能否供水它無法保證崭倘。回到TCP上來解釋就是進程可能死鎖或者擁塞类垫,操作系統(tǒng)是正常收發(fā)TCP消息的司光,但服務端繁忙無法提供服務。

其次悉患,默認檢測的時間是7200s=120min=2h残家。這個間隔實際上是非常長的,這么遲緩的響應能力在大部分的應用場景下是無法接受的售躁。當然我們可以手動去修改SO_KEEPLIVE選項的參數(shù)坞淮,但這是系統(tǒng)級的變量,修改意味著會影響所有運行的程序陪捷。

有朋友提到這個參數(shù)在socket中可以為pre socket單獨設置回窘,類似Buffer

除此之外市袖,還有一個比較棘手的問題是keep alive的數(shù)據(jù)包有可能會被運營商攔截啡直。如果僅僅依賴TCP的保活機制凌盯,那么在這種情況服務器可能會釋放掉一個運行正常的連接付枫。

總結

考慮到以上的問題烹玉,很多上層的協(xié)議都提供了心跳機制來維持連接(比如MQTT中的PINGREQPINGRESP)驰怎。這并不是一種重復設計或者浪費!我們必須要明確的是TCP作為運輸層的協(xié)議二打,它提供的是一個host級別的可靠傳輸服務县忌,TCP所有任務實際的本質就是傳輸(就像我們提到的只是檢測連接是否存活)。如果在業(yè)務場景中有需要心跳機制來處理的邏輯,這部分的實現(xiàn)應該交由應用層來完成症杏。作為應用層的開發(fā)人員装获,應當把TCP簡單看成一個負責網絡傳輸?shù)耐獠緼PI,雖然它被廣泛應用但并不完美可靠厉颤,應用層關于自身差錯糾錯的邏輯是不應該被省略的穴豫。


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市逼友,隨后出現(xiàn)的幾起案子精肃,更是在濱河造成了極大的恐慌,老刑警劉巖帜乞,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件司抱,死亡現(xiàn)場離奇詭異,居然都是意外死亡黎烈,警方通過查閱死者的電腦和手機习柠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來照棋,“玉大人资溃,你說我怎么就攤上這事”亓” “怎么了肉拓?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長梳庆。 經常有香客問我暖途,道長,這世上最難降的妖魔是什么膏执? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任驻售,我火速辦了婚禮,結果婚禮上更米,老公的妹妹穿的比我還像新娘欺栗。我一直安慰自己,他們只是感情好征峦,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布迟几。 她就那樣靜靜地躺著,像睡著了一般栏笆。 火紅的嫁衣襯著肌膚如雪类腮。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天蛉加,我揣著相機與錄音蚜枢,去河邊找鬼缸逃。 笑死,一個胖子當著我的面吹牛厂抽,可吹牛的內容都是我干的需频。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼筷凤,長吁一口氣:“原來是場噩夢啊……” “哼昭殉!你這毒婦竟也來了?” 一聲冷哼從身側響起藐守,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤饲化,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后吗伤,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吃靠,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年足淆,在試婚紗的時候發(fā)現(xiàn)自己被綠了巢块。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡巧号,死狀恐怖族奢,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情丹鸿,我是刑警寧澤越走,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站靠欢,受9級特大地震影響廊敌,放射性物質發(fā)生泄漏。R本人自食惡果不足惜门怪,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一骡澈、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掷空,春花似錦肋殴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至酿傍,卻和暖如春烙懦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背拧粪。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工修陡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人可霎。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓魄鸦,卻偏偏與公主長得像,于是被迫代替她去往敵國和親癣朗。 傳聞我的和親對象是個殘疾皇子拾因,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容