再說說TCP和UDP源埠的確定
到達杭州已經兩週了,基本已經適應了新環境的工作節奏,在生活上依然有些許困難會感到無助,但相信所有問題在不久終究會解決的,遇到困難的時候就是成長的時候,比如這兩週我學會了識別洗髮露和護髮素,比如我學會了用支付寶掃碼坐公交車,等等…
本週來說一個老話題,即 一個TCP連線如何確定自己的源埠 。這個問題在幾年前就分析過,正好前些天一個朋友又問了,我就又進一步進行了思考,覺得正好可以作為本週的話題來討論一下。
可以先看看我之前的分析:
TCP源埠選擇演算法與列維模型 :ofollow,noindex" target="_blank">https://blog.csdn.net/dog250/article/details/14054987
簡單談一點linux核心中套接字的bind機制–資料結構以及埠確定 :https://blog.csdn.net/dog250/article/details/5303572
先不要管所謂的什麼 列維模型 ,它只是一個大自然中高效的聚集搜尋模式,類似冪律一樣普遍,這個後面再說,幾年前的事了,少時不知愁滋味,紙上談兵。
現在只看演算法就足夠了。
當然,這篇文章並沒有描述更多的細節,所以我打算在本文中補充一二。
先看核心是如何組織TCP源埠號資料結構,我依然用一個圖示表達,這比程式碼更加清晰一些:
以上這個結構在核心中叫做bhash,是TCP協議實現中3個核心hash之一,這3個hash結構分別是:
- bhash:維護連線的源埠號,以源埠號計算hash值
- ehash:維護establish連線,以四元組計算hash值
- lhash:維護偵聽TCP,以{srcIP,srcPORT}二元組計算hash值
顯然,關於如何確定源埠的問題就轉化為了上述資料結構的查詢,插入的問題,這個問題的解法是明確的,即:
為新的待確定源埠的連線socket查詢到一個最優的插入位置並插入。
我們非常明確的一個目標就是: 維持四元組的唯一性!
這無疑是一個搜尋結構的操作問題。哈哈,又是一道面試題咯。
接下來要解決的是,在兩個不同的場景下,如何操作以上這個資料結構。兩個場景分別如下:
- TCP socket在bind的時候
- TCP socket在connect的時候
顯然,在TCP進行bind的時候,由於此時並不確定目標是誰,無論是目標IP還是目標埠都不確定,甚至不曉得這個TCP是不是一個Listener,那麼四元組的唯一性約束顯然強化了不少,即: 必須保證{srcIP,srcPORT}二元組的唯一性! 事實上此時我們要保證的是{srcIP,srcPORT,0,0}元組的唯一性。
與bind場景不同的是,如果一個socket事先沒有bind,直接呼叫了connect,當我們呼叫connect的時候,此時確定的是{dstIP,dstPORT}元組,那麼此時的約束就鬆了不少,也就是說要想保證四元組唯一性,這種場景下給我們的機會會更多一些。
具體來講,我給出一個流程:
有了connect的場景分析,bind場景就再簡單不過了,省略下面ehash的部分即可:
理清了關係之後,很簡單是吧。這裡講的是TCP,對於UDP而言也一樣適用,只不過UDP有更簡單的解法。
以上就是確定源埠的基本原則,然而在具體操作過程中,還有一個原則,即維護資料結構的平衡型,我們不希望單獨的hash衝突連結串列過長,因為遍歷一個衝突連結串列的時間複雜度是O(n),這顯然毫無可擴充套件性,所以在插入過程中需要 儘量插入到短的hash bucket連結串列中 。
這就涉及到另一個問題,即:
圖示中初始的探測埠port=pn如何選擇的問題!
這裡就是列維模型在起作用了!
物理類聚,這是普世真理。Linux核心在實現這個確定源埠的過程中,經過了幾次進化,但萬變不離其宗,對於TCP而言,Linux從一個隨機確定的hash bucket開始探測,然後環狀遍歷所有的bhash bucket,對每一個bucket執行上面圖示裡的演算法。
對於UDP而言,我勸大家review一下Linux核心2.6.18,3.10,4.9+的程式碼,這代表了三個進化階段,起初在2.6.18版本時,UDP維護了一個全域性的 udp_port_rover 變數,指示下一次探測可用源埠時從哪裡開始,然而到了3.10,4.x版本,實現方式便起了變化,不再通過連結串列資料結構進行多次廣度優先遍歷,而是採用深度優先原則使用點陣圖來實現,但這並沒有改變實質。
程式碼並不難懂,相對於像屎一樣的TCP擁塞控制演算法的程式碼,這個要好很多,找 get_port 回撥函式就好,然後看看 tcp_v4_connect 函式,大概10分鐘應該可以讀懂,我這裡就不再贅述細節,記住一個原則,如果你要實現自己的演算法,優先找最短的衝突連結串列進行遍歷插入,你稍微費點事,帶來的是整個系統性能的提升,可謂人人為我,我為人人。
最後,我還想說說列維模型。
列維模型最初是2004年由一個名為Dirk Brockmann 的德國物理學家發現的一個普世模型,我最開始看到後簡直深陷而不可自拔!
我發現這個簡單的模型竟然能解釋人類文明的形成,我是多麼喜歡歷史學,我發現這個模型竟然能解釋它,並且能預測未來!
當我看完了《三體》三部曲之後,這更加加深了我對列維模型的好感和狂熱!
盜自己之前文章一張圖吧,實在是忍著飢餓在寫這段文字:
這就是列維模型!TCP和UDP確定源埠的演算法絕對符合這個模型,不信你接受10w+的連線後匯出資料畫成圖示看看,我試過,你也試試,非常壯美。
你會發現,自然界,我們生活的空間,沒有什麼東西是平鋪平淡的, 正是聚集造就了我們美好的世界 ,不然一堆原子均勻的分佈在宇宙空間,那才沒有任何意義,然而墒總是在增加,宇宙趨向於無序,這也是宇宙最終的結局,不禁令人感到悲哀!
我一直在關注冪律,如何結合列維模型,我發現列維模型是導致冪律的根本緣由,然而進一步問,列維模型的原因是什麼?
我的答案就是 惰性 !
就我自己而言,我喜歡喝各種新上市的飲料,酒類,我會不斷嘗試新的東西,但是最終我總是會聚焦到某一個牌子,從此不再過問別的,只買這個牌子,比如真露燒酒,比如百事可樂,比如紅標威士忌… 我不換別的牌子不是因為我喜歡這些牌子,而是因為我習慣了這些牌子 , 僅此而已,我的惰性讓我在選飲料選酒時,給了真露,百事,紅標更多的權重,僅此而已。
進一步思考,這是為什麼?
也許就是我大腦的cache吧!我的大腦並不能隨時擁抱變化,你的也不行,再強大的大腦都不行,所以我覺得,分級cache是現代電腦科學中非常非常非常重要的一個設計!
擁抱變化,成了一個多麼優秀且不可及的潛質,因為沒人想擁抱變化!
冪律是什麼導致的,我覺得就是惰性的影響導致的聚集效應導致的。
好了,本文該結束了。現在,溫州老闆正在香港進貨。